Spring Cloudspring cloud

Spring Cloud Hystrix 分析(四)之Zuul集

2021-02-28  本文已影响0人  Blog

前面的几节我们重点分析了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网关!

上一篇下一篇

猜你喜欢

热点阅读