導入CloudWeGo - Shmipcは、ByteDanceのサービスフレームワークチームによって開発された高性能プロセス間通信ライブラリです。共有メモリ上に構築され、ゼロコピー通信を特徴としています。また、導入された同期メカニズムによりバッチI /O処理が可能で、他のプロセス間通信方式と比較してパフォーマンスが大幅に向上します。ByteDanceでは、Shmipcはサービスメッシュシナリオにおいて、メッシュプロキシプロセスとビジネスロジックプロセス間の通信、および一般的なサイドカープロセスとの通信に使用され、大規模パッケージやI/O集中型のシナリオで大幅なパフォーマンス向上を実現します。 オープンソースコミュニティではこのトピックに関する情報があまり公開されていません。Shmipcのオープンソースリリースは、コミュニティへの貢献と参考資料の提供を目的としています。この記事では、主にShmipcの主要な設計原則と将来の進化計画について紹介します。 Go バージョンの実装: https://github.com/cloudwego/shmipc-go デザインの詳細: https://github.com/cloudwego/shmipc-spec プロジェクトの背景ByteDanceでは、Service Meshの導入時に大幅なパフォーマンス最適化を実施しました。Service Meshのトラフィックインターセプションの重要な特徴の一つは、メッシュプロキシがプロセス間通信(IPC)を介してマイクロサービスフレームワークと通信することです。これにより、iptablesなどのオープンソースソリューションよりも優れたパフォーマンスを実現しています。しかし、従来の最適化手法では、もはや大幅なパフォーマンス向上は得られませんでした。そこで、私たちはプロセス間通信に着目し、Shmipcを開発しました。 デザインコンセプトゼロコピー実稼働環境において最も広く使用されているプロセス間通信(IPC)方式は、UnixドメインソケットとTCPループバック(localhost:$PORT)であり、ベンチマークにおけるパフォーマンス差はわずかです。技術的には、どちらもユーザー空間とカーネル空間間で通信データをコピーする必要があります。RPCシナリオでは、1つのRPCプロセスでIPC用のメモリコピーが4回発生します。リクエストパス用に2回、レスポンスパス用に2回です。 現代のCPUではシーケンシャルコピーは非常に高速ですが、大規模なシナリオでは最大4回のメモリコピーを削減することでCPU使用率を節約できます。これは、共有メモリ通信のゼロコピー特性のおかげで簡単に実現できます。ただし、ゼロコピーを実現するには、共有メモリ自体に関連するいくつかの追加手順が必要です。例えば、 - マイクロサービスフレームワークのシリアル化とデシリアル化について詳しく説明します。リクエストまたはレスポンスがシリアル化される際、対応するバイナリデータが共有メモリ内に既に存在している必要があります。非共有メモリバッファにシリアル化されてから共有メモリバッファにコピーされるのではなく、です。
- プロセス同期メカニズムを実装します。あるプロセスが共有メモリにデータを書き込む場合、別のプロセスはそれを認識できないため、別のプロセスに通知するための同期メカニズムが必要です。
- 効率的なメモリ割り当てと再利用。プロセス間共有メモリの割り当てと再利用メカニズムのオーバーヘッドが、ゼロコピー特性の利点を損なわない程度に低く抑えられていることを確認します。
同期メカニズムさまざまなシナリオを検討する: - オンデマンドのリアルタイム同期。遅延に非常に敏感なオンラインシナリオに適しており、各書き込み操作の後にピアプロセスに通知します。Linuxでは、TCPループバック、Unixドメインソケット、イベントファイルディスクリプタ(FDS)など、多くのオプションがあります。イベントファイルディスクリプタはベンチマークパフォーマンスがわずかに優れていますが、プロセス間でファイルディスクリプタを渡すと複雑さが増し、IPCではパフォーマンスの向上は顕著ではありません。複雑さとパフォーマンスのトレードオフは慎重に検討する必要があります。ByteDanceでは、プロセス同期にUnixドメインソケットを選択しました。
- スケジュール同期。オフラインシナリオに適しており、レイテンシの影響を受けません。共有メモリ内のカスタムフラグにアクセスしてデータが書き込まれたかどうかを検出するために、頻繁にスリープサイクルを使用します。ただし、スリープサイクル自体にシステムコールが必要となるため、Unixドメインソケットによる読み書きよりもオーバーヘッドが大きくなることに注意してください。
- ポーリング同期。レイテンシの影響を受けやすいものの、CPUの影響を受けにくいシナリオに適しています。これは、単一のコアを使用して共有メモリ内のカスタムフラグをポーリングすることで実現できます。
一般に、オンデマンドのリアルタイム同期と定期的な同期は完了するためにシステムコールを必要としますが、ポーリング同期はシステムコールを必要としませんが、CPU コアを常にフル稼働させる必要があります。 大量収穫IOオンラインシナリオでは、オンデマンドのリアルタイム同期では、各データ書き込みのプロセス同期が必要です (下図の 4)。これによりレイテンシの問題は解決されますが、ゼロコピーのパフォーマンス上の利点は、相互作用を必要とするデータパケットの数が重要なしきい値を超えた場合にのみ明らかになります。したがって、バッチ IO 処理を扱うために共有メモリ内に IO キューが構築され、高 IO 密度のシナリオでも利点を得られるようになります。基本的な考え方は、1 つのプロセスが IO キューに要求を書き込むと、別のプロセスにその処理を通知するというものです。次の要求が到着すると (下図の IOEvents 2~N に対応し、各 IOEvent は共有メモリ内の要求の場所を個別に示します)、ピアプロセスがまだ IO キュー内の要求を処理中であれば、通知は必要ありません。したがって、プロセスの IO 集約型が多ければ多いほど、バッチ処理のパフォーマンスは向上します。 さらに、オフラインシナリオでは、時間同期自体がIOのバッチ処理となります。バッチ処理の効果により、プロセス同期によって発生するシステムコールを効果的に削減できます。スリープ間隔が長いほど、プロセス同期のオーバーヘッドは低くなります。 ポーリング同期では、プロセス同期のオーバーヘッドを削減するように設計されているため、バッチI/Oハーベスティングを考慮する必要はありません。一方、ポーリング同期はCPUコアを最大限に活用し、同期メカニズムのオーバーヘッドをデフォルトで最大化することで、極めて低い同期レイテンシを実現します。 パフォーマンスの向上ベンチマーク X軸はパケットサイズ、Y軸は1回のピンポンにかかる時間をマイクロ秒単位で表します(値が低いほど良い)。ご覧のとおり、パケットサイズが小さいシナリオでは、ShmipcはUnixドメインソケットよりもパフォーマンス上の利点があり、パケットサイズが大きいほどパフォーマンスが向上します。 データソース: git clone https://github.com/cloudwego/shmipc-go && go test -bench=BenchmarkParallelPingPong -run BenchmarkParallelPingPong 生産環境ByteDanceの本番環境におけるサービスメッシュエコシステムでは、 3,000以上のサービスと100万以上のインスタンスにShmipcを導入しました。様々なビジネスシナリオで様々なメリットが実証されており、リスク管理ビジネスでは最も高い効果が得られ、全体のリソース使用量を24%削減しました。しかし、一部のシナリオでは大きなメリットが見られず、場合によっては悪化するケースもありました。大規模パッケージとI/O負荷の高いシナリオの両方で、大きなメリットが明らかになりました。 坑道掘削記録 ByteDance の実際の実装プロセスでは、いくつかの落とし穴に遭遇し、それがいくつかのオンラインインシデントにつながりましたが、これらの経験は参考として非常に貴重です。 - 共有メモリリーク。IPCプロセスにおける共有メモリの割り当てと回収は2つのプロセスにまたがるため、慎重に処理しないとメモリリークが発生しやすくなります。この問題は非常に深刻ですが、メモリリークを事前に検出し、事後調査のための監視手段を用意することで、影響を軽減できます。
- プロアクティブな検出。プロアクティブな検出は、割り当てられたメモリの合計サイズや回収されたメモリの合計サイズなどの統計情報を追加し、それを監視システムにまとめることで実現できます。
- 観察方法:共有メモリレイアウト設計にメタデータを追加することで、組み込みのデバッグツールを使用してリーク発生時の共有メモリを分析できます。これにより、リークしたメモリの量、その内容、および関連メタデータが明らかになります。
- パケットクロストーク。パケットクロストークは最も厄介な問題であり、原因は多岐にわたり、深刻な結果につながることがよくあります。ある業務アプリケーションでパケットクロストークのインシデントが発生したことがあります。原因は、大きなパケットによって共有メモリが不足し、通常パスへのフォールバック設計上の欠陥によってパケットクロストークの発生確率が低かったことです。トラブルシューティングのプロセスと原因は一般的ではありません。より良いアプローチは、より多くのシナリオで統合テストと単体テストを実施し、パケットクロストークを未然に防ぐことです。
- 共有メモリのクラッシュ。共有メモリには、ファイルシステム内のパスを mmapping するのではなく、可能な限りmemfd を使用する必要があります。以前は、ファイルシステムパスを mmapping することでメモリを共有していました。Shmipc の有効化と共有メモリパスは環境変数で指定し、ブートプロセスをアプリケーションプロセスに挿入していました。しかし、アプリケーションプロセスが、アプリケーションプロセスの環境変数を継承し、Shmipc も統合するプロセスをフォークする状況が発生することがあります。フォークされたプロセスとアプリケーションプロセスが同じ共有メモリを mmapping すると、クラッシュが発生します。ByteDance のシナリオでは、アプリケーションプロセスが Go のプラグインメカニズムを使用して外部
.soファイルをロードしていました。この.soファイルは Shmipc を統合し、すべての環境変数を参照しながらアプリケーションプロセス内で実行されていました。その結果、アプリケーションプロセスと同じ共有メモリを mmapping し、実行時に未定義の動作が発生しました。 - Sigbus のコアダンプ。当初は、
mmap /dev/shm/パス(tmpfs)以下のファイルをマッピングすることでメモリを共有し、ほとんどのアプリケーションサービスは Docker コンテナインスタンスで実行していました。コンテナインスタンスには `tmpfs` の容量制限があり(`df -h` で確認できます)、`mmap` がアクセスする共有メモリがこの制限を超えると、Sigbus エラーが発生していました。`mmap` 自体はエラーを報告しませんが、Sigbus エラーは実行時にアプリケーションプロセスが制限を超えるアドレス空間を使用し、クラッシュを引き起こした場合にのみ発生します。解決策は、ポイント 3 と同様に、共有メモリに`memfd` を使用することです。
その後の進化- マイクロサービス RPC フレームワーク CloudWeGo/Kitex に統合します。
- マイクロサービス HTTP フレームワーク CloudWeGo/Hertz に統合します。
- Shmipc のオープンソース Rust バージョンは、Rust RPC フレームワーク CloudWeGo/Volo に統合されています。
- Shmipc のオープンソース C++ バージョン。
- 時間指定同期メカニズムを導入すると、オフラインのシナリオに適しています。
- ポーリング同期メカニズムは、レイテンシ要件が極めて低いシナリオに適しています。
- これにより、Log SDK と Log Agent、Metrics SDK と Metrics Agent などの他の IPC シナリオも有効になります。
要約本記事がShmipcの設計原理を含む基本的な理解を深める一助となれば幸いです。実装の詳細や使用方法については、記事冒頭に記載されているプロジェクトアドレスをご参照ください。ご関心のある方は、ShmipcプロジェクトにIssueやPRを提出し、共にCloudWeGoオープンソースコミュニティを構築していただければ幸いです。また、Shmipcが、IPC分野における高性能クラウドネイティブアーキテクチャの構築において、より多くの開発者や企業に貢献することを願っています。 |