龙空技术网

一次升级让我学习Spring Cloud netflix zuul

非鸽传书 215

前言:

当前兄弟们对“apachecommondbutil”可能比较注重,我们都想要知道一些“apachecommondbutil”的相关内容。那么小编也在网络上网罗了一些关于“apachecommondbutil””的相关文章,希望同学们能喜欢,小伙伴们一起来学习一下吧!

一次升级让我学习Spring Cloud netflix zuul前言

架构框架升级后,应用升级涉及到网关。网关的过滤器过滤不到请求。

正好借此机会梳理一下 Zuul的基本原理和解决问题的过程。当然本文也只是浅尝辄止,更多的东西以后有时间再学习

下面详细描述下问题现象:

前端访问时错误信息如下

后台没有进入过滤器的日志,后切换到本地进行测试一样无法进入过滤器。

框架升级前后类的代码及配置信息未变动。变动的基本只有pom文件。但是访问接口时不会进入自定义的filter里面。

着实郁闷,和架构说可能是架构升级引起的问题,架构扯皮说肯定是我改动了啥,没办法已经三天的,所以我决定先自己看下Zuul。希望能找到解决问题的办法。

配置文件如下:

spring:...zuul:# 需要忽略的头部信息,不在传播到其他服务# 注意这里面一直就没有配置过滤的路径  sensitive-headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials  ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken,Access-Control-Allow-Credentials  
过滤器
import ...@Componentpublic class MyFilter extends ZuulFilter {    @Resource    RedisUtil redisUtil;    @Value("...")    private String getUserByToken;    private static final Logger log = LoggerFactory.getLogger(MyFilter.class);    @Override    public String filterType() {        return "route";    }    @Override    public int filterOrder() {        return 0;    }    @Override    public boolean shouldFilter() {        return true;    }    @Override    public Object run() {        log.info("------- begin fileter ----------");        ...        return null;    }   }
从Zuul源码开始

启动类中的注解@EnableZuulProxy

@SpringBootApplication@EnableZuulProxy@ComponentScan({"...", "..."})public class AbcApplication {    public static void main(String[] args) {        SpringApplication.run(AbcApplication.class, args);    }}

进入注解查看。

@EnableCircuitBreaker@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Import({ZuulProxyMarkerConfiguration.class})public @interface EnableZuulProxy {}

这里面引用了ZuulProxyMarkerConfiguration,该类注入了一个其内部类Marker bean,这个bean会触发ZuulProxyAutoConfiguration

//  ----------   ZuulProxyMarkerConfiguration  ------------@Configuration(    proxyBeanMethods = false)public class ZuulProxyMarkerConfiguration {    public ZuulProxyMarkerConfiguration() {    }    @Bean    public ZuulProxyMarkerConfiguration.Marker zuulProxyMarkerBean() {        return new ZuulProxyMarkerConfiguration.Marker();    }    class Marker {        Marker() {        }    }}// ----------   ZuulProxyAutoConfiguration  ------------@Configuration(    proxyBeanMethods = false)@Import({RestClientRibbonConfiguration.class, OkHttpRibbonConfiguration.class, HttpClientRibbonConfiguration.class, HttpClientConfiguration.class})@ConditionalOnBean({Marker.class})public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {    @Autowired(        required = false    )    private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();    @Autowired(        required = false    )    private Registration registration;    @Autowired    private DiscoveryClient discovery;    public ZuulProxyAutoConfiguration() {    }    public HasFeatures zuulFeature() {        return HasFeatures.namedFeature("Zuul (Discovery)", ZuulProxyAutoConfiguration.class);    }    @Bean    @ConditionalOnMissingBean({DiscoveryClientRouteLocator.class})    public DiscoveryClientRouteLocator discoveryRouteLocator(ServiceRouteMapper serviceRouteMapper) {        return new DiscoveryClientRouteLocator(this.server.getServlet().getContextPath(), this.discovery, this.zuulProperties, serviceRouteMapper, this.registration);    }    @Bean    @ConditionalOnMissingBean({PreDecorationFilter.class})    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {        return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties, proxyRequestHelper);    }    @Bean    @ConditionalOnMissingBean({RibbonRoutingFilter.class})    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) {        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);        return filter;    }    @Bean    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientFactory httpClientFactory) {        return new SimpleHostRoutingFilter(helper, zuulProperties, connectionManagerFactory, httpClientFactory);    }    @Bean    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class})    public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper, ZuulProperties zuulProperties, CloseableHttpClient httpClient) {        return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);    }    @Bean    @ConditionalOnMissingBean({ServiceRouteMapper.class})    public ServiceRouteMapper serviceRouteMapper() {        return new SimpleServiceRouteMapper();    }    @Configuration(        proxyBeanMethods = false    )    @ConditionalOnClass({Health.class})    protected static class EndpointConfiguration {        @Autowired(            required = false        )        private HttpTraceRepository traces;        protected EndpointConfiguration() {        }        @Bean        @ConditionalOnAvailableEndpoint        public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {            return new RoutesEndpoint(routeLocator);        }        @ConditionalOnAvailableEndpoint        @Bean        public FiltersEndpoint filtersEndpoint() {            FilterRegistry filterRegistry = FilterRegistry.instance();            return new FiltersEndpoint(filterRegistry);        }        @Bean        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {            TraceProxyRequestHelper helper = new TraceProxyRequestHelper(zuulProperties);            if (this.traces != null) {                helper.setTraces(this.traces);            }            return helper;        }    }    @Configuration(        proxyBeanMethods = false    )    @ConditionalOnMissingClass({"org.springframework.boot.actuate.health.Health"})    protected static class NoActuatorConfiguration {        protected NoActuatorConfiguration() {        }        @Bean        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {            ProxyRequestHelper helper = new ProxyRequestHelper(zuulProperties);            return helper;        }    }}

ZuulProxyAutoConfiguration注入了一些 filter,它继承的父类ZuulServerAutoConfiguration。这个父类会在缺失zuulServlet bean的情况下注入了ZuulServlet,该类是zuul的核心类。当然也注入了一些其他类。

public class ZuulServerAutoConfiguration {            // 初始化ZuulFilterInitializer类,将所有filter向FilterRegistry注册。FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器,并有一些基本的CRUD方法。    @Configuration(proxyBeanMethods = false)    protected static class ZuulFilterConfiguration {        @Autowired        private Map<String, ZuulFilter> filters;        @Bean        public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,                TracerFactory tracerFactory) {            FilterLoader filterLoader = FilterLoader.getInstance();            FilterRegistry filterRegistry = FilterRegistry.instance();            return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,                    filterLoader, filterRegistry);        }    }        @Bean    @ConditionalOnMissingBean(name = "zuulServlet")    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",            matchIfMissing = true)    public ServletRegistrationBean zuulServlet() {        ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(                new ZuulServlet(), this.zuulProperties.getServletPattern());        // The whole point of exposing this servlet is to provide a route that doesn't        // buffer requests.        servlet.addInitParameter("buffer-requests", "false");        return servlet;    }    @Bean    @ConditionalOnMissingBean(name = "zuulServletFilter")    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",            matchIfMissing = false)    public FilterRegistrationBean zuulServletFilter() {        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();        filterRegistration.setUrlPatterns(                Collections.singleton(this.zuulProperties.getServletPattern()));        filterRegistration.setFilter(new ZuulServletFilter());        filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);        // The whole point of exposing this servlet is to provide a route that doesn't        // buffer requests.        filterRegistration.addInitParameter("buffer-requests", "false");        return filterRegistration;    }}

ZuulServlet作用类似于Spring MVC中的DispatchServlet,起到了前端控制器的作用,所有的请求都由它接管。(主要看下service方法)

public class ZuulServlet extends HttpServlet {    @Override    public void init(ServletConfig config) throws ServletException {        super.init(config);        String bufferReqsStr = config.getInitParameter("buffer-requests");        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;        zuulRunner = new ZuulRunner(bufferReqs);    }        @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();        }    }}

这里先关注下这几行代码。

// 1、初始化 RequestContext (跟踪进去可以看到是将当前的servlet放入ThreadLocal中  RequestContext继承的是 ConcurrentHashMap)init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);// 2、获取 contextRequestContext context = RequestContext.getCurrentContext();// 3、释放 contextRequestContext.getCurrentContext().unset();

每个请求生成RequestContext在请求结束时销毁掉该RequestContext,RequestContext的生命周期为请求到zuulServlet开始处理,直到请求结束返回结果。RequestContext对象在处理请求的过程中,一直存在,所以这个对象为所有FIlter共享。

看代码很容易得到下面的执行顺序

preRoute();route();postRoute();

这三个方法最终调用的都是 zuulRunner,作用是将请求的HtppServletRequest,HttpSerletResponse放在RequestContext类中(init方法),并包装一个FitlerProcessor。FilterProcessor调用Filters类,

public void postRoute() throws ZuulException {    FilterProcessor.getInstance().postRoute();}public void route() throws ZuulException {    FilterProcessor.getInstance().route();}public void preRoute() throws ZuulException {    FilterProcessor.getInstance().preRoute();}....

依据上面我简单地画了一个图(先凑合看,如有错误欢迎留言指正)

问题分析过程

1、怀疑是不是Spring容器中没有 zuulServlet这个Bean。因此我写了一个方法从 FactoryBean中查看是否存在

这里可以看到时存在对应的Bean的且 urls 数组中数据为 /zuul/*。此使我怀疑是升级后默认值变化引起的。因此我切换回之前的版本后再次使用这个方法来查看,发现结果是一样的。

2、也许是jar包版本的问题,检查下升级前后jar包版本变化。版本的确是变化了,在pom中将升级后的版本调整至升级前的版本仍然无效

3、打开debug日志,调用任意接口查看日志(我调用的是.../returnfrontUrl)

2021-07-12 17:54:10.731 DEBUG 6732 --- [ XNIO-1 task-61] o.s.s.w.h.S.SESSION_LOGGER               : No session found by id: Caching result for getSession(false) for this HttpServletRequest.2021-07-12 17:54:10.986 DEBUG 6732 --- [ XNIO-1 task-62] io.undertow.request.security             : Attempting to authenticate /valueAdded/templateMessage/returnfrontUrl, authentication required: false2021-07-12 17:54:10.986 DEBUG 6732 --- [ XNIO-1 task-62] io.undertow.request.security             : Authentication outcome was NOT_ATTEMPTED with method io.undertow.security.impl.CachedAuthenticatedSessionMechanism@b9d4b71 for /valueAdded/templateMessage/returnfrontUrl2021-07-12 17:54:10.986 DEBUG 6732 --- [ XNIO-1 task-62] io.undertow.request.security             : Authentication result was ATTEMPTED for /valueAdded/templateMessage/returnfrontUrl2021-07-12 17:54:10.987 DEBUG 6732 --- [ XNIO-1 task-62] c.c.s.l.c.cache.CacheControlFilter       : Looking up cache seconds for [/valueAdded/templateMessage/returnfrontUrl]2021-07-12 17:54:10.987 DEBUG 6732 --- [ XNIO-1 task-62] c.c.s.l.c.cache.CacheControlFilter       : Applying CacheControl to [/valueAdded/templateMessage/returnfrontUrl]2021-07-12 17:54:10.987 DEBUG 6732 --- [ XNIO-1 task-62] o.s.web.servlet.DispatcherServlet        : POST ".../returnfrontUrl", parameters={}2021-07-12 17:54:10.987 DEBUG 6732 --- [ XNIO-1 task-62] o.s.s.w.h.S.SESSION_LOGGER               : No session found by id: Caching result for getSession(false) for this HttpServletRequest.2021-07-12 17:54:10.988 DEBUG 6732 --- [ XNIO-1 task-62] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]2021-07-12 17:54:10.989 DEBUG 6732 --- [ XNIO-1 task-62] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found2021-07-12 17:54:10.990 DEBUG 6732 --- [ XNIO-1 task-62] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND2021-07-12 17:54:10.990 DEBUG 6732 --- [ XNIO-1 task-62] o.s.s.w.h.S.SESSION_LOGGER               : No session found by id: Caching result for getSession(false) for this HttpServletRequest.2021-07-12 17:54:10.990 DEBUG 6732 --- [ XNIO-1 task-62] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for POST "/error", parameters={}2021-07-12 17:54:10.990 DEBUG 6732 --- [ XNIO-1 task-62] o.s.s.w.h.S.SESSION_LOGGER               : No session found by id: Caching result for getSession(false) for this HttpServletRequest.2021-07-12 17:54:10.990 TRACE 6732 --- [ XNIO-1 task-62] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error}, { /error, produces [text/html]}]

这里发现了和前端报错一样的信息,追溯上面的日志。从上面的日志来看,应用没有走到 zuulServlet中。

到这里可以确认就是过滤器没有生效,且容器中是由对应的Bean实例的,因此目前很大的可能性就是因为升级以前的默认配置可能被变动了因此需要手动增加配置

zuul:#需要忽略的头部信息,不在传播到其他服务  sensitive-headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials  ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken,Access-Control-Allow-Credentials#  ignored-patterns: ''  routes:    abc:      path: '/abc/**'      stripPrefix: false   ...

再次访问,问题得到了解决。

好吧事实证明, 不要轻易怀疑架构出问题(除非找到实锤)。

zuul的配置

下面是搬运网上的资料,大家可以参考。

zuul:  # 是否开启重试,默认为false  # 注意使用此功能需要引入依赖,并且需要配合最下面的ribbon配置一起使用  #        <dependency>  #          <groupId>org.springframework.retry</groupId>  #         <artifactId>spring-retry</artifactId>  #    </dependency>  retryable: false  # 设置全局访问的前缀,配置之后所有请求前缀需要加上/api  prefix: /api  # 配置该属性将会禁止header中的某些属性向下传递,默认为"Cookie", "Set-Cookie", "Authorization"  # 其源码在PreDecorationFilter.run()中体现,其原理主要是将配置项加入下面的ignored-headers而已  sensitive-headers:    - Cookie    - Set-Cookie    - Authorization    - token  # 忽略请求头中的某些属性,具体实现在构建ProxyRequestHelper.buildZuulRequestHeaders(HttpServletRequest request) 中体现,  # 会根据配置的ignored-headers排除掉忽略的header然后重新构造一个header供ribbon使用  ignored-headers:    - token  # 此属性只会对SimpleHostRoutingFilter生效,RibbonRoutingFilter使用的是ribbon的配置  host:    # 最大连接数,默认为200    max-total-connections: 200    # 单个路由可以使用的最大连接数,默认为20    max-per-route-connections: 20    # http client中从connection pool中获得一个connection的超时时间,默认为-1,不超时    connection-request-timeout-millis: -1    # 连接建立的超时时间;,默认为2000ms    connect-timeout-millis: 2000    # 响应超时时间,默认为10000ms    socket-timeout-millis: 10000  # 路由规则  routes:    # 用户自定义一个名称,内部是一个Map<String, ZuulRoute>    user-server:      # 需要拦截后转发请求的地址,支持通配符匹配,不配置默认为/user-server/**      path: /user-server/**      # spring cloud服务id,不配默认为上面自定义的名称      serverId: user-server      # 是否支持上面的prefix,默认为true,设为false访问该服务前缀不需要/api      stripPrefix: true      # 将请求发送到指定的服务器,不会走ribbon、hystrix,正常请求是走RibbonRoutingFilter,此请求会走SimpleHostRoutingFilter      url:       # 和全局配置作用一样,优先级高于全局配置      sensitive-headers:        - Cookie        - Set-Cookie        - Authorization      # 是否启用自定义配置,默认为false,初始化时会根据sensitive-headers的长度来判断,长度大于0则为true      custom-sensitive-headers: false  # 可配置THREAD(线程池模式), SEMAPHORE(信号量模式),默认为SEMAPHORE  # 此功能是基于hystrix实现,将请求包装为一个HystrixCommond,hystrix内部自身实现了熔断、隔离等机制  ribbonIsolationStrategy: SEMAPHORE  # 可以通过这个配置控制所有服务的信号量,默认为100,这里指的是每个服务都是100不是所有服务加起来为100  semaphore:    maxSemaphores: 100  # 单独配置每个服务的信号量,不配置默认为100  eureka:    # 用户自定义一个名称    user-server:      # 信号量模式      semaphore:        # 最大访问量,默认为全局配置的值也就是100,单独配置的优先级高于全局配置        # 此配置的源码在AbstractRibbonCommand.getSetter(final String commandKey,ZuulProperties zuulProperties, IClientConfig config)中被使用        maxSemaphores: 100        # ribbon配置,ribbon的初始化源码在RibbonClientConfiguration.ribbonClientConfig()# 核心代码为config.loadProperties(this.name);这句代码加载了ribbon的配置# 重试是针对在eureka注册表中能够获取到服务对应的实例然后发起http请求失败时生效,如果根本无法获取到服务实例则会直接抛出异常ribbon:  # 下面三个参数在源码DefaultLoadBalancerRetryHandler的构造方法中被使用  # 构造方法:public DefaultLoadBalancerRetryHandler(IClientConfig clientConfig)  # 是否所有请求都启用重试机制,默认为false,源码中发现默认的时候只支持的是GET请求,如果配置为true支持所有请求  # 源码:return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();  # 重试核心源代码在RetryTemplate.doExecute(RetryCallback<T, E> retryCallback,RecoveryCallback<T> recoveryCallback, RetryState state)  # 重试的原理如下图所示,N代表第一次请求,R代表重试  # 第一种情况MaxAutoRetries=1,MaxAutoRetriesNextServer=1,总请求次数为4次  # NR NR  # 第二种情况MaxAutoRetries=2,MaxAutoRetriesNextServer=1,总请求次数为6次  # NRR NRR  # 第三种情况MaxAutoRetries=1,MaxAutoRetriesNextServer=2,总请求次数为6次  # NR NR NR  # 第四种情况MaxAutoRetries=2,MaxAutoRetriesNextServer=2,总请求次数为9次  # NRR NRR NRR  OkToRetryOnAllOperations: true  # 当前实例重试次数,默认为0  MaxAutoRetries: 1  # 切换实例重试次数,默认为1  MaxAutoRetriesNextServer: 2        

标签: #apachecommondbutil