2017-04-25 7 views
12

CONFIGURATION
Webサーバー「長い」時間を待っている間:nginxの
アプリケーションサーバ:200件の要求提供スレッド
私のサーバー用 予想応答時間のデフォルト設定でのTomcat:< 30秒(ありサードパーティの依存関係のたくさんの)リユースTomcatのスレッド

SCENARIO
アプリケーションが使用するトークンを生成する必要がありますごとに10秒です。トークン生成の予想時間は約5秒ですが、サードパーティ製のシステムがネットワーク経由で接続されているため、これは明らかに一貫性がなく、10秒になる可能性があります。
トークン生成プロセス中に、毎秒着信要求のほぼ80%が待機する必要があります。私はトークン生成のために待機している要求が「長い」時間を待つ必要がありますので
を起こるはず、待っている間に他の着信要求を提供するために再利用するために役立つこれらの要求のための理由がない信じるもの

トークン生成プロセスを完了します。
基本的には、私の20%が奉仕し続けるのが理にかなっています。待機中のスレッドが他のリクエストに利用されていない場合、Tomcatリクエストのサービスの制限に達し、サーバーが本質的に停止します。私はTomcatのNIOコネクタへの切り替えはこの仕事をするだろうと予想当初

を試してみましたWHAT

。しかし、thisの比較を見て、私は本当に希望がありませんでした。それにもかかわらず、私は要求を10秒間待つように強制し、それはうまくいかなかった。
私は、待っている間にリクエストを棚上げし、このスレッドが自由に再利用できるようにする必要があることを知らせる必要があると考えています。同様に、要求が転送される準備ができたらスレッドプールからスレッドを取得するためにTomcatが必要になります。しかし、私はそれを行う方法やそれが可能であっても盲目になっています。

ガイダンスやヘルプはありますか?

+0

トークン生成プロセス中に、毎秒着信要求の約80%が待機する必要があります。* "、着信要求の80%がアプリケーションへの着信要求か、トークン生成のためにサードパーティシステムに送信したリクエスト。私はあなたがあなたの完全な答えでこの全体を明確にする必要があると思います。私が言ったように、誰もが明白ではないかもしれないので、解明して、解決策を得るチャンスを増やすかもしれないからです。 – hagrawal

+0

@hagrawal着信要求の80%がサードパーティを待っています。 –

答えて

6

非同期サーブレットが必要ですが、外部トークンジェネレータへの非同期HTTPコールも必要です。トークン要求ごとに1つのスレッドをまだ作成している場合は、サーブレットからExecutorServiceにスレッドプールを使用してリクエストを渡すことで、何も得られません。あるスレッドが複数のHTTP要求を処理できるように、スレッドをHTTP要求から切り離す必要があります。これは、Apache Asynch HttpClientまたはAsync Http Clientのような非同期HTTPクライアントで実現できます。

まずあなたはこのサーブレットは、Apache HttpClientを非同期を使用して非同期HTTP呼び出しを行い、この1

public class ProxyService extends HttpServlet { 

    private CloseableHttpAsyncClient httpClient; 

    @Override 
    public void init() throws ServletException { 
     httpClient = HttpAsyncClients.custom(). 
       setMaxConnTotal(Integer.parseInt(getInitParameter("maxtotalconnections"))).    
       setMaxConnPerRoute(Integer.parseInt(getInitParameter("maxconnectionsperroute"))). 
       build(); 
     httpClient.start(); 
    } 

    @Override 
    public void destroy() { 
     try { 
      httpClient.close(); 
     } catch (IOException e) { } 
    } 

    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) { 
     AsyncContext asyncCtx = request.startAsync(request, response); 
     asyncCtx.setTimeout(ExternalServiceMock.TIMEOUT_SECONDS * ExternalServiceMock.K);  
     ResponseListener listener = new ResponseListener(); 
     asyncCtx.addListener(listener); 
     Future<String> result = httpClient.execute(HttpAsyncMethods.createGet(getInitParameter("serviceurl")), new ResponseConsumer(asyncCtx), null); 
    } 

} 

のような非同期サーブレットを作成する必要があります。 RFC 2616の仕様に従って、HttpAsyncClientはデフォルトで同じホストへの最大2つの同時接続のみを許可するため、ルートごとの最大接続数を設定することができます。そして、HttpAsyncClient configurationに示すように構成できる他の多くのオプションがあります。 HttpAsyncClientは作成するのに費用がかかります。したがって、各GET操作でインスタンスを作成する必要はありません。

1つのリスナーがAsyncContextにフックされます。このリスナーは、上記の例でタイムアウトを処理するためにのみ使用されます。

public class ResponseListener implements AsyncListener { 

    @Override 
    public void onStartAsync(AsyncEvent event) throws IOException { 
    } 

    @Override 
    public void onComplete(AsyncEvent event) throws IOException { 
    } 

    @Override 
    public void onError(AsyncEvent event) throws IOException { 
     event.getAsyncContext().getResponse().getWriter().print("error:"); 
    } 

    @Override 
    public void onTimeout(AsyncEvent event) throws IOException { 
     event.getAsyncContext().getResponse().getWriter().print("timeout:"); 
    } 

} 

次に、HTTPクライアントのコンシューマーが必要です。このコンシューマは、complete()を呼び出して、Future<String>を呼び出し元ProxyServiceサーブレットに返すステップとして、buildResult()がHttpClientによって内部的に実行されるときに、AsyncContextに通知します。

public class ResponseConsumer extends AsyncCharConsumer<String> { 

    private int responseCode; 
    private StringBuilder responseBuffer; 
    private AsyncContext asyncCtx; 

    public ResponseConsumer(AsyncContext asyncCtx) { 
     this.responseBuffer = new StringBuilder(); 
     this.asyncCtx = asyncCtx; 
    } 

    @Override 
    protected void releaseResources() { } 

    @Override 
    protected String buildResult(final HttpContext context) { 
     try { 
      PrintWriter responseWriter = asyncCtx.getResponse().getWriter(); 
      switch (responseCode) { 
       case javax.servlet.http.HttpServletResponse.SC_OK: 
        responseWriter.print("success:" + responseBuffer.toString()); 
        break; 
       default: 
        responseWriter.print("error:" + responseBuffer.toString()); 
       } 
     } catch (IOException e) { } 
     asyncCtx.complete();   
     return responseBuffer.toString(); 
    } 

    @Override 
    protected void onCharReceived(CharBuffer buffer, IOControl ioc) throws IOException { 
     while (buffer.hasRemaining()) 
      responseBuffer.append(buffer.get()); 
    } 

    @Override 
    protected void onResponseReceived(HttpResponse response) throws HttpException, IOException {   
     responseCode = response.getStatusLine().getStatusCode(); 
    } 

} 

ProxyServiceのサーブレットのweb.xml構成であってもよい

<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     id="WebApp_ID" version="3.0" metadata-complete="true"> 
    <display-name>asyncservlet-demo</display-name> 

    <servlet> 
    <servlet-name>External Service Mock</servlet-name> 
    <servlet-class>ExternalServiceMock</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet> 
    <servlet-name>Proxy Service</servlet-name> 
    <servlet-class>ProxyService</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    <async-supported>true</async-supported> 
    <init-param> 
     <param-name>maxtotalconnections</param-name> 
     <param-value>200</param-value> 
    </init-param> 
    <init-param> 
     <param-name>maxconnectionsperroute</param-name> 
     <param-value>4</param-value> 
    </init-param> 
    <init-param> 
     <param-name>serviceurl</param-name> 
     <param-value>http://127.0.0.1:8080/asyncservlet/externalservicemock</param-value> 
    </init-param> 
    </servlet> 

    <servlet-mapping> 
    <servlet-name>External Service Mock</servlet-name> 
    <url-pattern>/externalservicemock</url-pattern> 
    </servlet-mapping> 

    <servlet-mapping> 
    <servlet-name>Proxy Service</servlet-name> 
    <url-pattern>/proxyservice</url-pattern> 
    </servlet-mapping> 

</web-app> 

秒の遅れを持つトークン生成のためのモックサーブレットのようなものがあります。

public class ExternalServiceMock extends HttpServlet{ 

    public static final int TIMEOUT_SECONDS = 13; 
    public static final long K = 1000l; 

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 
     Random rnd = new Random(); 
     try { 
      Thread.sleep(rnd.nextInt(TIMEOUT_SECONDS) * K); 
     } catch (InterruptedException e) { } 
     final byte[] token = String.format("%10d", Math.abs(rnd.nextLong())).getBytes(ISO_8859_1); 
     response.setContentType("text/plain"); 
     response.setCharacterEncoding(ISO_8859_1.name()); 
     response.setContentLength(token.length); 
     response.getOutputStream().write(token); 
    } 

} 

することができますfully working example at GitHubを取得します。

1

この問題は本質的に、非常に多くの「反応性の」ライブラリとツールキットが存在する理由です。

tomcatコネクタを調整または交換することで解決できる問題はありません。
基本的にすべてのブロッキングIOコールを削除する必要があります。ブロッキングIOコールを非ブロッキングIOに置き換えるには、アプリケーションの大部分を書き直す必要があります。
HTTPサーバーが非ブロッキングである必要があります。サーブレット3.1などの非ブロッキングAPIを使用する必要があり、サードパーティAPIへの呼び出しはブロックされていない必要があります。
Vert.xやRxJavaのようなライブラリは、このすべてを支援するためのツールを提供します。

それ以外の唯一の方法は、スレッドプールのサイズを増やすことです。オペレーティングシステムは、CPUをスケジューリングして、非アクティブなスレッドでパフォーマンスが低下することはないようにしていますが、反応的アプローチに比べてオーバーヘッドが増えます。

あなたのアプリケーションについて詳しく知ることなく、具体的なアプローチについてのアドバイスを提供することは困難です。

+0

さて、私は、Tomcatがどのような状況でスレッドを再利用し始めたのか、どうやってそのような状況を生み出すことができるのかという疑問が残っていると思います。 RxJavaはまだ試していませんが、サンプルコードを実行すると更新されます。 –

+0

Tomcatは、スレッドの制御が返されたときにスレッドを再利用します。その場合、スレッドはサードパーティAPIへのネットワークコールのどこかでブロックされます。もちろん、その呼び出しの結果がクライアントへの応答を生成するのを待つ必要があります。反応的なアプローチは応答を待つのではなく、応答を生成するコールバック関数を設定します。この方法で、スレッドの制御をすぐにTomcatに戻すことができます。 – Magnus

0

非同期サーブレットリクエストまたはリアクティブライブラリ(他の回答に記載されているように)を使用すると、アーキテクチャの大幅な変更が必要になりますが、

トークンの使用をトークンの更新と区別することもできます。ここで

は、ナイーブな実装である:

public class TokenHolder { 
    public static volatile Token token = null; 
    private static Timer timer = new Timer(true); 
    static { 
     // set the token 1st time 
     TokenHolder.token = getNewToken(); 

     // schedule updates periodically 
     timer.schedule(new TimerTask(){ 
      public void run() { 
       TokenHolder.token = getNewToken(); 
      } 
     }, 10000, 10000); 
    } 
} 

今、あなたの要求は、単にサービスにアクセスするためにTokenHolder.tokenを使用することができます。

実際のアプリケーションでは、おそらくより高度なスケジューリングツールを使用します。

関連する問題