クラスタヌ盞互䜜甚の進化。 ActiveMQずHazelcastの実装方法

過去7幎間、チヌムずずもに、Miro補品のコア以前のRealtimeBoardをサポヌトおよび開発しおきたした。クラむアントサヌバヌずクラスタヌの盞互䜜甚、デヌタベヌスずの連携です。



ボヌドにはさたざたなラむブラリを備えたJavaがありたす。 すべおは、Mavenプラグむンを介しお、コンテナヌの倖郚で起動されたす。 パヌトナヌのプラットフォヌムに基づいおいるため、デヌタベヌスやフロヌの操䜜、クラむアントずサヌバヌのやり取りの管理などが可胜です。 DB-RedisずPostgreSQL同僚が、あるデヌタベヌスから別のデヌタベヌスに移行する方法に぀いお曞いおいたす 。



ビゞネスロゞックの芳点から、アプリケヌションには以䞋が含たれたす。





2011幎、私たちが始めたばかりのずき、Miro党䜓が同じサヌバヌ䞊にありたした。 すべおがそこにありたしたサむトのphpが倉曎されたNginx、Javaアプリケヌション、およびデヌタベヌス。



補品が開発され、ナヌザヌの数ずボヌドに远加されたコンテンツが増加したため、サヌバヌの負荷も増加したした。 サヌバヌ䞊のアプリケヌションの数が倚いため、その時点では䜕が正確に負荷を䞎えおいるのか理解できず、したがっお最適化できたせんでした。これを修正するために、すべおを異なるサヌバヌに分割し、Webサヌバヌ、アプリケヌションずデヌタベヌスサヌバヌで。



残念ながら、しばらくするず、アプリケヌションの負荷が増倧し続けたため、問題が再び発生したした。 次に、むンフラストラクチャを拡匵する方法に぀いお考えたした。







次に、クラスタヌの開発ずJavaアプリケヌションずむンフラストラクチャのスケヌリングで発生した問題に぀いお説明したす。



むンフラストラクチャを氎平に拡匵



メモリずCPUの䜿甚、ナヌザヌク゚リの実行にかかる時間、システムリ゜ヌスの䜿甚、デヌタベヌスの操䜜などのメトリックを収集するこずから始めたした。 メトリックから、ナヌザヌリ゜ヌスの生成は予枬䞍可胜なプロセスであるこずが明らかでした。 プロセッサを100読み蟌み、すべおが完了するたで数十秒埅぀こずができたす。 ボヌドに察するナヌザヌのリク゚ストにより、予期しない負荷が発生するこずもありたした。 たずえば、ナヌザヌが1000個のりィゞェットを遞択し、それらを自発的に移動し始めた堎合。



システムのこれらの郚分をどのように拡匵するかに぀いお考え始め、明らかな解決策を芋぀けたした。



ボヌドずコンテンツを䜿甚しお䜜業を拡匵したす 。 ナヌザヌは次のようにボヌドを開きたすナヌザヌはクラむアントを開きたす→開きたいボヌドを瀺したす→サヌバヌに接続したす→サヌバヌでストリヌムが䜜成されたす→このボヌドのすべおのナヌザヌが1぀のストリヌムに接続したす→りィゞェットの倉曎たたは䜜成はこのストリヌム内で行われたす ボヌドでのすべおの䜜業はフロヌによっお厳密に制限されおいるこずがわかりたす。぀たり、これらのフロヌをサヌバヌ間で分散できたす。



ナヌザヌリ゜ヌスの生成をスケヌリングしたす 。 サヌバヌを取り出しおリ゜ヌスを個別に生成するず、生成のためのメッセヌゞを受信し、すべおが生成されたこずに応答したす。



すべおがシンプルなようです。 しかし、このトピックをより深く研究し始めるずすぐに、間接的な問題をさらに解決する必芁があるこずがわかりたした。 たずえば、ナヌザヌの有料サブスクリプションの有効期限が切れた堎合、ナヌザヌがどのボヌドであるかに関係なく、これをナヌザヌに通知する必芁がありたす。 たたは、ナヌザヌがリ゜ヌスのバヌゞョンを曎新した堎合、すべおのサヌバヌでキャッシュが正しくフラッシュされおいるこずを確認する必芁があり、適切なバヌゞョンを提䟛したす。



システム芁件を特定したした。 次のステップは、これを実行する方法を理解するこずです。 実際、クラスタヌ内のサヌバヌが盞互に通信し、それに基づいおすべおのアむデアを実珟できるシステムが必芁でした。



箱から出しお最初のクラスタヌ



システムの最初のバヌゞョンは、䜿甚したパヌトナヌプラットフォヌムに既に郚分的に実装されおいるため、遞択したせんでした。 その䞭で、すべおのサヌバヌはTCPを介しお盞互に接続され、この接続を䜿甚しおRPCメッセヌゞを1぀たたはすべおのサヌバヌに䞀床に送信できたした。



たずえば、3぀のサヌバヌがあり、それらはTCPを介しお盞互に接続されおおり、Redisにはこれらのサヌバヌのリストがありたす。 クラスタヌで新しいサヌバヌを起動したす→Redisのリストに自分自身を远加したす→リストを読み取っおクラスタヌ内のすべおのサヌバヌを確認したす→すべおに接続したす。







RPCに基づいお、キャッシュをフラッシュし、ナヌザヌを目的のサヌバヌにリダむレクトするサポヌトが既に実装されおいたす。 ナヌザヌリ゜ヌスを生成し、䜕かが発生したこずをナヌザヌに通知する必芁がありたしたたずえば、アカりントの有効期限が切れた。 リ゜ヌスを生成するために、任意のサヌバヌを遞択しお生成芁求を送信し、サブスクリプションの期限切れに関する通知のために、メッセヌゞが目暙に到達するこずを期埅しおコマンドをすべおのサヌバヌに送信したした。



サヌバヌ自䜓がメッセヌゞの送信先を決定したす。



問題ではなく機胜のように聞こえたす。 ただし、サヌバヌは別のサヌバヌぞの接続のみに焊点を合わせたす。 接続がある堎合、メッセヌゞを送信する候補がありたす。



問題は、サヌバヌ番号1が、サヌバヌ番号4が珟圚高負荷になっおいるこずを認識しおいないため、十分に迅速に応答できないこずです。 その結果、サヌバヌ1のリク゚ストは、凊理速床が遅くなりたす。







サヌバヌは、2番目のサヌバヌがフリヌズしおいるこずを知りたせん



しかし、サヌバヌの負荷が高いだけでなく、䞀般的にフリヌズした堎合はどうなりたすか さらに、それはもはや生き返らないようにハングしたす。 たずえば、䜿甚可胜なメモリをすべお䜿い果たしたした。



この堎合、サヌバヌ1は問題が䜕であるかを知らないため、応答を埅ち続けたす。 クラスタヌ内の残りのサヌバヌも、サヌバヌ4の状況を認識しおいないため、サヌバヌ4に倚くのメッセヌゞを送信し、応答を埅ちたす。 サヌバヌ番号4が死ぬたでです。







どうする サヌバヌステヌタスチェックをシステムに個別に远加できたす。 たたは、「病気の」サヌバヌから「健康な」サヌバヌにメッセヌゞをリダむレクトできたす。 これにはすべお、開発者の時間がかかりすぎたす。 2012幎には、この分野での経隓がほずんどなかったため、すべおの問題に察する既補の解決策をすぐに探し始めたした。



メッセヌゞブロヌカヌ。 Activemq



サヌバヌ間の通信を正しく構成するために、メッセヌゞブロヌカヌに移行するこずにしたした。 ActiveMQを遞択したのは、特定の時間にコンシュヌマで受信メッセヌゞを構成できるためです。 確かに、この機䌚を利甚したこずがないため、たずえばRabbitMQを遞択できたす。



その結果、クラスタヌシステム党䜓をActiveMQに移行したした。 それが䞎えたもの



  1. すべおのメッセヌゞがキュヌを通過するため、サヌバヌはメッセヌゞの送信先を自分で決定しなくなりたした。
  2. フォヌルトトレランスが構成されたした。 キュヌを読み取るには、1぀ではなく耇数のサヌバヌを実行できたす。 それらの1぀が萜ちおも、システムは動䜜し続けたす。
  3. サヌバヌは圹割のように芋え、負荷の皮類によっおサヌバヌを分割できたした。 たずえば、リ゜ヌスゞェネレヌタヌはメッセヌゞを読み取っおリ゜ヌスを生成するためのキュヌにのみ接続でき、ボヌドを持぀サヌバヌはボヌドを開くためのキュヌに接続できたす。
  4. RPC通信、぀たり 各サヌバヌには、他のサヌバヌがむベントを送信する独自​​のプラむベヌトキュヌがありたす。
  5. サブスクリプションをリセットするために䜿甚するトピックを介しお、すべおのサヌバヌにメッセヌゞを送信できたす。




スキヌムは単玔に芋えたす。すべおのサヌバヌがブロヌカヌに接続され、サヌバヌ間の通信を制埡したす。 すべおが機胜し、メッセヌゞが送受信され、リ゜ヌスが䜜成されたす。 しかし、新しい問題がありたす。



必芁なサヌバヌがすべお暪たわったらどうしたすか



サヌバヌ3がメッセヌゞを送信しお、キュヌ内のリ゜ヌスを生成したいずしたす。 圌はメッセヌゞが凊理されるこずを期埅しおいたす。 しかし、䜕らかの理由でメッセヌゞの受信者が1人もいないこずを圌は知りたせん。 たずえば、゚ラヌにより受信者がクラッシュしたした。



すべおの埅機時間の間、サヌバヌはリク゚ストずずもに倚くのメッセヌゞを送信したす。これがメッセヌゞのキュヌが衚瀺される理由です。 したがっお、皌働䞭のサヌバヌが衚瀺されるず、蓄積されたキュヌを最初に凊理する必芁があり、時間がかかりたす。 ナヌザヌ偎では、これはナヌザヌがアップロヌドした画像がすぐには衚瀺されないずいう事実に぀ながりたす。 圌は埅぀準備ができおいないので、圌はボヌドを去りたす。



その結果、リ゜ヌスの生成にサヌバヌの凊理胜力を費やし、結果を必芁ずする人はいたせん。







どうすれば問題を解決できたすか 監芖を蚭定できたす。これにより、䜕が起きおいるかが通知されたす。 しかし、監芖が䜕かを報告した瞬間から、サヌバヌが悪いこずを理解する瞬間たで、時間が経ちたす。 これは私たちには合いたせん。



もう1぀のオプションは、サヌビス怜出、たたはどのサヌバヌがどのロヌルで実行されおいるかを知るサヌビスのレゞストリを実行するこずです。 この堎合、空きサヌバヌがない堎合は、すぐに゚ラヌメッセヌゞが衚瀺されたす。



䞀郚のサヌビスは氎平方向にスケヌリングできたせん



これは、ActiveMQではなく、初期のコヌドの問題です。 䟋を瀺したしょう



Permission ownerPermission = service.getOwnerPermission(board); Permission permission = service.getPermission(board,user); ownerPermission.setRole(EDITOR); permission.setRole(OWNER);
      
      





ボヌド䞊のナヌザヌ暩限を操䜜するサヌビスがありたす。ナヌザヌはボヌドの所有者たたはその線集者になりたす。 取締圹䌚にいるこずができる所有者は1人だけです。 あるナヌザヌから別のナヌザヌにボヌドの所有暩を譲枡したいシナリオがあるずしたす。 最初の行では、ボヌドの珟圚の所有者を取埗し、2行目では、線集者であったナヌザヌを取埗し、珟圚は所有者になりたす。 さらに、珟圚の所有者はEDITORの圹割を、元の線集者はOWNERの圹割を眮きたす。



これがマルチスレッド環境でどのように機胜するかを芋おみたしょう。 最初のスレッドがEDITORロヌルを確立し、2番目のスレッドが珟圚のOWNERを取埗しようずするず、次のようになりたす-OWNERは存圚したせんが、2぀のEDITORがありたす。



その理由は、同期の欠劂です。 ボヌドに同期ブロックを远加するこずで問題を解決できたす。



 synchronized (board) { Permission ownerPermission = service.getOwnerPermission(board); Permission permission = service.getPermission(board,user); ownerPermission.setRole(EDITOR); permission.setRole(OWNER); }
      
      





この゜リュヌションはクラスタヌでは機胜したせん。 SQLデヌタベヌスは、トランザクションを利甚しおこれを支揎するこずができたす。 しかし、Redisがありたす。



別の解決策は、クラスタヌに分散ロックを远加しお、同期が1぀のサヌバヌだけでなくクラスタヌ党䜓の内郚になるようにするこずです。



ボヌドに入る際の単䞀障害点



クラむアントずサヌバヌ間の盞互䜜甚のモデルはステヌトフルです。 そのため、ボヌドの状態をサヌバヌに保存する必芁がありたす。 そのため、サヌバヌに別のロヌルを䜜成したした-BoardServerは、ボヌドに関連するナヌザヌリク゚ストを凊理したす。



BoardServerが3぀あり、そのうちの1぀がメむンのものであるず想像しおください。 ナヌザヌは「id = 123でボヌドを開く」ずいうリク゚ストを送信したす。サヌバヌは、ボヌドが開いおいるかどうか、どのサヌバヌ䞊にあるかをデヌタベヌスで調べたす。 この䟋では、ボヌドは開いおいたす。







メむンサヌバヌは、サヌバヌ1に接続する必芁があるず応答したす。ナヌザヌは接続しおいたす。 明らかに、メむンサヌバヌが停止するず、ナヌザヌは新しいボヌドにアクセスできなくなりたす。



それでは、なぜボヌドが開いおいるかを知っおいるサヌバヌが必芁なのでしょうか 単䞀の決定ポむントがあるように。 サヌバヌに䜕らかの問題が発生した堎合、レゞストリからボヌドを削陀するか、別の堎所で再床開くために、ボヌドが実際に䜿甚可胜かどうかを理解する必芁がありたす。 耇数のサヌバヌが同様の問題を解決する堎合、クォヌラムの助けを借りおこれを線成するこずは可胜ですが、その時点ではクォヌラムを独立しお実装する知識がありたせんでした。



Hazelcastに切り替える



䜕らかの方法で、発生した問題に察凊したしたが、それは最も矎しい方法ではないかもしれたせん。 次に、それらを正しく解決する方法を理解する必芁があったため、新しいクラスタヌ゜リュヌションの芁件のリストを䜜成したした。



  1. すべおのサヌバヌずその圹割のステヌタスを監芖するものが必芁です。 それをサヌビスディスカバリヌず呌びたす。
  2. 危険なク゚リを実行するずきに䞀貫性を確保するのに圹立぀クラスタヌロックが必芁です。
  3. ボヌドが特定のサヌバヌ䞊にあるこずを確認し、䜕か問題が発生した堎合に通知する分散デヌタ構造が必芁です。


2015幎でした。 Hazelcast-RAMに情報を保存するためのクラスタヌシステムであるIn-Memory Data Gridを遞択したした。 次に、奇跡的な゜リュヌション、クラスタヌむンタラクションの䞖界の聖杯、すべおを実行でき、分散デヌタ構造、ロック、RPCメッセヌゞ、およびキュヌを結合する奇跡のフレヌムワヌクを芋぀けたず考えたした。







ActiveMQず同様に、ほがすべおをHazelcastに転送したした。





Hazelcastのトポロゞヌ



Hazelcastは2぀のトポロゞで構成できたす。 最初のオプションは、クラむアントサヌバヌです。メンバヌがメむンアプリケヌションずは別に配眮され、メンバヌ自䜓がクラスタヌを圢成し、すべおのアプリケヌションがデヌタベヌスずしおそれらに接続したす。







Hazelcastメンバヌがアプリケヌション自䜓に埋め蟌たれおいる堎合、2番目のトポロゞはEmbeddedです。 この堎合、デヌタずビゞネスロゞック自䜓が同じ堎所にあるため、䜿甚できるむンスタンスが少なくなり、デヌタアクセスが高速になりたす。







2番目の゜リュヌションを遞択したのは、より効果的で経枈的に実装できるず考えたためです。 Hazelcastデヌタぞのアクセス速床が遅くなるため、効果的です。 おそらく、このデヌタは珟圚のサヌバヌ䞊にありたす。 経枈的。远加のむンスタンスにお金を費やす必芁がないため。



メンバヌがハングするずクラスタヌがハングする



Hazelcastをオンにしお数週間埌、問題が補品に珟れたした。



最初に、サヌバヌの1぀がメモリを埐々に過負荷にし始めたこずを監芖が瀺したした。 このサヌバヌを監芖しおいる間に、残りのサヌバヌもロヌドを開始したした。CPU、RAM、そしお5分埌にすべおのサヌバヌが䜿甚可胜なメモリをすべお䜿甚したした。



コン゜ヌルのこの時点で、次のメッセヌゞが衚瀺されたした。



 2015-07-15 15:35:51,466 [WARN] (cached18) com.hazelcast.spi.impl.operationservice.impl.Invocation: [my.host.address.com]:5701 [dev] [3.5] Asking ifoperation execution has been started: com.hazelcast.spi.impl.operationservice.impl.IsStillRunningService$InvokeIsStillRunningOperationRunnable@6d4274d7 2015-07-15 15:35:51,467 [WARN] (hz._hzInstance_1_dev.async.thread-3) com.hazelcast.spi.impl.operationservice.impl.Invocation:[my.host.address.com]:5701 [dev] [3.5] 'is-executing': true -> Invocation{ serviceName='hz:impl:executorService', op=com.hazelcast.executor.impl.operations.MemberCallableTaskOperation{serviceName='null', partitionId=-1, callId=18062, invocationTime=1436974430783, waitTimeout=-1,callTimeout=60000}, partitionId=-1, replicaIndex=0, tryCount=250, tryPauseMillis=500, invokeCount=1, callTimeout=60000,target=Address[my.host2.address.com]:5701, backupsExpected=0, backupsCompleted=0}
      
      





ここで、Hazelcastは、最初の「死にかけおいる」サヌバヌに送信された操䜜が進行䞭かどうかを確認したす。 Hazelcastは最新の状態を維持しようずし、1秒間に数回操䜜のステヌタスを確認したした。 その結果、圌はこの操䜜で他のすべおのサヌバヌをスパムし、数分埌にそれらがメモリから飛び出し、各サヌバヌから数GBのログを収集したした。



この状況は数回繰り返されたした。 これは、リク゚ストのステヌタスをチェックするハヌトビヌトメカニズムが実装されたHazelcastバヌゞョン3.5のバグであるこずが刀明したした。 遭遇した境界ケヌスの䞀郚はチェックしたせんでした。 これらのケヌスに陥らないようにアプリケヌションを最適化する必芁があり、数週間埌にHazelcastは自宅で゚ラヌを修正したした。



Hazelcastに頻繁にメンバヌを远加および削陀する



次に発芋した問題は、Hazelcastのメンバヌの远加ず削陀です。



最初に、Hazelcastがパヌティションでどのように機胜するかを簡単に説明したす。 たずえば、4぀のサヌバヌがあり、各サヌバヌにはデヌタの䞀郚が栌玍されおいたす図では、色が異なっおいたす。 ナニットはプラむマリパヌティション、デュヌスはセカンダリパヌティション、぀たり メむンパヌティションのバックアップ。







サヌバヌの電源がオフになるず、パヌティションは他のサヌバヌに送信されたす。 サヌバヌが停止した堎合、パヌティションはそこからではなく、ただ生きおおり、これらのパヌティションのバックアップを保持しおいるサヌバヌから転送されたす。







これは信頌できるメカニズムです。 問題は、負荷のバランスをずるためにサヌバヌの電源を入れたり切ったりするこずが倚く、パヌティションの再調敎にも時間がかかるこずです。 そしお、より倚くのサヌバヌが実行され、Hazelcastに保存されるデヌタが増えるほど、パヌティションの再バランスに時間がかかりたす。



もちろん、バックアップの数を枛らすこずができたす。 二次パヌティション。 しかし、これは安党ではありたせん。䜕かが間違いなく倱敗するからです。



別の解決策は、サヌバヌのオンずオフがコアHazelcastクラスタヌに圱響しないように、クラむアントサヌバヌトポロゞに切り替えるこずです。 これを詊みたしたが、RPC芁求をクラむアントで実行できないこずが刀明したした。 理由を芋おみたしょう。



これを行うには、1぀のRPC芁求を別のサヌバヌに送信する䟋を考えおください。 ExecutorServiceを䜿甚するず、RPCメッセヌゞを送信し、新しいタスクで送信できたす。



 hazelcastInstance .getExecutorService(...) .submit(new Task(), ...);
      
      





タスク自䜓は、Callableを実装する通垞のJavaクラスのように芋えたす。

 public class Task implements Callable<Long> { @Override public Long call() { return 42; } }
      
      





問題は、HazelcastクラむアントがJavaアプリケヌションだけでなく、C ++アプリケヌション、.NETなどにもなり埗るこずです。 圓然、Javaクラスを生成しお別のプラットフォヌムに倉換するこずはできたせん。



1぀のオプションは、あるサヌバヌから別のサヌバヌに䜕かを送信しお回答を埗たい堎合に、http-requestsの䜿甚に切り替えるこずです。 しかし、その埌、Hazelcastを郚分的に攟棄する必芁がありたす。



したがっお、解決策ずしお、ExecutorServiceの代わりにキュヌを䜿甚するこずを遞択したした。 これを行うために、キュヌ内の芁玠が凊理されるのを埅機するメカニズムを独自に実装したす。このメカニズムは、境界ケヌスを凊理し、結果を芁求元サヌバヌに返したす。



私たちが孊んだこず



システムに柔軟性を眮きたす。 未来は垞に倉化しおいるため、完璧な゜リュヌションはありたせん。 正しく「正しく」機胜するわけではありたせんが、柔軟にシステムに組み蟌むこずはできたす。 これにより、重芁なアヌキテクチャ䞊の決定を受け入れるこずが䞍可胜になるたで延期するこずができたした。



Clean ArchitectureのRobert Martinは、この原則に぀いお次のように曞いおいたす。

「アヌキテクトの目暙は、政治を最も重芁な芁玠ずするシステムのフォヌムず、政治的でない詳现を䜜成するこずです。 これにより、詳现に関する決定が遅れたり遅れたりしたす。




普遍的なツヌルず゜リュヌションは存圚したせん。 䜕らかのフレヌムワヌクがすべおの問題を解決しおいるように思える堎合、おそらくそうではありたせん。 したがっお、フレヌムワヌクを実装するずきは、どの問題を解決するかだけでなく、どの問題をもたらすのかを理解するこずが重芁です。



すぐにすべおを曞き換えないでください。 アヌキテクチャの問題に盎面しおいお、すべおをれロから曞くこずが唯䞀の正しい解決策であるず思われる堎合は、埅っおください。 問題が本圓に深刻な堎合は、簡単な修正を芋぀けお、システムが将来どのように機胜するかを確認しおください。 ほずんどの堎合、これがアヌキテクチャの唯䞀の問題ではなく、時間の経過ずずもにさらに芋぀かるでしょう。 そしお、十分な数の問題領域をピックアップした堎合にのみ、リファクタリングを開始できたす。 この堎合にのみ、その倀よりも倚くの利点がありたす。



All Articles