咱们知道spring-cloud-zuul是依赖springMVC来注册路由的,而springMVC又是在创建在servlet之上的(这里微服务专家杨波老师写过一篇文章讲述其网络模型,能够参考看看),在servlet3.0以前使用的是thread per connection方式处理请求,就是每个请求须要servlet容器为其分配一个线程来处理,直到响应完用户请求,才被释放回容器线程池,若是后端业务处理比较耗时,那么这个线程将会被一直阻塞,不能干其余事情,若是耗时请求比较多时,servlet容器线程将被耗尽,也就没法处理新的请求了,因此Netflix还专门开发了一个熔断的组件Hystrix
来保护这样的服务,防止其因后端的一些慢服务耗尽资源,形成服务不可用。不过在servlet3.0出来以后支持异步servlet了,能够把业务操做放到独立的线程池里面去,这样能够尽快释放servlet线程,springMVC自己也支持异步servlet了,本篇文章将带你如何使用servlet3.0的异步特性来改造spring-cloud-zuul优化其性能。java
咱们先来建立一个zuul的maven项目,就叫async-zuul
吧,具体代码我放在github上了。项目依赖于consul作注册中心,启动时先要在本地启动consul,为了能看到效果咱们先来新建一个zuul的filter类:git
@Component public class TestFilter extends ZuulFilter { //忽略无关代码,具体看github上的源码 @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); System.out.println("==============线程名称:" + Thread.currentThread().getName() + ",访问url:" + request.getRequestURI() + "================"); return null; } }
主要就是打印下线程的名称,这个filter是zuul的前置过滤器,咱们主要就是看下在zuul在执行路由时是由什么线程执行的。好了咱们来启动下main方法,不过咱们还须要一个后端服务,很简单,建立一个springcloud项目名叫book
便可,并提供一个url:/book/borrow
,启动后把服务注册到consul上,成功后咱们经过zuul的代理来访问下book服务:github
http://localhost:8080/book/book/borrow
输出:spring
==========线程名称:http-nio-8080-exec-10,访问url:/book/book/borrow=======
很清楚的看到执行filter的线程是servlet容器线程,等下咱们改形成异步后再作一下对比。segmentfault
还记得在文章spring-cloud-zuul原理解析(一)中咱们分析到,spring-cloud-zuul的路由映射使用到springMVC的两大组件ZuulHandlerMapping
和ZuulController
,目前确定是没法支持异步servlet的。那么这两个类在哪里被加载的呢?答案就是ZuulServerAutoConfiguration
,此类是spring-cloud-zuul自动配置类,源码以下:后端
@Configuration @ConditionalOnBean(annotation=EnableZuulProxy.class) @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration { //无关代码省略.......... @Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; } //无关代码省略.......... }
能够看到这两个类在spring-cloud-zuul中并无为咱们提供扩展,无法替换它们来实现servlet的异步逻辑,那该怎么办呢?spring-cloud-zuul还有一个自动配置配ZuulProxyAutoConfiguration
继承自ZuulServerAutoConfiguration
,咱们把这两个配置类所有替换掉,换成咱们本身的不就能够了么?是的,不过首先咱们得先排除加载这两个自动配置类,springboot为咱们提供这样的设置:api
@EnableZuulProxy //排除ZuulProxyAutoConfiguration配置类 @SpringBootApplication(exclude=ZuulProxyAutoConfiguration.class) public class Startup { public static void main(String[] args) { SpringApplication.run(Startup.class, args); } }
以后,咱们建立两个本身的配置配,彻底拷贝ZuulServerAutoConfiguration
和ZuulProxyAutoConfiguration
这两个类,不过光这两个类仍是不行,这两个类使用到了类RibbonCommandFactoryConfiguration
,里面的内部类是protected
的,咱们无法使用,也得本身建立,也是拷贝自RibbonCommandFactoryConfiguration
,而后咱们还需修改ZuulController
的逻辑改为异步方式,因此再新建一个类继承ZuulController,这样咱们就新建了本身的三个配置类和一个本身的
ZuulController`类,以下:springboot
public class MyZuulController extends ZuulController{ private final AsyncTaskExecutor asyncTaskExecutor; public MyZuulController(AsyncTaskExecutor asyncTaskExecutor) { super(); this.asyncTaskExecutor = asyncTaskExecutor; } @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //真正的异步化逻辑 final AsyncContext asyncCtx = request.startAsync(); this.asyncTaskExecutor.execute(new Runnable() { @Override public void run() { try { MyZuulController.this.handleRequestInternal((HttpServletRequest)asyncCtx.getRequest(), (HttpServletResponse)asyncCtx.getResponse()); }catch (Exception e) { e.printStackTrace(); }finally { asyncCtx.complete(); RequestContext.getCurrentContext().unset(); } } }); return null; } } @Configuration @ConditionalOnBean(annotation=EnableZuulProxy.class) @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) @Import(ServerPropertiesAutoConfiguration.class) public class MyZuulServerAutoConfiguration { //省略代码,彻底拷贝自ZuulServerAutoConfiguration /** * 自定义线程池 * @return */ @Bean public AsyncTaskExecutor zuulAsyncPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("zuul-async-"); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(50); return executor; } //这里换成咱们本身的MyZuulController类,而且传入一个咱们自定义的线程池 @Bean public ZuulController zuulController(AsyncTaskExecutor asyncTaskExecutor) { return new MyZuulController(asyncTaskExecutor); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,AsyncTaskExecutor asyncTaskExecutor) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController(asyncTaskExecutor)); mapping.setErrorController(this.errorController); return mapping; } } @Configuration @Import({ MyRibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, MyRibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, MyRibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class }) @ConditionalOnBean(annotation=EnableZuulProxy.class) public class MyZuulProxyAutoConfiguration extends MyZuulServerAutoConfiguration { //省略代码,彻底拷贝自ZuulProxyAutoConfiguration } public class MyRibbonCommandFactoryConfiguration { //省略代码,彻底拷贝自RibbonCommandFactoryConfiguration }
这里咱们稍做了一点修改网络
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) //替换成: @ConditionalOnBean(annotation=EnableZuulProxy.class)
这样作的目的主要是配合注解@EnableZuulProxy
使用,只有开启了此注解才加载配置类。咱们还替换ZuulController
成咱们自定义的MyZuulController
了,这里是异步化的主要逻辑,其实也很是简单,就是使用了serv3.0为咱们提供的api来开启异步化。万事已经具有啦,咱们再次启动zuul,访问上面的url,输出:app
==========线程名称:zuul-async-1,访问url:/book/book/borrow==========
哈哈,执行filter的线程变成咱们自定义的线程名称了,达到了咱们的需求,servlet已经变成异步的了。
这是我对spring-cloud-zuul实现异步servlet的想法,记录下来,可能不是最好的实现方式,若是您有更好的方法欢迎留言给我一块儿探讨下!