在微服务架构下,后端服务的信息一般是动态变化的,客户端很难及时获取动态变化的服务地址信息,因此在微服务架构中为了简化服务调用逻辑,通常会引用API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间调用的复杂度。
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。Zuul中可以做到服务转发、管理,负载均衡,权限控制等等。
Zuul实现API网关功能最核心的部件就是:过滤器Filter。每一个进入zuul的HTTP请求,都会经过一系列的过滤器处理链,最终得到处理结果返回给调用端。
其中zuul主要过滤器有:
(1)pre过滤器
由上图可知,外部HTTP请求进入zuul时,首先会进入pre过滤器。请求在这里会被pre过滤器处理,比如请求路由前的参数加工、请求头处理、权限验证、IP黑名单、路由规则匹配等。当存在多个pre过滤器时,按照filterOrder()方法设置的权重依次执行。
(2)routing过滤器
当请求从pre过滤器出来过后就会被routing过滤器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,进入post阶段。
(3)post过滤器
此阶段请求将会被post类型的过滤器进行处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工、转换和操作结果日志记录等。如果请求正常结束,post过滤器会将结果返回给调用端。
(4)error过滤器
error过滤器是特殊的过滤器,在以上三个环节中任何一个环节发生错误,请求都会进入error过滤器中。此阶段中可以进行公共异常处理。error过滤器执行完毕后,请求也会进入post过滤器中,通过post过滤器将结果返回给调用端。
(1)请求进入DispatchServlet.doService()后,通过ZuulController.handleRequest()最后将请求传给 ZuulServlet.service()方法。ZuulServlet 源码如下。
public class ZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
private ZuulRunner zuulRunner;
public ZuulServlet() {
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true");
this.zuulRunner = new ZuulRunner(bufferReqs);
}
/**
覆盖HttpServlet父类中的方法,执行过滤器中的“pre”,“routing”,“post”过滤
*/
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
/*
初始化zuulRunner
*/
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
/*
执行pre过滤器
*/
this.preRoute();
} catch (ZuulException var12) {
/*
执行error过滤器。
此处可以看出,在pre、routing、post三个阶段中都贯穿这error过滤器
*/
this.error(var12);
this.postRoute();
return;
}
try {
/*
执行routing过滤器
*/
this.route();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
/*
执行post过滤器
*/
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
void postRoute() throws ZuulException {
this.zuulRunner.postRoute();
}
void route() throws ZuulException {
this.zuulRunner.route();
}
void preRoute() throws ZuulException {
this.zuulRunner.preRoute();
}
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
this.zuulRunner.init(servletRequest, servletResponse);
}
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
this.zuulRunner.error();
}
}
(2)请求通过ZuulRunner转发过后,进入FilterProcessor类中。其中着重看两个方法:
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
/*
获取相同类型的所有过滤器
sType分别为“pre”、“route”、“post”、“error”
*/
List list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= (Boolean)result;
}
}
}
return bResult;
}
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
/*
RequestContext:用于在过滤器之间传递消息。
它的数据保存在每个请求的ThreadLocal中。
它用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse都存储在RequestContext中。
RequestContext扩展了ConcurrentHashMap,所以,任何数据都可以存储在上下文中
*/
RequestContext ctx = RequestContext.getCurrentContext();
/*
是否需要打印debug信息
*/
boolean bDebug = ctx.debugRouting();
String metricPrefix = "zuul.filter-";
long execTime = 0L;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
Object o = null;
Throwable t = null;
if (bDebug) {
Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
copy = ctx.copy();
}
/*
调用filter的run方法,执行过滤逻辑。
ZuulFilter.runFilter()方法中通过this.run() 直接调用我们自己filter的run方法。
*/
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
switch(s) {
case FAILED:
t = result.getException();
/*添加filter执行信息*/
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
if (bDebug) {
Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
Debug.compareContextState(filterName, copy);
}
}
if (t != null) {
throw t;
} else {
this.usageNotifier.notify(filter, s);
return o;
}
} catch (Throwable var15) {
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage());
}
this.usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (var15 instanceof ZuulException) {
throw (ZuulException)var15;
} else {
ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
(3)ZuulFilter.runFilter()方法
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
/*当前filter是否可用*/
if (!this.isFilterDisabled()) {
/*
调用我们实现的filter中的shouldFilter()方法
*/
if (this.shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = this.run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable var7) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(var7);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
(4)ZuulFilter的排序比较方法
public int compareTo(ZuulFilter filter) {
/*
同类型的filter通过提供的filterOrder进行排序
*/
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
其中比较重要的几个:
(1)PreDecorationFilter:
此过滤器是源码提供的pre阶段最后执行的核心过滤器。主要职责就是请求路由前的预处理,例如路由规则匹配、请求上下文设置基本信息和路由信息。
(2)RibbonRoutingFilter:
此过滤器是源码提供的routing阶段第一个执行的过滤器,通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。此过滤器只对请求上下文中存在serviceId参数的请求进行处理,也就是说只对通过serviceId调用注册服务的方式进行拦截处理。如果routing规则配置的是URL的方式,请求会被SimpleHostRoutingFilter拦截处理。如果routing规则配置的是forward,转发到zuul本地服务,请求会被SendForwardFilter拦截处理。
application.yml配置文件示例如下:
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
serviceUrl:
defaultZone: http://localhost:8001/eureka/
server:
route53-bind-rebind-retries:
server:
port: 8002
spring:
application:
name: zuul
servlet:
multipart:
max-file-size: 100Mb
max-request-size: 100Mb
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
zuul:
routes:
# 所有以/admin/开头的请求都会被转发到api-admin服务上去。
# 此routing规则会被RibbonRoutingFilter过滤器处理
api-admin:
path: /admin/**
serviceId: api-admin
# 所有类似localhost:8002/local/**的请求地址都会转发到网关本地的localhost:8002/**上
# 此routing规则会被SendForwardFilter过滤器处理
local:
path: /local/**
url: forward:/
#以test开头的请求直接路由到http://192.168.43.166:8005/地址去
#此routing规则会被SimpleHostRoutingFilter过滤器处理
test:
path: /test/**
url: http://192.108.0.166:8005/
(3)SendErrorFilter:
此过滤器是源码提供post阶段第一个执行的过滤器。主要处理请求上下文中包含error.status_code参数且还没有被此过滤器处理过的请求。具体逻辑就是利用请求上下文中的错误信息来组织成一个forward到API网关/error错误端点的请求来产生错误响应。
(4)SendResponseFilter:
它的执行顺序为1000,是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含它们其中一个的时候就会执行处理逻辑。而该过滤器的处理逻辑就是利用请求上下文的响应信息来组织需要发送回客户端的响应内容。
文章参考:http://blog.didispace.com/spring-cloud-source-zuul/
欢迎加入【缘·乐山】571814743,交流学习经验,技术分享,创业孵化