はじめに
Ibco は、WeChat バックエンドで大規模に使用されている c/c++ コルーチン ライブラリで、2013 年以来、WeChat バックエンドの数万台のマシンで安定して実行されています。 Libco は、Tencent の 6 つの主要なオープンソース プロジェクトの 1 つとして、2013 年に初めてオープンソース化されました。最近、メジャー アップデートが行われ、https://github.com/tencent/libco で同期されています。 libco は、システムの高い同時実行機能を提供しながら、バックエンドのアジャイル同期スタイルのプログラミング モデルをサポートします。
libco によってサポートされる機能
ビジネス ロジックに侵入する必要がなく、マルチプロセスおよびマルチスレッド サービスをコルーチン サービスに変換し、同時実行機能が 100 倍向上します
CGI フレームワークをサポートし、Web サービスを簡単に構築します (新規) ;
gethostbyname 、mysqlclient、ssl およびその他の一般的に使用される 3 番目のライブラリをサポート (新機能)
オプションの共有スタック モードにより、1 台のマシンで数千万の接続に簡単にアクセスできます
完全で簡潔なコルーチン プログラミング インターフェイス
– pthread のようなインターフェイス設計。コルーチンの作成と復元は、co_create や co_resume などのシンプルで明確なインターフェイスを通じて完了できます。 – コルーチン間の通信用のクラス __thread、コルーチン セマフォ co_signal (新規)。言語レベルのラムダ実装、コルーチンと組み合わせて、バックグラウンドの非同期タスクを適切に記述して実行します (新規) – epoll/kqueue に基づく小型で軽量のネットワーク フレームワーク、時間ルーレットに基づく高性能タイマー
バックグラウンド。
初期の WeChat 複雑で変化するビジネス要件と、バックエンドでの製品の迅速な反復のため、ほとんどのモジュールは半同期および半非同期モデルを採用しています。アクセス層は非同期モデルであり、ビジネス ロジック層は同期マルチプロセスまたはマルチスレッド モデルです。ビジネス ロジックの同時実行能力はわずか数十から数百です。 WeChatのビジネスが成長するにつれて、システムの規模はますます大きくなり、各モジュールはバックエンドサービス/ネットワークジッターの影響を受けやすくなります。
非同期変換の選択
WeChatバックエンドの同時実行機能を向上させるための一般的なアプローチは、既存のネットワーク上のすべてのサービスを非同期モデルに変更することです。このアプローチでは、フレームワークからビジネス ロジック コードに至るまでの膨大な作業が必要となり、完全な変換が必要となり、時間と労力がかかり、リスクが伴います。そこで私たちはコルーチンの使用を検討し始めました。
しかし、コルーチンを使用すると、次のような課題に直面します:
業界のコルーチンには、C/C++ 環境での大規模なアプリケーションの経験がありません;
コルーチンのスケジューリングを制御する方法;
次のような同期スタイルの API 呼び出しを処理する方法ソケット、mysqlclient など;
既存のグローバル変数とスレッドのプライベート変数の使用に対処する方法;
最終的に、libco を通じて上記の問題をすべて解決し、ビジネス ロジックの非侵襲的な非同期変換を実現しました。 libco を使用して、数百の WeChat バックエンド モジュールをコルーチンと非同期変換に変換しました。変換プロセス中、ビジネス ロジック コードは基本的に変更されませんでした。これまでのところ、WeChat バックエンドのサービスのほとんどはマルチプロセスまたはマルチスレッドのコルーチン モデルであり、以前と比べて同時実行機能が質的に向上しており、libco は WeChat バックエンド フレームワークの基礎となっています。
libcoフレームワーク
libcoはフレームワーク内でインターフェース層、システム機能フック層、イベントドリブン層の3つの層に分かれています。
同期スタイルの API 処理
同期スタイルの API、主に同期ネットワーク呼び出しの場合、libco の主なタスクは、これらの待機によるリソースの占有を排除し、システムの同時実行パフォーマンスを向上させることです。通常のネットワーク バックグラウンド サービスの場合、完全なネットワーク インタラクションを完了するために、接続、書き込み、読み取りなどの手順を実行することがあります。これらの API を同期的に呼び出すと、スレッド全体がネットワーク対話を待ってハングします。
同期プログラミングスタイルの同時実行パフォーマンスは良くありませんが、コードロジックが明確で記述が簡単であるという利点があり、迅速なビジネスの反復とアジャイル開発をサポートできます。既存のビジネス ロジック コードをオンラインで変更することなく同期プログラミングの利点を維持し続けるために、libco は革新的にネットワーク呼び出しインターフェイス (フック) を引き継ぎ、コルーチンの放棄と回復をコールバックを使用した非同期ネットワーク IO のイベントとして登録しました。 。ビジネス処理が同期ネットワーク リクエストに遭遇すると、libco レイヤーはネットワーク リクエストを非同期イベントとして登録し、このコルーチンは CPU 占有を放棄し、CPU は実行のために他のコルーチンに渡されます。 Libco は、ネットワーク イベントが発生するかタイムアウトになると、コルーチンの実行を自動的に再開します。
同期スタイル API のほとんどを Hook メソッドを通じて引き継ぎ、libco は適切な時間に実行を再開するようにコルーチンをスケジュールします。
数千万のコルーチンのサポート
デフォルトでは、libco は各コルーチンが独自の実行スタックを持つことを許可します。コルーチンの作成時に、固定サイズのメモリがコルーチンの実行スタックとしてヒープ メモリから割り当てられます。フロントエンドでアクセス接続を処理するためにコルーチンを使用する場合、大規模なアクセス サービスの場合、サービスの同時実行制限はメモリによって簡単に制限されてしまいます。この目的のために、libco はスタックレス コルーチン共有スタック モードも提供します。これにより、複数のコルーチンが同じ実行スタックを共有するように設定できるようになります。同じ共有スタック内のコルーチン間を切り替える場合、現在実行中のスタックの内容をコルーチンのプライベート メモリにコピーする必要があります。このようなメモリ コピーの数を減らすために、共有スタックのメモリ コピーは、異なるコルーチン間で切り替えるときにのみ発生します。共有スタックの占有者が変更されていない場合、実行中のスタックをコピーする必要はありません。
libco コルーチンの共有コルーチン スタック モードを使用すると、十分なコルーチンを作成するだけで、単一のマシンで数千万の接続に簡単にアクセスできます。 libco 共有スタック モードを通じて 1,000 万個のコルーチン (E5-2670 v3 @ 2.30GHz * 2、128G メモリ) を作成すると、各 100,000 個のコルーチンが 128k メモリを使用し、安定したエコー サービス全体の合計メモリ消費量は約 66G になります。
コルーチンのプライベート変数
マルチプロセス プログラムがマルチスレッド プログラムに変換されるとき、__thread を使用してグローバル変数をすばやく変更できます。コルーチン環境では、コルーチン変数 ROUTINE_VAR を作成しました。これにより、コルーチンのワークロードが大幅に簡素化されます。コルーチン変換。
コルーチンは基本的にスレッド内でシリアルに実行されるため、スレッドのプライベート変数を定義すると、再入性の問題が発生する可能性があります。たとえば、__thread というスレッド プライベート変数を定義する場合、当初は各実行ロジックがこの変数に排他的にアクセスできるようにしたいと考えていました。しかし、実行環境をコルーチンに移行すると、同じスレッドのプライベート変数が複数のコルーチンで操作される可能性があり、変数侵入の問題が発生します。このため、libco の非同期変換を実行するときに、ほとんどのスレッド プライベート変数をコルーチン レベルのプライベート変数に変更しました。コルーチンのプライベート変数には次の特性があります。コードがマルチスレッドの非コルーチン環境で実行されている場合、変数はスレッド プライベートです。コードがコルーチン環境で実行されている場合、この変数はコルーチン プライベートです。基礎となるコルーチンのプライベート変数は、実行環境を自動的に決定し、必要な値を正しく返します。
コルーチンのプライベート変数は、既存の環境を同期から非同期に変える上で決定的な役割を果たします。同時に、たった 1 行の宣言コードで済む非常にシンプルで便利なコルーチンのプライベート変数の定義メソッドを定義しました。 。
gethostbynameのフックメソッド
既存のネットワークサービスの場合、システムのgethostbyname APIインターフェイスを通じて実際のアドレスを取得するためにDNSにクエリを実行する必要がある場合があります。コルーチンの変換中に、フックのソケット ファミリ関数が gethostbyname に適用できないことがわかりました。コルーチンが gethostbyname を呼び出すと、結果が同期的に待機されるため、同じスレッド内の他のコルーチンの実行が遅延します。 glibc の gethostbyname ソース コードを調べたところ、フックが有効にならないことがわかりました。これは主に、glibc が一般的な poll メソッドではなく、内部でイベントを待機する __poll メソッドを定義しているためであり、同時に glibc はスレッド プライベート変数も定義しているためです。異なるコルーチンで使用される可能性があるため、切り替えにより再入が発生し、データが不正確になる可能性があります。最後に、gethostbyname コルーチンの非同期化は、Hook __poll メソッドとコルーチンのプライベート変数の定義によって解決されます。
Gethostbyname は、glibc によって提供される同期クエリ DNS インターフェイスです。業界には gethostbyname の優れた非同期ソリューションが多数ありますが、これらの実装にはサードパーティ ライブラリの導入が必要であり、非同期コールバック通知メカニズムを提供するために基礎となる層が必要です。 libco はフックメソッドにより、glibc ソースコードを変更することなく gethostbyname の非同期化を実現します。
コルーチンセマフォ
マルチスレッド環境では、スレッド間の同期要件が発生します。たとえば、あるスレッドの実行は別のスレッドのシグナルを待つ必要がありますが、この要件を解決するには通常 pthread_signal を使用します。 。 libco では、コルーチン間の同時実行要件を処理するために、コルーチン セマフォ co_signal を定義します。コルーチンは、待機中のコルーチンに通知するか、co_cond_signal および co_cond_broadcast を通じて待機中のすべてのコルーチンを起動するかを決定できます。
概要
Libco は、完全なコルーチン プログラミング インターフェイス、一般的に使用されるソケット ファミリ関数フックなどを提供する効率的な c/c++ コルーチン ライブラリであり、企業が同期プログラミング モデルを使用して迅速な反復開発を行えるようにします。過去数年間の安定した運用により、libco は WeChat のバックエンド フレームワークの基礎として極めて重要な役割を果たしてきました。