Zuul是Netflix开源的微服务网关,它可以和Eureka,consul,Ribbon,Hystrix等组件配合使用,网上也有很多如何使用zuul的文章,我们也在生产环境使用了,所以读了下zuul的源码,下面把它分享出来,与大家探讨下zuul核心原理。
一、spring-cloud-zuul是如何映射路由的?
zuul的路由映射是使用springMVC功能,我们知道springMVC有两大核心组件:
- HandlerMapping:映射器
- HandlerAdapter:适配器
具体的springMVC原理这里不做讲解,我们来看下zuul是如何自定义HandlerMapping来注册路由映射的?下图是springMVC的类继承关系
很清晰看到Zuul提供的ZuulHandlerMapping是AbstractUrlHandlerMapping的子类,这个类是根据url来查找处理器,核心处理方法在lookupHandler里面:
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
//过滤忽略的路由规则
String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]);
if (PatternMatchUtils.simpleMatch(ignored, urlPath)) {
return null;
}
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
if (this.dirty) {
synchronized (this) {
if (this.dirty) {//如果没有加载过路由或者路由有刷新,则加载路由
registerHandlers();
this.dirty = false;
}
}
}
//根据url调用父类获取处理器
return super.lookupHandler(urlPath, request);
}
private void registerHandlers() {
//使用路由定位器获取路由规则
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
//调用父类,注册处理器
registerHandler(route.getFullPath(), this.zuul);
}
}
}
梳理一下,以上方法的核心几步:
- 判断urlPath是否被忽略,如果忽略则返回null
- 判断路由规则有没有加载过或者更新过,没有加载或者有更新则重新加载
- 注册处理器的时候,使用的是
ZuulController
,是Controller
的子类,对应的适配器是SimpleControllerHandlerAdapter
,也就说每一个路由规则公共处理器都是ZuulController
,这个处理器最终会调用ZuulServlet
经过zuul定义的和自定义的拦截器,这个zuul的核心,后面我们作详细讲解。 - 根据url找到处理器,返回
二、路由定位器
在上面我们注册了路由规则,而路由规则是由路由定位器获取,那么zuul给我们提供哪些路由定位器,类图如下:
- SimpleRouteLocator:主要加载配置文件的路由规则
- DiscoveryClientRouteLocator:服务发现的路由定位器,去注册中心如Eureka,consul等拿到服务名称,以这样的方式
/服务名称/**
映射成路由规则 - CompositeRouteLocator:复合路由定位器,主要集成所有的路由定位器(如配置文件路由定位器,服务发现定位器,自定义路由定位器等)来路由定位。
- RefreshableRouteLocator:路由刷新,只有实现了此接口的路由定位器才能被刷新
扩展:
1、这里我们可以实现自己的路由定位器,扩展自己想要的功能,如从数据库加载路由规则,可以参考文章
2、利用服务发现的路由定位器去加载理由规则的时候,我们只是简单的是把serviceId映射成路由规则,有的时间我们还是想在serviceId和路由之间提供约定 ,于是我们可以使用PatternServiceRouteMapper来实现
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
这样serviceId:myusers-v1
将被映射到路由/ v1 / myusers / **
,这里任何正则表达式都可以接受,根据自己需要自己设定。
三、过滤器
前面提到,所以路由请求都会被控制器ZuulControoler
拦截到,最终交由ZuulServlet
来处理,核心处理代码如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
这段代码体现了zuul过滤器的生命周期,官方提供了一张图很形象的展示:
zuul把过滤器分为四个阶段,分别是
- pre:主要是在请求路由之前调用,很多验证可以在这里做
- route:在路由请求时候被调用,主要用来转发请求
- post:主要用来处理响应请求
- error:当错误发生时,会经由这个类型的过滤器处理
zuul为我们提供了各个阶段的过滤器一共10个
这里我们来着重看下路由阶段的两个过滤器
- SimpleHostRoutingFilter:主要提供当路由设置url方式时,由这个路由器来转发请求,使用的是apache的
CloseableHttpClient
来发送http请求 - RibbonRoutingFilter:当路由设置serviceId时,由此过滤器来转发请求,这里集成了ribbon,Hystrix,实现负载均衡,熔断的功能;默认情况下也是使用apache的
HttpClient
来转发请求
未完待续......
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。