最近、私と私のプログラマーチームは、アプリケーションのリクエスト機構に些細な障害を発見しました。いくつかのチェックの後、その根本的な原因を見つけました。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
障害と診断
アプリケーションの処理要求のためのスレッドプールが一杯になっていたため、追加の要求を処理できませんでした。スレッドをダンプした後、ログ書き込み領域でスレッドスタックがブロックされているのがわかりました。スレッドスタックがログ書き込み領域でブロックされるのは新しい問題ではありませんが、以前はこれは他の何かの結果だと考えていました。したがって、過去の経験に基づいて、問題はログ書き込み領域にはないと仮定しました。
多くのトラブルシューティングを使用し、問題の根本的な原因を見つけられずに挫折した後、私たちはソースコードを見て、何が問題を引き起こしているのかについていくつかの手がかりを得ようとしました。コードにログロックがあることを発見しました。このログロックから、スレッドスタックからArrayBlockingQueue.putにブロックがあることがわかりました。さらに調べてみると、それは1024長のBlockingQueueであることがわかりました。これが何を意味するかというと、このキューに1024個のオブジェクトがあると、後続のputリクエストがブロックされてしまうということです。
このコードを書いたプログラマは、BlockingQueueが一杯になったら、そしてそのデータが処理されるべきだと考えていたようです。問題のコードは以下の通りです。
if (blockingQueue.retainCapacity() < 1) { //todo } blockingQueue.put
ここで、問題には大きく分けて2つの部分があります。
-
完全な判定は、「else」ではなく「put」に直接行く。
-
キューが一杯になった後も処理ロジックは //todo...となる。
上記のコードは、この特定のプログラマがBlockingQueueインターフェイスに精通していないことを示しています。この結果を得るためには、最初にこの判断を行う必要はありません。より良い方法は、blockingQueue.offerを使用することでしょう。これが'false'として返ってきたら、関連する例外処理を実装することができます。
BlockingQueuesの種類
BlockingQueue は、プロダクション/コンシューマー・モードで一般的に使用されるデータ構造です。最も一般的に使用される型は、ArrayBlockingQueue、LinkedBlockingQueue、およびSynchronousQueueです。
ArrayBlockingQueueとLinkedBlockingQueueの主な違いは、キューに配置されるオブジェクトにあります。一方は配列に使用され、他方はリンクテーブルに使用されます。その他の違いについては、コードノートにも記載されています。
リンクされたキューは、典型的にはアレイベースのキューよりも高いスループットを持ちますが、ほとんどの同時実行アプリケーションでは予測可能なパフォーマンスは低くなります。
SynchronousQueue は、特殊な BlockingQueue です。これは、offer中に使用されます。現在他のスレッドがテイクやポーリングを実行していない場合、offerは失敗します。テイク中、他のスレッドが同時にofferを実行していない場合も失敗します。この特別なモードは、高い応答要件を持つキューや非固定スレッドプールからのスレッドに適しています。
問題の概要
オンラインビジネスのシナリオでは、同時実行と外部アクセスがブロックされているすべての領域にタイムアウトのメカニズムが必要です。タイムアウトメカニズムの欠如がオンラインビジネスの重大な障害の根本原因になっているのを何度見てきたかわかりません。
オンラインビジネスでは、リクエストを素早く処理し、完了させることを重視しています。そのため、オンラインビジネスシステムの設計やコードプログラミングでは、高速に失敗することが最も重要な原則となります。この原則によると、上記のコードの中で最も明らかな間違いは、タイムアウトの仕組みを使ったofferではなく、putを使っていることです。あるいは、重要でないシナリオでは、offerを直接使用すべきであり、'false'の結果は、直接例外を投げたり記録したりする原因になると言うこともできます。
BlockingQueueシナリオに関しては、タイムアウト機構に加えて、キューの長さを制限しなければなりません。それ以外の場合は、Integer. MAX_VALUEがデフォルトで使用されます。この場合、コードにバグがあるとメモリが停止します。
BlockingQueueについて語る際には、BlockingQueueの中で最も使われている領域、つまりスレッドプールについても触れておきましょう。
ThreadPoolExecutorにはBlockingQueueパラメータがあります。ここでArrayBlockingQueueまたはLinkedBlockingQueueが使用され、スレッドプールのcoreSizeとpoolSizeが異なる場合、coreSizeのスレッドが占有された後、スレッドプールはまずBlockingQueueにoffer
を送信します。成功した場合、プロセスは終了します。
しかし、 このシナリオはオンラインビジネスのニーズに常に適合するわけではありません。
オンラインビジネスはテンポの速い環境で運営されており、キューにリクエストを配置するのとは対照的に高速な処理を必要としています。要求がすべてでキューに積み重ねられていない場合、実際には、それはオンラインビジネスのために最善です。オンラインビジネスの構造のこの種のものは、簡単に崩れやすく、システムの処理能力を超えているリクエストを直接拒否して、代わりにエラーメッセージを出すことは、比較的シンプルでありながらも良い方法です。しかし、それは制限されたメカニズムです。
高コンカレントで分散型のコードを書くときには、システム設計だけではなく、コードの細かい部分にも注意を払うことが重要であることを忘れないでください。そうすることで、上記のような問題を回避することができます。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ