DUICUO

分析 | TARSオープンソースプロジェクトがGo言語バージョンをリリース

Tarsは、テンセントがオープンソース化したマイクロサービスフレームワークです。昨年4月にオープンソース化され、今年6月にLinux Foundationに寄贈されました。Tarsは、開発から運用までをカバーする包括的なソリューションをユーザーに提供し、製品やサービスの迅速な開発、リリース、展開、立ち上げ、保守を支援します。スケーラブルなプロトコルエンコード/デコード、高性能RPC通信フレームワーク、名前ルーティングと検出、リリース監視、ログ統計、構成管理を統合し、マイクロサービスを活用した安定性と信頼性に優れた分散アプリケーションを迅速に構築し、完全かつ効果的なサービスガバナンスを実現します。1年以上の開発期間を経て、Tarsは現在、China Literature Group、Huya Live、iFlytek、Youpin Wealth、Longtu Games、Jintaiyang Educationなど、多くの企業で利用されています。

9月15日、テンセントはTarsのGolang版であるTars-Goを正式にオープンソース化することを発表しました。Tarsのオープンソース化発表では、Tarsと現在市場に出回っている他のマイクロサービスフレームワークとの違い、技術アーキテクチャ、パフォーマンスデータ、そして関連する技術的な詳細が明らかになりました。本記事では、今回リリースされたGolang版Tarsについて詳しく紹介します。

プロジェクトアドレス: https://github.com/TarsCloud/TarsGo

サービス ガバナンスと複数の言語をサポートします。この機能を提供するのは Tars だけです。

マイクロサービスアーキテクチャはここ2年間で爆発的な人気を博し、最も主流のアーキテクチャパターンとなりました。マイクロサービスフレームワークについて議論する際には、Dubbo、gRPC、Spring Cloudといった著名なプロジェクトを当然ながら挙げることができます。これらのマイクロサービスフレームワークは、サービスガバナンスと多言語サポートの有無に基づいて、以下の4つのカテゴリに分類できます。

  • サービス呼び出しのみを処理し、サービスガバナンスを欠くフレームワークの代表例としては、gRPCとThriftが挙げられます。これらのフレームワークはサービス間通信の問題を効果的に解決し、多くのフレームワークが複数の言語をサポートしていますが、ユーザーはこれらのフレームワークを使用する際に、サービスガバナンスを自ら管理する必要があります。

  • サービスガバナンスを包含しながらも、単一の言語をサポートするフレームワーク。代表的な例としては、Spring CloudやDubboなどが挙げられます。どちらもJavaで実装されたフレームワークです。ユーザーは複数のオープンソースプロジェクトを統合し、サービスガバナンスなどのニーズを満たすことができます。

  • サービスメッシュサービスガバナンスをサポートし、サイドカーパターンを用いて複数言語のフレームワークサポートの問題を解決します。企業は通信の問題や非同期呼び出しを解決するために、一連の通信コンポーネントをカプセル化する必要があり、アーキテクチャとメンテナンスの複雑さが増します。

  • サービスガバナンスを組み込み、多言語に対応したフレームワーク。現在、業界では比較的数が少なく、Tars以外に代表的なフレームワークは見当たりません。

上記の分析から、Tarsはサービスガバナンスをサポートしながら多言語サポートも提供するマイクロサービスフレームワークであることがわかります。これがTarsのユニークな特徴であり、その強みです。

Tarsは物理マシン、仮想マシン、コンテナ上で実行できます。メインプロトコルはIDLベースのTarsプロトコルです。これはpbに似たバイナリ解析プロトコルです。Tarsは他のプロトコルやユーザー定義プロトコルもサポートしています。

主な呼び出し方法はRPCで、同期、非同期、一方向呼び出しをサポートしています。サービスガバナンスに関しては、サービス登録やディスカバリといった業界標準の機能に加え、Setモデル、自動リージョン認識、オーバーロード保護といった大規模トラフィック処理のためのガバナンス機能も提供しています。言語に関しては、新たにサポートされたGolangに加え、現在C++、Java、NodeJS、PHPをサポートしています。さらに、フレームワーク全体はDevOpsとの連携も容易です。

Tars は、レジストリ、サービス ノード、基本サービス クラスターの 3 つの部分に分かれています。

レジストリ

レジストリは、マイクロサービス クラスターの管理および制御ノードであり、サービス登録や検出などの機能を提供します。

サービスノード

サービスノードは、Tarsにおける最小単位です。コンテナ、仮想マシン、または物理マシンのいずれかです。ビジネスサービスは、複数のサービスノードを展開することで、キャパシティとフォールトトレランスの問題に対処します。サービスノードには、ノード管理サービスと1つ以上のビジネスサービスが含まれます。ノードサービスは、ノード上のサービスの統合管理を提供し、サービスノードの起動、停止、監視などの機能を提供します。また、ビジネスサービスノードから報告されるハートビートを受信し、サービス検出のデータソースとしてレジストリに報告します。

基本サービスクラスター

基本サービスクラスターは、マイクロサービスのガバナンス問題を解決するために設計された一連のサービスです。サービスノードの数は可変であり、フォールトトレランスと災害復旧のために、通常は複数のサーバーに展開する必要があります。具体的なノード数はビジネスの規模によって異なります。例えば、ビジネス規模が大きく、より多くのログ記録が必要な場合は、より多くのログサービスノードを展開する必要があります。基本サービスには、主に監視と統計、構成センター、ログ集約、認証と承認、分散コールチェーンが含まれます。Tarsは非常に包括的なサービスガバナンス機能を備えています。

Tarsはレジストリ、サービスノード、そして基盤となるサービスクラスタと連携し、サービス検出/登録、負荷分散、認証、分散トレースといったサービスガバナンスタスクを透過的に実行します。例えば、フレームワークはレジストリを介してxxxsvrを登録し、クライアントはレジストリにアクセスしてサービスアドレス情報のリストを取得します。その後、クライアントは必要に応じて適切な負荷分散方式を選択し、サービスを呼び出します。負荷分散は、ラウンドロビン、ハッシュ、重み付けなど、複数の方式をサポートしています。

障害ノードをより迅速に保護するために、クライアントは、呼び出されたサービスの呼び出し時に発生した異常に基づいて障害の有無を判断し、より迅速な障害保護を実現します。具体的には、クライアントからサーバーへの呼び出しで、設定されたしきい値を超えるタイムアウトが連続して発生した場合、またはタイムアウト率が特定のパーセンテージしきい値を超えた場合、クライアントはそのサーバーノードをブロックし、トラフィックを正常なノードにリダイレクトします。ブロックされたサーバーノードでは、定期的に再接続が試行され、成功すると通常のトラフィック分散が再開されます。

ビジネスの成長に伴い、サービス展開は必然的にデータセンターやリージョンをまたぐものになります。従来の負荷分散手法では、サービスがリージョンやデータセンターをまたいで展開される場合、ネットワークの問題によりレイテンシが増加する可能性があります。サービス間のアクセスを高速化し、リージョン間およびデータセンター間の展開に伴うネットワークリソースの消費を削減し、ネットワーク障害の影響を軽減するために、Tarsはリージョンを考慮した自動サービスガバナンス機能を提供します。

レジストリと開発フレームワークを使用して自動領域認識を実現する利点は次のとおりです。

  • メンテナンスが簡単

  • 遅延を減らし、帯域幅の消費を減らす

  • より強力な災害復旧機能

さらに、Tars はSetモデルも提供します。

セットモデルは、ビジネス機能の特性に基づいてユニットをセットで展開することで、展開を標準化・標準化します。セットモデルの利点は次のとおりです。

  • 障害の拡大を効果的に防止

  • 容量管理に便利

トラフィック制御において、サービスリリースとデプロイメントにおいて直面する主な課題は、「業務に影響を与えずにサービス変更を行う方法」と「カナリアリリースの検証をいかに実施するか」です。Tarsでは、レジストリと開発フレームワークを通じてオンデマンドでトラフィック制御を実施し、ロスレスリリースとカナリアリリースの目標を達成できます。

言語サポートに関しては、これまでサポートされていた PHP、C++、NodeJS、Java に加えて、Golang サポートが追加されました。

さらに、Tars は、操作を可視化し、Web ベースで実行できるようにする OSS プラットフォームを提供します。

主に以下の特徴が含まれます。

  • ビジネス管理: これには、デプロイされたサービスに加えて、サービス管理、リリース管理、サービス構成、サービス監視、機能監視が含まれます。

  • 運用および保守管理: サービスの展開、容量の拡張、テンプレートの管理など。

  • オープン API を提供し、独自の OSS システムをカスタマイズできます。

ターズゴー、ターズゴー!

多言語サポートはTarsの大きな強みです。TarsはこれまでにC++、Java、PHP、NodeJS版をリリースしています。Goのコルーチン並行処理メカニズムは、大規模で高並列なバックエンドサーバーアプリケーションの開発に非常に適しています。さらに、Docker、Kubernetes、Etcdといったコンテナ化技術の急速な発展に伴い、Goはますます人気が高まり、クラウドネイティブ環境で推奨される言語となっています。こうしてTarsのGo版が開発され、Tars-Goのリリースは、今日のクラウドネイティブ化が進む環境において特に重要な意味を持ちます。

新しくリリースされた Go バージョン Tars-Go の全体的なアーキテクチャは、次の図に示すように、3 つの主要な部分に分けられます。

  • 左側はtars2goツールです。tars2goは、プログラミング言語の構造を記述するための形式手法であるバッカス正規形(BNF)に基づいています。tarsファイルの構文解析と字句解析を実行し、クライアントとサーバーの両方で使用できるコードを生成します。また、tarsプロトコルのバイナリストリームのエンコードとデコード機能も提供し、バイナリパケットを対応するGoデータ構造に変換します。

  • 右側は パッケージtarsにはクライアント サーバーには 2 つの部分があります。

    • クライアントは、Servantproxy、Communicator、ObjProxy、adapterproxyなどの論理構造で構成されています。これらの構造は、サーブレットおよびobjオブジェクトに対応するサーバーノードのIPアドレスとポートを管理し、C++ロジックとの整合性を維持します。下位レベルでは、net.Connを使用して特定の接続を確立し、SendQueueチャネルを使用して同時実行を制御します。また、クライアントには、機能監視と統計監視レポート用のGoroutineも含まれています。

    • サーバーは、`net` パッケージのリスナーを使用して TCP および UDP 接続を管理します。複数の Goroutine が `accept` 操作を処理し、承認された `net.Conn` 接続は `SendQueue` チャネルを介してバックエンドのハンドラーに渡されます。ハンドラーは、ワーカー Goroutine のセットで構成されます。各 Goroutine は `net.Conn` に基づいてパケットを送受信し、Tars プロトコルをデコードし、ディスパッチャー(tars2go によって生成)を使用してユーザーのコード実装を呼び出します。結果はバイナリストリームにエンコードされ、クライアントに返されます。サーバーには、非同期のリモートログレポートや、同期呼び出しによるリクエストのブロックを防ぐためのその他の機能を実装するための Goroutine も含まれています。

編集者は、TarsオープンソースチームがTars-Goの開発中に、様々な側面でパフォーマンスチューニングと修正を行ったことを知りました。Tars-Goの初期バージョンは機能開発と改善に重点が置かれており、体系的な負荷テストとパフォーマンス分析が不足していました。ビジネスでの使用期間を経て、パフォーマンス最適化が優先事項となりました。Tarsオープンソースチームはまず、tars2goツールを最適化し、構文木生成プロセス中に型情報を生成することで、型チェックにリフレクションを使用しないようにすることで、エンコードとデコードの効率を2倍向上させました。その後、サービス全体に対して複数回の負荷テストを実施し、CPUプロファイリングによるパフォーマンス分析を実施しました。

以下に、パフォーマンスの改善と最適化の例をいくつか示します。  

タイマーのパフォーマンスの問題

Tars-Goは、受信リクエストごとに処理用のgoroutineを作成します。呼び出しタイムアウトを処理するためにタイマーが作成され、リクエストが終了すると削除されます。同時実行性が高まると、タイマーの作成と削除が頻繁に行われ、サービスのCPU時間を大量に消費します。

開発チームは、マルチCPUシナリオにおいて多数のタイマーが存在するとパフォーマンスが著しく低下するという問題を発見しました。最適化のアプローチとして、各CPU (p) に独自のタイマーを割り当てることが挙げられます。これにより、全体的な同時実行パフォーマンスが大幅に向上する可能性があります。そのため、Tars-Goはコンパイル環境をバージョン1.10.3にアップグレードしました。プロファイルでは大幅なパフォーマンス向上が見られ、チームはタイムポーリングアルゴリズムに基づく独自のタイマーを実装することで、精度とパフォーマンス、そして効率性を両立させました。

ネットパッケージSetDeadline呼び出しのパフォーマンスの問題

Tars-Goは、ネットワーク接続の読み取り/書き込みタイムアウトを設定するために、`net`パッケージの`SetReadDeadline`および`SetWriteDeadline`呼び出しを使用します。しかし、プロファイリングの結果、これらの呼び出しは高並列処理環境ではCPU時間を大量に消費することが判明しました。これらの呼び出しを回避するために、Sysfdを使用してソケットの読み取り/書き込みタイムアウトを設定します。  

バイトバッファによるパフォーマンスの問題

下の図に示すように、時間の大部分はスライス関連の操作に費やされています。これは、パケットのエンコードおよびデコード処理中に、bytes.Bufferが一時記憶領域として使用されていることを示しています。基盤となるbytes.Bufferが使用するバイトスライスのサイズが不足すると、一定量のメモリ空間が割り当てられます。頻繁な割り当ては非効率であり、大きなパケットを処理する際にパフォーマンスが大幅に低下します。

Tars-Goは、RedisのメモリモデルとLinuxのスラブメカニズムに着想を得て、頻繁に作成・破棄されるオブジェクトに対して事前作成と再利用のアプローチを採用しています。Go言語自体は、一時オブジェクトを再利用してガベージコレクション(GC)を削減するための「sync.Pool」メカニズムを提供しています。これを基に、Tars-GoはLinuxのスラブメカニズムに類似したバッファ管理スキームを実装し、大幅なパフォーマンス向上を実現しています。

その他の最適化

上記のパフォーマンス最適化の後、 Tars-Go は小さなパケットの同時実行パフォーマンスを5 倍向上させました。

  • テストCPU: 4コア/8スレッド、クロック速度3.3GHz、16GB RAM

  • 負荷テストロジック: クライアントは一定量のデータをサーバーに送信し、サーバーはそのデータをそのままクライアントに返します。

  • サーバーは単一のプロセスであり、複数のクライアントがテストを開始します。

Tars-Goプログラミング例

Tarsプロトコルは、言語に依存しないIDLで記述されたバイナリプロトコルです。ツールはサーバーとクライアントのコードを自動生成します。以下はTarsプロトコルの例です。

プログラミングを行うときは、まず次に示すように Tars ファイルを定義する必要があります。インターフェイス Mult を定義します。ここで、a と b は入力パラメータ、c は出力パラメータで、どちらも整数です。

次に、インターフェースコードを生成します。`tars2go JesseTest.tars` を使用すると、`Pragma JesseTest` フレームワークの `servant` メソッドと `Mult` メソッドのフレームワーク実装が自動的に生成されます。ビジネスロジックは実装の詳細を気にする必要はありません。

最後に、インターフェース コードを実装し、入力パラメータ a と b を乗算した結果を c に格納し、クライアントに返します。

その後、`go build` を使用してコンパイルできます。

クライアントは、入力パラメータと出力パラメータに焦点を合わせ、Tars ファイルから変換されたパッケージをインポートして RPC 呼び出しを完了するだけで済みます。

今後、Linux FoundationはTarsプロジェクトのコミュニティ運営メカニズムを強化し、Tarsの影響力を中国から国際舞台へと拡大していきます。

  • タール:https://github.com/TarsCloud

  • Tars-Go : https://github.com/TarsCloud/TarsGo