Spring Cloud Hystrix 分析(四)之Zuul集
前面的几节我们重点分析了Hystrix的整个流程以及工作原理,以及基于@HystrixCommand注解使用方式之后的流程分析,这一节我们主要结合实际应用Zuul网关层来进一步分析Zuul网关里面是如何应用Hystrix熔断的!
ZuulFilter过滤器生命周期
这里我们引用Zuul官方的一张过滤器周期图,Zuul的核心功能就是filter,这里我们也能直观的看出,请求下游服务(Origin Server)是在routing filters这类路由过滤器进行,而Zuul的Filters过滤器核心执行逻辑在ZuulServlet,ZuulServlet继承HttpServlet作为总的执行入口,内部就会执行上述的ZuulFilter过滤器生命周期,那么下面我们就直接进入重点,看看我们的Hystrix是怎么被利用起来的!
EnableZuulProxy入口配置
@EnableCircuitBreaker
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyConfiguration.class)
public @interface EnableZuulProxy {
}
当我们使用某一个框架的时候,我们关心最多的一个问题莫过于它的入口在哪里?从入口进行分析无疑会减少很多工作量,@EnableZuulProxy直接作用在应用入口类,然后我们看看引入了ZuulProxyConfiguration这个配置类,那我们接着看看ZuulProxyConfiguration配置
ZuulProxyConfiguration
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
public class ZuulProxyConfiguration extends ZuulConfiguration {
......
// route filters
@Bean
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory<?> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);
return filter;
}
......
}
因为这一节我们是分析Hystrix如何被使用起来的,所以我们只需要关心RibbonRoutingFilter这个route类型的filters,因为这种filter才会请求下游服务
RibbonRoutingFilter
public class RibbonRoutingFilter extends ZuulFilter {
......
//当前过滤器是否生效
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
//配置的routes路由为使用serviceId方式,不是直连的url配置方式
return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
&& ctx.sendZuulResponse());
}
//过滤器执行方法
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
//添加忽略的头信息,zuul的ignored-headers配置,会过滤这些头信息参数
this.helper.addIgnoredHeaders();
try {
//构建Ribbon命令执行的上下文,保存相关参数的对象
RibbonCommandContext commandContext = buildCommandContext(context);
//转发请求,调用下游服务
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
//主要就是封装参数,封装头信息、请求参数信息...
protected RibbonCommandContext buildCommandContext(RequestContext context) {
HttpServletRequest request = context.getRequest();
MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() < 0) {
context.setChunkedRequestBody();
}
String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
String uri = this.helper.buildZuulRequestURI(request);
uri = uri.replace("//", "/");
long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength();
return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength);
}
//转发请求/调用下游服务
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
//封装RibbonCommand对象,内部继承HystrixExecutable,这样就可以使用Hystrix的功能
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
//执行命令,调用下游服务,最终调用到HystrixCommand#execute(),
//这样实现类通过实现Hystrix的run()、getFallback()方法就可以使用Hystrix的熔断功能了
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getStatusCode().value(),
response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
return handleException(info, ex);
}
}
......
}
在RibbonRoutingFilter这个过滤器通过片段中的代码注释信息,我们得知其实这个过滤器就做了一件事,那就是使用Hystrix的功能,把Hystrix引入进来,这样就把调用下游服务的任务交给了Hystrix,通过Hystrix来判断下游是否需要熔断等情况!
HttpClientRibbonCommandFactory
我们回过头继续看下ZuulProxyConfiguration配置类中通过@Import方式引入了RestClientRibbonConfiguration、OkHttpRibbonConfiguration、HttpClientRibbonConfiguration,此处我们的应用基于httpclient方式,那我们就只分析下HttpClientRibbonConfiguration的配置,其他方式大致都是一样的逻辑
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
......
//创建RibbonCommand
@Override
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
//降级实现类,默认null
ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
//获取serviceId,如@FeignClient注解中的value字段
final String serviceId = context.getServiceId();
//获取RibbonLoadBalancingHttpClient客户端
//默认在Ribbon的RibbonClientConfiguration#HttpClientRibbonConfiguration中注册
final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
serviceId, RibbonLoadBalancingHttpClient.class);
//设置负载均衡器ILoadBalancer
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
//返回封装了参数的HttpClientRibbonCommand,内部继承AbstractRibbonCommand
return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider,
clientFactory.getClientConfig(serviceId));
}
}
上面HttpClientRibbonCommandFactory中最重要的一件事情,获取Ribbon的RibbonLoadBalancingHttpClient然后设置负载均衡器!最后返回了HttpClientRibbonCommand,其继承了AbstractRibbonCommand,内部就实现了Hystrix的run()、getFallback()方法
AbstractRibbonCommand
public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwareClient<RQ, RS>, RQ extends ClientRequest, RS extends HttpResponse>
extends HystrixCommand<ClientHttpResponse> implements RibbonCommand {
......
//HystrixCommand的run方法实现
@Override
protected ClientHttpResponse run() throws Exception {
final RequestContext context = RequestContext.getCurrentContext();
//创建一个请求,此处对应RibbonApacheHttpRequest请求
RQ request = createRequest();
//负载均衡客户端执行负载均衡请求
RS response = this.client.executeWithLoadBalancer(request, config);
context.set("ribbonResponse", response);
//Hystrix执行命令超时,则显示关闭Http请求
if (this.isResponseTimedOut()) {
if (response != null) {
response.close();
}
}
return new RibbonHttpResponse(response);
}
//HystrixCommand的getFallback降级方法
@Override
protected ClientHttpResponse getFallback() {
if(zuulFallbackProvider != null) {
return zuulFallbackProvider.fallbackResponse();
}
return super.getFallback();
}
......
}
通过分析我们能得知最终的一个请求流程为:客户端发起请求->HttpServlet-> ZuulServlet->routing filters->Hystrix->Ribbon->返回结果给客户端,至此我们分析了Hystrix在实际应用中(Zuul网关)的一个大致实现,当然Zuul的功能也远非如此,Zuul的功能十分强大,在后续的总结里会单独分析Zuul网关!