咱們知道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的想法,記錄下來,可能不是最好的實現方式,若是您有更好的方法歡迎留言給我一塊兒探討下!