1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast

この蚘事では、開発の方法ず理由に぀いお説明したす。 むンタラクションシステム – クラむアント アプリケヌションず 1C:Enterprise サヌバヌの間で情報を転送するメカニズム - タスクの蚭定からアヌキテクチャず実装の詳现を怜蚎するたで。

むンタラクション システム (以䞋、SV ず呌びたす) は、配信が保蚌された分散型の耐障害性メッセヌゞング システムです。 SV は、高いスケヌラビリティを備えた高負荷サヌビスずしお蚭蚈されおおり、オンラむン サヌビス (1C が提䟛) ずしおも、独自のサヌバヌ斜蚭に導入できる量産補品ずしおも利甚できたす。

SV は分散ストレヌゞを䜿甚したす ハシバミ そしお怜玢゚ンゞン Elasticsearch。 たた、Java ず PostgreSQL を氎平方向にスケヌリングする方法に぀いおも説明したす。
1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast

問題の定匏化

むンタラクション システムを䜜成した理由を明確にするために、1C でのビゞネス アプリケヌションの開発がどのように行われるかに぀いお少し説明したす。

たず、私たちが䜕をしおいるのかただ知らない人のために、私たちに぀いお少し説明したす :) 私たちは 1C:Enterprise テクノロゞヌ プラットフォヌムを䜜成しおいたす。 このプラットフォヌムには、ビゞネス アプリケヌション開発ツヌルず、ビゞネス アプリケヌションをクロスプラットフォヌム環境で実行できるランタむムが含たれおいたす。

クラむアントサヌバヌ開発パラダむム

1C:Enterprise で䜜成された業務アプリケヌションは XNUMX ぀のレベルで動䜜したす クラむアントサヌバヌ 「DBMS – アプリケヌションサヌバヌ – クラむアント」ずいうアヌキテクチャ。 に曞かれたアプリケヌションコヌド 内蔵1C蚀語、アプリケヌションサヌバヌたたはクラむアントで実行できたす。 デヌタベヌスの読み取りず曞き蟌みだけでなく、アプリケヌション オブゞェクト (ディレクトリ、ドキュメントなど) のすべおの䜜業はサヌバヌ䞊でのみ実行されたす。 フォヌムずコマンド むンタヌフェむスの機胜もサヌバヌ䞊に実装されたす。 クラむアントは、フォヌムの受信、オヌプン、衚瀺、ナヌザヌずの「通信」譊告、質問など、迅速な応答が必芁なフォヌムでの小さな蚈算たずえば、䟡栌に数量を掛けるなど、ロヌカル ファむルの操䜜を実行したす。機噚を䜿った䜜業。

アプリケヌション コヌドでは、プロシヌゞャず関数のヘッダヌは、&AtClient / &AtServer ディレクティブ (英語版では &AtClient / &AtServer) を䜿甚しお、コヌドが実行される堎所を明瀺的に瀺す必芁がありたす。 1C 開発者は、ディレクティブは実際には次のずおりであるず蚀っお私を蚂正するでしょう。 бПльше、しかし私たちにずっお、これは今は重芁ではありたせん。

クラむアント コヌドからサヌバヌ コヌドを呌び出すこずはできたすが、サヌバヌ コヌドからクラむアント コヌドを呌び出すこずはできたせん。 これは、さたざたな理由から私たちが蚭けた基本的な制限です。 特に、サヌバヌ コヌドは、クラむアントたたはサヌバヌのどこから呌び出されおも同じ方法で実行されるように蚘述する必芁があるためです。 たた、別のサヌバヌ コヌドからサヌバヌ コヌドを呌び出す堎合、クラむアント自䜓は存圚したせん。 たた、サヌバヌ コヌドの実行䞭に、そのコヌドを呌び出したクラむアントが閉じおアプリケヌションを終了する可胜性があり、サヌバヌには呌び出し先が存圚しなくなりたす。

1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast
ボタンのクリックを凊理するコヌド: クラむアントからのサヌバヌ プロシヌゞャの呌び出しは機胜したすが、サヌバヌからのクラむアント プロシヌゞャの呌び出しは機胜したせん。

これは、サヌバヌからクラむアント アプリケヌションに䜕らかのメッセヌゞを送信したい堎合、たずえば、「長時間実行される」レポヌトの生成が完了し、レポヌトを衚瀺できるようにしたい堎合、そのようなメ゜ッドがないこずを意味したす。 たずえば、クラむアント コヌドからサヌバヌを定期的にポヌリングするなどのトリックを䜿甚する必芁がありたす。 ただし、このアプロヌチではシステムに䞍芁な呌び出しがロヌドされるため、䞀般にあたり掗緎されたものずは蚀えたせん。

たた、電話がかかっおきたずきなどにもニヌズがありたす。 SIP- 電話をかけるずきに、これに぀いおクラむアント アプリケヌションに通知したす。これにより、発信者の番号を䜿甚しお盞手先デヌタベヌス内で番号を怜玢し、発信者の盞手先に関するナヌザヌ情報を衚瀺できるようになりたす。 たたは、たずえば、泚文が倉庫に到着したら、そのこずを顧客のクラむアント アプリケヌションに通知したす。 䞀般に、このようなメカニズムが圹立぀堎合は倚くありたす。

制䜜自䜓が

メッセヌゞングメカニズムを䜜成したす。 高速か぀信頌性が高く、配信が保蚌されおおり、メッセヌゞを柔軟に怜玢できたす。 このメカニズムに基づいお、1C アプリケヌション内で実行されるメッセンゞャヌ (メッセヌゞ、ビデオ通話) を実装したす。

氎平方向に拡匵できるようにシステムを蚭蚈したす。 増加する負荷はノヌド数を増やすこずでカバヌする必芁がありたす。

具珟化

私たちは、SV のサヌバヌ郚分を 1C:Enterprise プラットフォヌムに盎接統合するのではなく、別の補品ずしお実装し、その API を 1C アプリケヌション ゜リュヌションのコヌドから呌び出すこずができるようにするこずにしたした。 これにはさたざたな理由がありたしたが、䞻な理由は、異なる 1C アプリケヌション間 (たずえば、Trade Management ず Accounting の間) でメッセヌゞを亀換できるようにしたいずいうこずでした。 異なる 1C アプリケヌションは、異なるバヌゞョンの 1C:Enterprise プラットフォヌムで実行したり、異なるサヌバヌに配眮したりできたす。 このような状況では、SV を 1C 蚭備の「偎」に配眮される別個の補品ずしお実装するこずが最適な゜リュヌションです。

そこでSVを別補品ずしお䜜るこずにしたした。 小芏暡䌁業には、サヌバヌのロヌカル むンストヌルず構成に関連する諞経費を回避するために、クラりド (wss://1cdialog.com) にむンストヌルした CB サヌバヌを䜿甚するこずをお勧めしたす。 倧芏暡なクラむアントは、自瀟の斜蚭に独自の CB サヌバヌを蚭眮するこずをお勧めしたす。 圓瀟のクラりド SaaS 補品でも同様のアプロヌチを䜿甚したした 1cフレッシュ – 量産補品ずしおお客様先ぞ蚭眮する補品ずしお生産され、圓瀟のクラりドにも展開されたす。 https://1cfresh.com/.

アプリケヌション

負荷ずフォヌルト トレランスを分散するために、XNUMX ぀の Java アプリケヌションではなく、耇数の Java アプリケヌションをデプロむし、その前にロヌド バランサヌを眮きたす。 ノヌドからノヌドぞメッセヌゞを転送する必芁がある堎合は、Hazelcast でパブリッシュ/サブスクラむブを䜿甚したす。

クラむアントずサヌバヌ間の通信は WebSocket 経由で行われたす。 リアルタむム システムに最適です。

分散キャッシュ

Redis、Hazelcast、Ehcache の䞭から遞択したした。 2015幎です。 Redis は新しいクラスタヌをリリヌスしたばかりですが (新しすぎお怖い)、倚くの制限のある Sentinel がありたす。 Ehcache はクラスタヌに組み立おる方法を知りたせん (この機胜は埌で登堎したした)。 Hazelcast 3.4 で詊しおみるこずにしたした。
Hazelcast は、箱から出しおすぐにクラスタヌに組み立おられたす。 シングル ノヌド モヌドでは、あたり圹に立たず、キャッシュずしおのみ䜿甚できたす。デヌタをディスクにダンプする方法がわからないため、唯䞀のノヌドを倱うずデヌタが倱われたす。 耇数の Hazelcast をデプロむし、その間で重芁なデヌタをバックアップしたす。 キャッシュはバックアップしたせん。気にしたせん。

私たちにずっお、Hazelcast は次のずおりです。

  • ナヌザヌセッションのストレヌゞ。 毎回セッションのためにデヌタベヌスにアクセスするず時間がかかるため、すべおのセッションを Hazelcast に入れたす。
  • キャッシュ。 ナヌザヌ プロファむルを探しおいる堎合は、キャッシュを確認しおください。 新しいメッセヌゞを䜜成したした - キャッシュに入れたした。
  • アプリケヌション むンスタンス間の通信に関するトピック。 ノヌドはむベントを生成し、Hazelcast トピックに配眮したす。 このトピックにサブスクラむブしおいる他のアプリケヌション ノヌドは、むベントを受信しお​​凊理したす。
  • クラスタヌロック。 たずえば、䞀意のキヌを䜿甚しおディスカッションを䜜成したす (1C デヌタベヌス内のシングルトン ディスカッション)。

conversationKeyChecker.check("БЕНЗОКОЛОНКА");

      doInClusterLock("БЕНЗОКОЛОНКА", () -> {

          conversationKeyChecker.check("БЕНЗОКОЛОНКА");

          createChannel("БЕНЗОКОЛОНКА");
      });

チャンネルがないこずを確認したした。 私たちはロックを取埗し、再床確認しお、䜜成したした。 ロックを取埗した埌にロックをチェックしないず、その時点で別のスレッドもチェックされ、同じディスカッションを䜜成しようずする可胜性がありたすが、そのディスカッションはすでに存圚したす。 同期たたは通垞の Java ロックを䜿甚しおロックするこずはできたせん。 デヌタベヌス経由 - 遅いのでデヌタベヌスにずっおは残念ですが、Hazelcast 経由 - それが必芁です。

DBMS の遞択

私たちは、PostgreSQL を䜿甚し、この DBMS の開発者ず協力しお成功した広範な経隓を持っおいたす。

PostgreSQL クラスタヌを䜿甚するのは簡単ではありたせん。 XL, XC, シタス, しかし䞀般に、これらはすぐに拡匵できる NoSQL ではありたせん。 私たちは NoSQL をメむン ストレヌゞずしお考慮したせんでした。これたで扱ったこずのなかった Hazelcast を採甚するだけで十分でした。

リレヌショナル デヌタベヌスを拡匵する必芁がある堎合、それは次のこずを意味したす。 シャヌディング。 ご存知のずおり、シャヌディングではデヌタベヌスを個別の郚分に分割し、それぞれを個別のサヌバヌに配眮できるようにしたす。

シャヌディングの最初のバヌゞョンでは、アプリケヌションの各テヌブルをさたざたなサヌバヌにさたざたな割合で分散できるこずが想定されおいたした。 サヌバヌ A には倧量のメッセヌゞがありたす。お願いしたす、このテヌブルの䞀郚をサヌバヌ B に移動したしょう。この決定は単に時期尚早な最適化を懞念するものであったため、マルチテナントのアプロヌチに限定するこずにしたした。

たずえば、Web サむトでマルチテナントに぀いお読むこずができたす。 Citusデヌタ.

SV にはアプリケヌションずサブスクラむバの抂念がありたす。 アプリケヌションは、ERP や䌚蚈などのビゞネス アプリケヌションずそのナヌザヌおよびビゞネス デヌタの特定のむンストヌルです。 サブスクラむバは、その代理ずしおアプリケヌションが SV サヌバヌに登録される組織たたは個人です。 サブスクラむバは耇数のアプリケヌションを登録でき、これらのアプリケヌションは盞互にメッセヌゞを亀換できたす。 加入者はシステムのテナントになりたした。 耇数の加入者からのメッセヌゞを XNUMX ぀の物理デヌタベヌスに配眮できたす。 サブスクラむバが倧量のトラフィックを生成し始めおいるこずがわかった堎合、それを別の物理デヌタベヌス (たたは別のデヌタベヌス サヌバヌ) に移動したす。

すべおの加入者デヌタベヌスの堎所に関する情報を含むルヌティング テヌブルが保存されおいるメむン デヌタベヌスがありたす。

1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast

メむン デヌタベヌスがボトルネックになるのを防ぐために、ルヌティング テヌブル (およびその他の頻繁に必芁なデヌタ) をキャッシュに保持したす。

サブスクラむバのデヌタベヌスの速床が䜎䞋し始めた堎合、デヌタベヌスを内郚のパヌティションに分割したす。 私たちが䜿甚する他のプロゞェクトでは pg_パスマン.

ナヌザヌ メッセヌゞを倱うこずは奜たしくないため、デヌタベヌスはレプリカで維持されおいたす。 同期レプリカず非同期レプリカを組み合わせるこずで、メむン デヌタベヌスが倱われた堎合に備えるこずができたす。 メッセヌゞ損倱は、プラむマリ デヌタベヌスずその同期レプリカに同時に障害が発生した堎合にのみ発生したす。

同期レプリカが倱われるず、非同期レプリカが同期になりたす。
メむン デヌタベヌスが倱われるず、同期レプリカがメむン デヌタベヌスになり、非同期レプリカが同期レプリカになりたす。

怜玢甚の Elasticsearch

ずりわけ、SV はメッセンゞャヌでもあるため、䞍正確な䞀臎を䜿甚しお、圢態を考慮した、高速で䟿利か぀柔軟な怜玢が必芁です。 私たちは車茪の再発明は行わず、ラむブラリに基づいお䜜成された無料の怜玢゚ンゞン Elasticsearch を䜿甚するこずにしたした。 ルセン。 たた、アプリケヌション ノヌドに障害が発生した堎合の問題を排陀するために、Elasticsearch をクラスタヌ (マスタヌ – デヌタ – デヌタ) にデプロむしたす。

github で芋぀けたした ロシア語圢態孊プラグむン Elasticsearch 甚に䜜成しお䜿甚したす。 Elasticsearch むンデックスには、単語のルヌト (プラグむンが決定する) ず N-gram が保存されたす。 ナヌザヌが怜玢するテキストを入力するず、入力されたテキストが N グラムの䞭から怜玢されたす。 むンデックスに保存されるず、単語「texts」は次の N グラムに分割されたす。

[それら、テク、テックス、テキスト、テキスト、ek、ex、ext、テキスト、ks、kst、ksty、st、sty、あなた]、

そしお「テキスト」ずいう蚀葉の語源も保存されたす。 この方法では、単語の先頭、䞭間、末尟を怜玢できたす。

倧局

1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast
蚘事の冒頭の図を繰り返したすが、説明が付いおいたす。

  • むンタヌネット䞊に公開されたバランサヌ。 nginx がありたすが、どれでも構いたせん。
  • Java アプリケヌション むンスタンスは、Hazelcast を介しお盞互に通信したす。
  • 䜿甚する Web ゜ケットを操䜜するには ネッティヌ.
  • Java アプリケヌションは Java 8 で曞かれおおり、バンドルで構成されおいたす。 OSGi。 蚈画には、Java 10 ぞの移行ずモゞュヌルぞの移行が含たれたす。

開発ずテスト

SV の開発ずテストの過皋で、私たちは䜿甚しおいる補品の倚くの興味深い機胜に気づきたした。

負荷テストずメモリリヌク

各 SV リリヌスのリリヌスには負荷テストが含たれたす。 次の堎合に成功したす。

  • テストは数日間機胜し、サヌビス障害は発生したせんでした
  • キヌ操䜜の応答時間は快適なしきい倀を超えたせんでした
  • 前バヌゞョンず比范したパフォヌマンスの䜎䞋は 10% 未満

テスト デヌタベヌスにデヌタを入力したす。これを行うために、運甚サヌバヌから最もアクティブなサブスクラむバヌに関する情報を受け取り、その数倀を 5 (メッセヌゞ、ディスカッション、ナヌザヌの数) で乗算し、その方法でテストしたす。

むンタラクション システムの負荷テストは、次の XNUMX ぀の構成で実行したす。

  1. ストレステスト
  2. 接続のみ
  3. 賌読者登録

ストレス テスト䞭、数癟のスレッドが起動され、メッセヌゞの曞き蟌み、ディスカッションの䜜成、メッセヌゞのリストの受信など、停止するこずなくシステムがロヌドされたす。 通垞のナヌザヌのアクション (未読メッセヌゞのリストを取埗する、誰かに曞き蟌む) ず゜フトりェア ゜リュヌション (異なる構成のパッケヌゞを送信する、アラヌトを凊理する) をシミュレヌトしたす。

たずえば、ストレス テストの䞀郚は次のようになりたす。

  • ナヌザヌのログむン
    • 未読のディスカッションをリク゚ストしたす
    • 50% の確率でメッセヌゞを読む
    • 50% の確率でテキストメッセヌゞを送信する
    • 次のナヌザヌ:
      • 20% の確率で新しいディスカッションが䜜成される
      • ディスカッションのいずれかをランダムに遞択したす
      • 䞭に入りたす
      • リク゚ストメッセヌゞ、ナヌザヌプロファむル
      • このディスカッションからランダムなナヌザヌに宛おた XNUMX ぀のメッセヌゞを䜜成したす
      • ディスカッションを終了したす
      • 20回繰り返したす
      • ログアりトし、スクリプトの先頭に戻りたす

    • チャットボットがシステムに䟵入したす (アプリケヌション コヌドからメッセヌゞングを゚ミュレヌトしたす)
      • 50% の確率でデヌタ亀換甚の新しいチャネルを䜜成する (特別な議論)
      • 50% の確率で既存のチャネルのいずれかにメッセヌゞを曞き蟌む

「接続のみ」シナリオが登堎したのには理由がありたす。 ナヌザヌがシステムに接続しおいるが、ただ参加しおいないずいう状況がありたす。 各ナヌザヌは朝 09 時にコンピュヌタヌの電源を入れ、サヌバヌぞの接続を確立し、沈黙したたたになりたす。 こい぀らは危険で、たくさんいたす。圌らが持っおいるパッケヌゞは PING/PONG だけですが、サヌバヌぞの接続を維持したす (接続を維持するこずはできたせん。新しいメッセヌゞがあったらどうしたすか)。 このテストでは、倚数のそのようなナヌザヌが 00 分以内にシステムにログむンしようずする状況を再珟したす。 これはストレステストに䌌おいたすが、その焊点はたさにこの最初の入力にありたす。そのため、倱敗はありたせん人はシステムを䜿甚しおおらず、すでに萜ちおいたす。それより悪いこずを考えるのは困難です。

サブスクラむバ登録スクリプトは、最初の起動から開始されたす。 ストレステストを実斜し、通信䞭にシステムの速床が䜎䞋しないこずを確認したした。 しかし、ナヌザヌがやっお来お、タむムアりトのために登録が倱敗し始めたした。 登録時に䜿甚したのは、 / dev / random、これはシステムの゚ントロピヌに関連したす。 サヌバヌには十分な゚ントロピヌを蓄積する時間がなく、新しい SecureRandom が芁求されたずきに数十秒間フリヌズしたした。 この状況から抜け出す方法はたくさんありたす。たずえば、安党性の䜎い /dev/urandom に切り替える、゚ントロピヌを生成する特別なボヌドをむンストヌルする、事前に乱数を生成しおプヌルに保存するなどです。 プヌルの問題は䞀時的に解決されたしたが、それ以来、新芏加入者を登録するための別のテストを実行しおいたす。

ロヌドゞェネレヌタずしお䜿甚したす JMeterの。 WebSocket の操䜜方法がわからないため、プラグむンが必芁です。 ク゚リ「jmeter websocket」の最初の怜玢結果は次のずおりです。 BlazeMeter の蚘事、お勧めしたす Maciej Zaleski によるプラグむン.

そこから始めるこずにしたした。

本栌的なテストを開始した盎埌に、JMeter がメモリ リヌクを開始したこずがわかりたした。

このプラグむンは別の倧きな話で、スタヌが 176 個あり、github には 132 個のフォヌクがありたす。 䜜者自身は 2015 幎以降この問題にコミットしおいたせん (2015 幎に実行したしたが、その時は疑惑は生じたせんでした)、メモリ リヌクに関するいく぀かの github の問題、7 件の未完了のプル リク゚ストがありたす。
このプラグむンを䜿甚しお負荷テストを実行する堎合は、次の議論に泚意しおください。

  1. マルチスレッド環境では、通垞の LinkedList が䜿甚され、結果は次のようになりたした。 NPE 実行時。 これは、ConcurrentLinkedDeque に切り替えるか、同期されたブロックによっお解決できたす。 私たちは最初のオプションを自分たちで遞択したした (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. メモリリヌク; 切断時に接続情報が削陀されない (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. ストリヌミング モヌド (WebSocket がサンプルの最埌に閉じられず、蚈画の埌半で䜿甚される堎合) では、応答パタヌンは機胜したせん (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

これは github にあるものの XNUMX ぀です。 我々のしたこず

  1. 取った フォヌク゚リラン・コヌガン (@elyrank) – 問題 1 ず 3 を修正したす
  2. 解決された問題 2
  3. 桟橋を 9.2.14 から 9.3.12 に曎新したした
  4. SimpleDateFormat を ThreadLocal にラップしたした。 SimpleDateFormat はスレッドセヌフではないため、実行時に NPE が発生したす
  5. 別のメモリ リヌクを修正したした (切断時に接続が正しく閉じられたせんでした)

それでも、それは流れたす

蚘憶力はXNUMX日ではなくXNUMX日でなくなり始めた。 時間がたったく残っおいなかったため、スレッドの数を枛らし、゚ヌゞェントを XNUMX ぀立ち䞊げるこずにしたした。 少なくずも䞀週間はこれで十分だろう。

XNUMX日が経ちたした 

珟圚、Hazelcast のメモリが䞍足しおいたす。 ログによるず、数日間のテストの埌、Hazelcast がメモリ䞍足に぀いお䞍平を蚀い始め、しばらくするずクラスタヌが厩壊し、ノヌドが XNUMX ぀ず぀停止し続けたした。 JVisualVM を hazelcast に接続するず、「ラむゞング ゜ヌ」が衚瀺されたした。これは定期的に GC を呌び出したしたが、メモリをクリアできたせんでした。

1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast

hazelcast 3.4 では、マップ/multiMap (map.destroy()) を削陀するずきに、メモリが完党に解攟されないこずが刀明したした。

github.com/hazelcast/hazelcast/issues/6317
github.com/hazelcast/hazelcast/issues/4888

このバグは珟圚 3.5 で修正されおいたすが、圓時は問題でした。 動的な名前を持぀新しい multiMap を䜜成し、ロゞックに埓っお削陀したした。 コヌドは次のようになりたした。

public void join(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.put(auth.getUserId(), auth);
}

public void leave(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.remove(auth.getUserId(), auth);

    if (sessions.size() == 0) {
        sessions.destroy();
    }
}

評䟡:

service.join(auth1, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");
service.join(auth2, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");

multiMap はサブスクリプションごずに䜜成され、䞍芁になった堎合は削陀されたした。 Mapを始めるこずにしたした、キヌはサブスクリプションの名前、倀はセッション識別子になりたす (必芁に応じお、そこからナヌザヌ識別子を取埗できたす)。

public void join(Authentication auth, String sub) {
    addValueToMap(sub, auth.getSessionId());
}

public void leave(Authentication auth, String sub) { 
    removeValueFromMap(sub, auth.getSessionId());
}

チャヌトが改善されたした。

1C 向けに高負荷のスケヌラブルなサヌビスを䜜成した方法ず理由: Enterprise: Java、PostgreSQL、Hazelcast

負荷テストに぀いお他に䜕を孊んだでしょうか?

  1. JSR223 は Groovy で蚘述し、コンパむル キャッシュを含める必芁がありたす。これははるかに高速です。 リンク.
  2. Jmeter-Plugins のグラフは暙準のものよりも理解しやすいです。 リンク.

Hazelcast の経隓に぀いお

Hazelcast は私たちにずっお新しい補品で、バヌゞョン 3.4.1 から䜿い始めたしたが、珟圚、本番サヌバヌはバヌゞョン 3.9.2 を実行しおいたす (この蚘事の執筆時点での Hazelcast の最新バヌゞョンは 3.10)。

IDの生成

私たちは敎数の識別子から始めたした。 新しい゚ンティティに別の Long が必芁だず想像しおみたしょう。 デヌタベヌス内の順序が適切ではありたせん。テヌブルがシャヌディングに関䞎しおいたす。DB1 にはメッセヌゞ ID=1 があり、DB1 にはメッセヌゞ ID=2 があるこずがわかりたす。この ID を Elasticsearch にも Hazelcast にも入れるこずはできたせん。ただし、最悪の堎合は、1 ぀のデヌタベヌスのデヌタを 10 ぀に結合する堎合です (たずえば、これらのサブスクラむバヌには 000 ぀のデヌタベヌスで十分であるず刀断する堎合)。 いく぀かの AtomicLong を Hazelcast に远加し、そこにカりンタヌを保持するず、新しい ID を取埗するパフォヌマンスは、incrementAndGet に Hazelcast ぞのリク゚ストの時間を加えたものになりたす。 しかし、Hazelcast にはよ​​り最適なもの、FlakeIdGenerator がありたす。 各クラむアントに連絡するずき、最初のクラむアントには 10  001、20 番目には 000  XNUMX などの ID 範囲が䞎えられたす。 これで、クラむアントは、発行された範囲が終了するたで、独自に新しい識別子を発行できるようになりたす。 動䜜は高速ですが、アプリケヌション (および Hazelcast クラむアント) を再起動するず、新しいシヌケンスが開始されるため、スキップなどが発生したす。 さらに、開発者は、ID が敎数であるにもかかわらず、非垞に䞀貫性がない理由を実際には理解しおいたせん。 すべおを比范怜蚎し、UUID に切り替えたした。

ちなみに、Twitter のようになりたい人のために、Snowcast ラむブラリがありたす。これは Hazelcast の䞊に Snowflake を実装したものです。 ここで芋るこずができたす:

github.com/noctarius/snowcast
github.com/twitter/スノヌフレヌク

しかし、私たちはもうそれには手を付けおいたせん。

TransactionalMap.replace

もう XNUMX ぀の驚き: TransactionalMap.replace が機胜したせん。 これがテストです:

@Test
public void replaceInMap_putsAndGetsInsideTransaction() {

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            context.getMap("map").put("key", "oldValue");
            context.getMap("map").replace("key", "oldValue", "newValue");
            
            String value = (String) context.getMap("map").get("key");
            assertEquals("newValue", value);

            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }        
    });
}

Expected : newValue
Actual : oldValue

getForUpdate を䜿甚しお独自の眮換を䜜成する必芁がありたした。

protected <K,V> boolean replaceInMap(String mapName, K key, V oldValue, V newValue) {
    TransactionalTaskContext context = HazelcastTransactionContextHolder.getContext();
    if (context != null) {
        log.trace("[CACHE] Replacing value in a transactional map");
        TransactionalMap<K, V> map = context.getMap(mapName);
        V value = map.getForUpdate(key);
        if (oldValue.equals(value)) {
            map.put(key, newValue);
            return true;
        }

        return false;
    }
    log.trace("[CACHE] Replacing value in a not transactional map");
    IMap<K, V> map = hazelcastInstance.getMap(mapName);
    return map.replace(key, oldValue, newValue);
}

通垞のデヌタ構造だけでなく、そのトランザクション バヌゞョンもテストしたす。 IMap は動䜜したすが、TransactionalMap は存圚したせん。

ダりンタむムなしで新しい JAR を挿入

たず、クラスのオブゞェクトを Hazelcast に蚘録するこずにしたした。 たずえば、Application クラスがあるので、それを保存しお読み取りたいずしたす。 保存

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
map.set(id, application);

読む

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
return map.get(id);

すべおが機胜しおいたす。 次に、Hazelcast で次の方法で怜玢するためのむンデックスを構築するこずにしたした。

map.addIndex("subscriberId", false);

そしお、新しい゚ンティティを䜜成するず、ClassNotFoundException を受け取り始めたした。 Hazelcast はむンデックスに远加しようずしたしたが、私たちのクラスに぀いお䜕も知らなかったので、このクラスの JAR が提䟛されるこずを望んでいたした。 それだけですべおうたくいきたしたが、クラスタヌを完党に停止せずに JAR を曎新するにはどうすればよいかずいう新たな問題が発生したした。 Hazelcast は、ノヌドごずの曎新䞭に新しい JAR を取埗したせん。 この時点で、むンデックス怜玢なしでも生きおいけるず刀断したした。 結局のずころ、Hazelcast を Key-Value ストアずしお䜿甚すれば、すべおが機胜するのでしょうか? あたり。 ここでも、IMap ず TransactionalMap の動䜜は異なりたす。 IMap が気にしない堎合、TransactionalMap ぱラヌをスロヌしたす。

Iマップ。 5000 個のオブゞェクトを曞き蟌み、読み取りたす。 すべおが期埅されおいたす。

@Test
void get5000() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application");
    UUID subscriberId = UUID.randomUUID();

    for (int i = 0; i < 5000; i++) {
        UUID id = UUID.randomUUID();
        String title = RandomStringUtils.random(5);
        Application application = new Application(id, title, subscriberId);
        
        map.set(id, application);
        Application retrieved = map.get(id);
        assertEquals(id, retrieved.getId());
    }
}

ただし、トランザクションでは機胜せず、ClassNotFoundException が発生したす。

@Test
void get_transaction() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application_t");
    UUID subscriberId = UUID.randomUUID();
    UUID id = UUID.randomUUID();

    Application application = new Application(id, "qwer", subscriberId);
    map.set(id, application);
    
    Application retrievedOutside = map.get(id);
    assertEquals(id, retrievedOutside.getId());

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            TransactionalMap<UUID, Application> transactionalMap = context.getMap("application_t");
            Application retrievedInside = transactionalMap.get(id);

            assertEquals(id, retrievedInside.getId());
            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }
    });
}

3.8 では、ナヌザヌ クラス デプロむメント メカニズムが登堎したした。 XNUMX ぀のマスタヌ ノヌドを指定し、そのノヌド䞊で JAR ファむルを曎新できたす。

珟圚、私たちはアプロヌチを完党に倉曎したした。私たちはそれを自分たちで JSON にシリアル化し、Hazelcast に保存したす。 Hazelcast はクラスの構造を知る必芁がないため、ダりンタむムなしで曎新できたす。 ドメむン オブゞェクトのバヌゞョン管理はアプリケヌションによっお制埡されたす。 異なるバヌゞョンのアプリケヌションが同時に実行される可胜性があり、新しいアプリケヌションが新しいフィヌルドを持぀オブゞェクトを曞き蟌むが、叀いアプリケヌションがこれらのフィヌルドをただ認識しおいないずいう状況が発生する可胜性がありたす。 同時に、新しいアプリケヌションは、叀いアプリケヌションによっお曞き蟌たれた、新しいフィヌルドを持たないオブゞェクトを読み取りたす。 このような状況はアプリケヌション内で凊理したすが、簡単にするためにフィヌルドの倉曎や削陀は行わず、新しいフィヌルドを远加しおクラスを拡匵するだけです。

高いパフォヌマンスを保蚌する方法

Hazelcast ぞの XNUMX 回の旅行 - 良い、デヌタベヌスぞの XNUMX 回の旅行 - 悪い

デヌタをキャッシュに保存する方が、デヌタベヌスに保存するよりも垞に優れおいたすが、未䜿甚のレコヌドを保存するこずも望たしくありたせん。 䜕をキャッシュするかに぀いおの決定は、開発の最終段階たで残されたす。 新しい機胜がコヌディングされるず、PostgreSQL のすべおのク゚リのログ蚘録がオンになり (log_min_duration_statement が 0 に)、負荷テストが 20 分間実行されたす。収集されたログを䜿甚しお、pgFouine や pgBadger などのナヌティリティで分析レポヌトを䜜成できたす。 レポヌトでは、䞻に䜎速で頻繁なク゚リを探したす。 遅いク゚リの堎合は、実行蚈画 (EXPLAIN) を構築し、そのようなク゚リを高速化できるかどうかを評䟡したす。 同じ入力デヌタに察する頻繁なリク゚ストはキャッシュにうたく収たりたす。 ク゚リを「フラット」に保ち、ク゚リごずに XNUMX ぀のテヌブルを保持するように努めたす。

搟取

オンラむンサヌビスずしおのSVは2017幎春に皌働し、単䜓補品ずしおは2017幎XNUMX月にSVがリリヌスされたした圓時はベヌタ版の状態。

XNUMX 幎以䞊の運甚においお、CB オンラむン サヌビスの運甚に重倧な問題は発生しおいたせん。 オンラむン サヌビスを監芖するには、 ザビックスから収集しおデプロむしたす 竹.

SV サヌバヌのディストリビュヌションは、ネむティブ パッケヌゞ (RPM、DEB、MSI) の圢匏で提䟛されたす。 さらに、Windows の堎合は、サヌバヌ、Hazelcast、および Elasticsearch を XNUMX 台のマシンにむンストヌルする XNUMX ぀のむンストヌラヌを XNUMX ぀の EXE 圢匏で提䟛したす。 圓初、このバヌゞョンのむンストヌルを「デモ」バヌゞョンず呌んでいたしたが、これが最も䞀般的な導入オプションであるこずが明らかになりたした。

出所 habr.com

コメントを远加したす