servlet 3.0非同期特性を使用してspring-cloud-zulを改造

7392 ワード

spring-cloud-zulはspringMVCに依存してルーティングを登録していることを知っていますが、springMVCはservletの上に構築されています(ここではマイクロサービス専門家の楊波先生がネットワークモデルを書いたことがありますが、参考にしてみてください).servlet 3.0の前にthread per connection方式でリクエストを処理していました.各リクエストはservletコンテナにスレッドを割り当てて処理する必要があり、ユーザーリクエストに応答するまでコンテナスレッドプールに戻されます.バックエンドのトラフィック処理に時間がかかると、このスレッドはブロックされ続け、他のことはできません.消費時間リクエストが多い場合、servletコンテナスレッドは消費され、新しいリクエストを処理できません.したがって、Netflixは、バックエンドの遅いサービスのためにリソースが消費され、サービスが利用できないことを防止するために、溶断されたコンポーネントHystrixを開発した.しかし、servlet 3.0が出てから非同期servletをサポートします.ビジネス操作を独立したスレッドプールに入れることができます.これにより、servletスレッドをできるだけ早く解放することができます.springMVC自体も非同期servletをサポートします.この文章では、servlet 3.0の非同期特性を使用してspring-cloud-zulのパフォーマンスを最適化する方法について説明します.
まずzulのmavenプロジェクトを作成しましょう.async-zuulと言います.具体的なコードはgithubに置いてあります.プロジェクトはconsulに依存して登録センターを行い、起動時にローカルでconsulを起動し、効果を見るためにzulのfilterクラスを新規作成します.
@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はzulのフロントフィルタです.zulがルーティングを実行するときにどのスレッドで実行されているかを見てみましょう.次のmainメソッドを起動しますが、バックエンドサービスが必要です.簡単です.springcloudプロジェクトの名前はbookです.url:/book/borrowを提供します.起動後、consulにサービスを登録します.成功したら、zulのエージェントを通じてbookサービスに問い合わせます.
http://localhost:8080/book/book/borrow

出力:
==========    :http-nio-8080-exec-10,  url:/book/book/borrow=======

filterを実行するスレッドがservletコンテナスレッドであることはよくわかりますが、非同期に改造してから比較してみましょう.
文章spring-cloud-zul原理解析(一)ではspring-cloud-zulのルーティングマッピングがspringMVCの2大コンポーネントZuulHandlerMappingZuulControllerに使用されていることを分析したが,現在は非同期servletをサポートできないに違いない.では、この2つのクラスはどこでロードされていますか?答えはZuulServerAutoConfigurationです.spring-cloud-zul自動構成クラスです.ソースコードは次のとおりです.
@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;
    }
    //      ..........
}

この2つのクラスはspring-cloud-zulで拡張を提供していないことがわかり、servletの非同期論理を実現するためにそれらを置き換えることができません.どうすればいいのでしょうか.Spring-cloud-zulにはZuulProxyAutoConfigurationからZuulServerAutoConfigurationに続く自動構成があります.この2つの構成クラスをすべて置き換えて、私たち自身に変えればいいのではないでしょうか.はい、しかし、まずロードの2つの自動構成クラスを排除しなければなりません.springbootはこのような設定を提供します.
@EnableZuulProxy
//  ZuulProxyAutoConfiguration   
@SpringBootApplication(exclude=ZuulProxyAutoConfiguration.class)
public class Startup {
    public static void main(String[] args) {
        SpringApplication.run(Startup.class, args);
    }
}

その後、私たちは2つの自分の配置を作成して、ZuulServerAutoConfigurationZuulProxyAutoConfigurationの2つのクラスを完全にコピーしましたが、この2つのクラスだけではだめです.この2つのクラスはクラスRibbonCommandFactoryConfigurationに使用されています.中の内部クラスはprotectedで、私たちは使用できません.自分で作成しなければなりません.RibbonCommandFactoryConfigurationからコピーしなければなりません.それから、ZuulControllerの論理を非同期方式に変更する必要があります.ZuulController ZuulController`クラスを継承するクラスを追加します.以下のようにします.
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に置き換えました.ここでは非同期化の主な論理ですが、serv 3.0が提供してくれたapiを使用して非同期化をオンにします.準備が整いました.zulを再起動し、上のurlにアクセスし、出力します.
==========    :zuul-async-1,  url:/book/book/borrow==========

ははは、filterを実行するスレッドは私たちのカスタムスレッド名になり、私たちのニーズに達し、servletは非同期になりました.
これは私がspring-cloud-zulに対して非同期servletを実現する考えで、記録して、最も良い実現方式ではないかもしれませんが、もしあなたがもっと良い方法があれば、伝言を残して私に一緒に検討してください!