100 万 QPS システムを設計するにはどうすればよいですか?

1. リレーションシップチェーンビジネスの紹介

マスター ステーションのビジネスの観点から見ると、関係チェーンは、ユーザー A とユーザー B の間の次の関係を指します。フォロー(サブスク)を中心にフォロー属性によって細分化されており、ブロック、無言フォロー、相互フォロー、特殊フォローなど様々な属性や状態が含まれます。現在、マスター ステーションの関係チェーンの規模は比較的大きく、比較的急速な速度で成長し続けています。プラットフォーム ベースのビジネスとして、リレーションシップ チェーン サービスは、1 対多のリレーションシップ ポイント検索、完全なリレーションシップ リスト、リレーションシップのカウントなどの基本的なクエリを提供します。包括的なクエリのピーク QPS はほぼ 100 万であり、コアによって依存されています。ダイナミクスやコメントなどのビジネス。

データ量とクエリリクエストが継続的に増加する傾向にある中、リアルタイムで正確なデータを確保し、高いサービス可用性を維持することがリレーションシップチェーンアーキテクチャの進化の中核目標となっています。

写真

(図:スペースページにおけるリレーションシップチェーンのビジネスシナリオ)

写真

(図: リレーションシップ チェーン ステート マシン)

2. トランザクションのボトルネック - ストレージの進化

リレーショナルデータベース

当該書き込みイベントは純粋なステータス属性の転送に相当するため、リレーショナル データベースの使用に非常に適しています。メイン サイト コミュニティの開発の初期段階では、リレーションシップ チェーンの規模は小さく、mysql を直接使用することには当然の利点があります。開発とメンテナンスが簡単で、ロジックが明確であり、必要なのはリレーションシップ テーブルとオンライン利用に対応したカウントテーブル。コミュニティの開発速度を考慮して、以前の設計では圧力を分散するために 500 のテーブル (関係テーブル) と 50 のテーブル (計数テーブル) を使用していました。

写真

(図:リレーショナルテーブルの構造例)

写真

(図:カウントテーブルの構成例)

このストレージ構造では、追加中 FID リクエストを例として、mysql はトランザクションで次の操作を実行する必要があります。

  • Mid のカウントをクエリしてロックし、fid のカウントをクエリしてロックします。

  • Mid->fid の関係をクエリしてロックし、fid->mid の関係をクエリしてロックします。

  • ステートマシンによれば、メモリ内の新しい関係を計算した後、mid->fid 間の関係を変更し、fid->mid 間の関係を変更します (一方向の注意から相互の関係に変更するなど)。

  • Mid のフォロワー数と fid のファンの数を変更します。

このアーキテクチャは 2021 年まで維持されます。コミュニティの継続的な発展に伴い、このアーキテクチャの欠点がますます明らかになってきています。一方で、テーブルが分割されていても、リレーションシップ チェーン データの全体的なサイズは依然として超過しています。推奨される全体的なストレージ容量 (現在 TB レベル); 一方、トランザクションが重いと、mysql は高い書き込みトラフィックをサポートできなくなります。元の同期書き込みアーキテクチャでは、パフォーマンスはアテンション失敗率が高くなります。単純にアップグレードした場合、非同期書き込みアーキテクチャでは、パフォーマンスはメッセージのバックログになります。メッセージのバックログが一時キャッシュの有効期間よりも長く続くと、顧客からの苦情が発生し、症状の治療法にはなりません。

写真

(図: mysqlをコアストレージとして使用する「同期」書き込み関係のフローチャート)

KVストレージ

※Bilibili自社開発の分散KVストレージの紹介については、Bilibili分散KVストレージ実践編をご参照ください。

最終的に使用することを決定したアップグレード計画は、データ ストレージを mysql から KV ストレージに移行し、論理的に「mysql の同期書き込み」を「KV の非同期書き込み」に変更します。「KV を同期的に書き込む」を選択しない理由は、1 つのリレーションシップが複数の KV レコードに対応し、KV がトランザクションをサポートしていないためですが、他方、非同期アーキテクチャは、起こり得る多数の瞬間的なアテンション リクエストに耐えることができます。 。mysql binlog を購読しているビジネスと互換性を持たせるために、「KV を非同期で書き込む」後、「mysql を非同期で書き込む」ことになります。

新しいアーキテクチャでは、各ユーザーのアテンション リクエストに対して、データバスの配信は成功したリクエストとみなされ、mysql binlog はリアルタイムに敏感でない一部のビジネス パーティ (データ プラットフォームなど) にのみ提供されます。わずかなバックログを気にする必要がありますが、リアルタイム要件が高いビジネス パーティの場合は、非同期書き込み KV イベントを処理した後に、これらのビジネス サブスクリプションのデータバスを提供します。

写真

(図: KV をコアストレージとして使用し、mysql 冗長ストレージを使用した非同期書き込み関係のフローチャート)

KV ストレージの最大の利点は、最下層が冗長な mysql カウント テーブルを置き換えるカウント メソッドを提供できることです。この利点は、関係を保存するだけの KV テーブルを維持するだけでよいことです。私たちが設計したストレージ構造は次のとおりです。

  • キーは{attr|mid} fid、attr はリレーションシップ ジッパー タイプ、mid と fid は両方ともユーザー ID を表し、{attr|mid} は attr と Mid をハッシュとして結合することを表し、ハッシュの下にある複数の fid は辞書編集形式で保存されます。順序、結合 KV サービスによって提供されるジッパー トラバーサル メソッド (スキャン) は、ハッシュの下のすべての fid を取得できます。

  • value は、attribute (関係属性) と mtime (変更時間) を含む構造体です。

attr とattribute は混同しやすいですが、2 つの違いは次のとおりです。

  • キー内のattrはリレーションシップのzipper型で、順方向リレーションシップ3種類、逆方向リレーションシップ2種類の合計5種類があります。 ATTR_WHISPERはクラスに静かに従うことを意味します(mid静かにフォローされるfid)、ATTR_FOLLOWはクラスに従うことを意味します(mid の後に fid) 、ATTR_BLACK は黒 (mid の黒化された fid) を意味し、ATTR_WHISPERED は静かに続くことを意味します (mid の後に静かに fid が続きます)、ATTR_FOLLOWED はフォローされることを意味します (mid の後に fid が続きます)。ユーザーにとって、さまざまなリストと関係チェーン タイプの間のマッピング関係は次のとおりです。

  • ウォッチ リスト: さまざまな製品要件に従って、ほとんどの場合、フォローアップ関係チェーン (attr=ATTR_FOLLOW) を指しますが、一部のシナリオでは、サイレント フォローアップ関係チェーン (attr=ATTR_WHISPER) も追加されます。

  • ファン リスト: 静かにフォローされる関係チェーン (attr=ATTR_WHISPERED) とフォローされる関係チェーン (attr=ATTR_FOLLOWED) のコレクション。

  • ブラックリスト リスト: ブラック関係チェーンをプルします (attr=ATTR_BLACK)。

  • value の属性は現在の関係属性を示し、WHISPER は静かに従う、FOLLOW はフォローする、FRIEND は相互フォロー、BLACK は黒を引っ張るという意味の合計 4 種類があります。これを前の属性と混同しやすいですが、それらの間の完全なマッピング関係は次のとおりです。

  • attr=ATTR_WHISPER または ATTR_WHISPERED の下には、attribute=WHISPER を指定できます。

  • Attr=ATTR_FOLLOW または ATTR_FOLLOWED には、attribute=FOLLOW または FRIEND を指定できます。

  • attr=ATTR_BLACK の下には、attribute=BLACK を指定できます。

MidA の 5 つのリレーションシップ ジッパーを図に示します。

写真

(写真:midAの5つの関係ジッパー)

要約すると、KV ストレージにアップグレードした後の読み取り操作はそれほど複雑ではありません。

  • Mid と fid の間の関係を順方向にクエリしたい場合は、(get、batch_get) をチェックして 3 種類の順方向属性を走査するだけです。

  • アテンション関係とブラックリストの全量をクエリしたい場合は、対応する属性を見つけてそれぞれスキャンを実行するだけで済みます。

  • ユーザー数をクエリする場合は、count に対応する属性のみが必要です。

もう少し複雑なロジックは、リレーションの記述にあります。

  • MySQL にはアトミック性を確保するためのトランザクションがありますが、kv ストレージはトランザクションをサポートしていません。ユーザーのリクエストに関する限り、データバスの配信は成功とみなされますので、このメッセージを非同期で処理する場合は、書き込みの 100% 成功を保証する必要があるため、非同期メッセージを処理する場合は、無限リトライ用のロジックを追加します。失敗について。

  • 極端な場合には、書き込み競合の問題が発生することもあります。たとえば、ある時点で、ユーザー A がユーザー B をフォローし、同時にユーザー B がユーザー A をフォローすると、予期しないデータ エラーが発生する可能性があります。 (一方向の注意と相互相関は 2 つの異なる特性であり、どちらかの当事者の注意の行動がこの特性に影響するため)。この状況を回避するために、メッセージ キュー内の同じキーの下にあるデータの順序性を利用し、同じユーザーのペアにキーが割り当てられるようにすることで、同じユーザーのペアの操作が保証されます。秩序ある方法で実行されます。

さらに、各アテンション イベントに対して、mid の fid にアテンションを追加する例を取り上げます。

  • ジョブは最初に肯定的なフォロー関係を設定し、次に上限を確認する必要があります。上限を超えた場合はロールバックして終了します。

  • 次に、mid のフォロー関係 (attr=ATTR_WHISPERED)、fid のフォロー関係 (attr=ATTR_FOLLOW、一方向のアテンションから相互関係への変更など) など、このアテンション アクションによって影響を受ける他のすべての逆属性をバッチに配置します。

  • 上記の put 操作のいずれかが失敗した場合は、再試行する必要があります。これらの操作が完了するまで、次のイベントは成功したとみなされます。

  • データバスをポストして、加入者に懸念イベントが発生したことを通知します。

  • mysql イベントを非同期的に書き込み、関連するイベントを mysql と同期して、サブスクライバーが使用できるバイナリログを生成します。

3. 急速な成長 - キャッシュの反復

ストレージ層キャッシュ memcached

オンライン クエリ リクエストの一定の割合は、完全なウォッチリストと完全なブラックリストをクエリするためのものです。前節で述べたように、関係チェーン数を重複して保存しないようにするために、KV のストレージ設計は非常に特殊であり、ユーザーの肯定的なフォロー関係は 3 つの異なる属性 (つまり、3 つの異なる関係ジッパー) に分散されます。KV ストレージからユーザーの完全な関係リストを取得したい場合は、3 つの肯定的な関係ジッパーの循環スキャンを同時に実行する必要があります (各スキャンには上限があるため)。スキャン方法は比較的貧弱なので、KV ストレージの上位層に一連のキャッシュが追加され、ソースへのリターン率を下げることでスキャン QPS が厳密に制御されます。

memcached は大きなキーに対してパフォーマンスが優れていることを考慮して、前任者は KV ストレージの上位層に memcached キャッシュを追加して、ユーザーの完全な関係リストを保存しました。具体的なビジネス プロセスは次のとおりです。

写真

(図: 完全な関係リストのクエリ ビジネス プロセス)

ピーク時のキャッシュ バックトゥソース データから判断すると、memcached は KV ストレージに対するリクエストの 97% ~ 99% に耐えることができ、キャッシュをミスするのは 6K 未満の QPS だけであり、その効果は明ら​​かです。

写真

写真

(図: memcached QPS とキャッシュ バックトゥソース レート)

クエリレイヤーキャッシュハッシュ

フォロー リストのリクエストに加えて、リクエストの大部分は 1 対多のポイントチェック関係 (ユーザーと 1 人以上の他のユーザーの間の関係をクエリする) です。毎回 memcached を実行してメモリ内の交差を取得すると、ネットワークのオーバーヘッドが非常に大きくなるため、このクエリ シナリオでは、列挙に適したキャッシュのセットを設計する必要もあります。

アクティブ ユーザーのフォロワー数は通常、数十から数百の範囲にあります。ポイント チェックに使用されるキャッシュは厳密に順序付けされている必要はありませんが、指定されたハッシュキー、redis ハッシュ、および hget、hmget、それによって提供される hset、hmset メソッドは、このシーンに非常に適しています。したがって、クエリ レイヤーのキャッシュ設計は次のようになります。キーはmid、ハッシュキーはmidと関係を持つ各ユーザーのID、値はその関係データであり、以前にmidAによってKVに保存されたデータに対応します。 :

写真

(図:redisハッシュキャッシュにおける懸念されるストレージ構造の関係)

ハッシュには、midA のすべてのポジティブ アテンション関係が格納されているため、キャッシュ ミスをソースに返す必要がある場合、全量のアテンション関係を取得するために、以前の memcached と組み合わせて使用​​できます。 :

写真

(図: Redis ハッシュ アーキテクチャにおける 1 対多のリレーションシップ クエリ ビジネス プロセス)

この一連のキャッシュに基づいて、1 対 1 および 1 対多のフォロー関係をチェックするための平均時間のかかるインターフェイスは基本的に 1 ミリ秒に維持され、ハッシュのヒット率は 70% ~ 75% に達する可能性があります。 100 万近くの QPS をサポートするのは比較的簡単で、Redis クラスターの水平拡張により、より多くのビジネス リクエストをサポートできます。

写真

写真

(図: Redis ハッシュ キャッシュの QPS とキャッシュ バックトゥソース レート)

クエリ レイヤー キャッシュ kv (失敗したように見える試み)

2022 年後半までに、一方で、この製品は「私がフォローしている xx も ta に注意を払う」という要件を提示します。この種の 2 次関係のクエリは、ハッシュ アーキテクチャの下では非常に困難です。

  • ハッシュには肯定的な関係クエリのみが保存されるため、最初に「I」のフォロー リストを取得し、次にフォロー リスト内の全員と ta の間のフォロー関係を横断してクエリする必要があります。

  • 「私」ウォッチリストの多くは非アクティブなユーザーであるため、ハッシュおよび memcached キャッシュにヒットするのは困難です。つまり、各リクエストはバッチ化されてソース KV ストレージに送り返されます。また、レコメンデーション側は、リレーションシップチェーンサービスの計算時間を非常に短くしておくこともできるため、今回リクエストがタイムアウトによりキャンセルされると、このリクエストに属するバックトゥソースKVストレージのスキャン操作はすべて終了します。イベント アラームは大量のアラーム ノイズをもたらします (タイムアウトしたリクエストが 1 つだけであっても、rpc エラーの量はそのリクエストでソースに送り返されたスキャンの数になるため)。

写真

(図:アーキテクチャ切り替え前後のKVストレージスキャン操作におけるRPCエラー数)

一方で、本製品は注目度の上限を解放するというアイデアを提案しており、そうした需要が立ち上がった後は注目度の高いユーザーが増え、一部のユーザーでも注目度がすぐに埋まってしまうと考えています。機能開始後のハッシュ構造 欠陥やリスクも日々明らかになります。リスクポイントは、同じ Redis インスタンスに、キャッシュを見逃したり、バックトゥソースをトリガーしたり、hmset がキャッシュをバックフィルしたりする関心の高いユーザーが複数いる場合、継続的な高い書き込み QPS によって Redis の CPU 使用率がフルになる可能性があることです (たとえば、 、毎秒 2 人のユーザーがキャッシュをバックフィルする必要があり、その関係リストは 5000、実際の書き込み QPS は 10,000)。

上記の背景の下、チーム内での議論の後、単純なキャッシュを通じてハッシュを直接置き換えることを期待して、最初に redis kv 構造キャッシュを導入しました。キーはユーザー A とユーザー B のユーザー ID、値はユーザーですA とユーザー B の関係の例は次のとおりです。

写真

(図: Redis KV キャッシュにおける関係のあるストレージ構造)

このキャッシュ構造では、ソース KV ストレージをチェックするだけで済みます。これは、KV ストレージ チェック操作 (get、batch_get) のパフォーマンスがスキャン操作よりもはるかに優れているためです。また、redis kv が実行される場合、memcached への依存を減らすためです。キャッシュ ミスが発生した場合、ソース KV ストレージに直接戻ってポイント チェック (get、batch_get) を実行し、キャッシュをバックフィルします。フローチャートは次のとおりです。

写真

(図: Redis KV アーキテクチャにおける 1 対多関係のクエリ ビジネス プロセス)

2% のユーザーをグレースケールしたところ、kv 構造キャッシュのヒット率が徐々に 60% に収束し、キャッシュ メモリの使用率とキーの数が予想をはるかに上回っていることがわかりました。これは、リクエストの 40% がキャッシュをミスしてソースに戻ることを意味します。これは、数百万の QPS のプレッシャーの下では明らかに受け入れられません。miss によってキャッシュされたリクエストを分析した結果、主なビジネスのソースはコメントであり、ほとんどのリクエストは「関係なし」を返します。つまり、コメント シーンは多数の見知らぬ人の注目関係をクエリすることになるため、多くの空の監視員が存在し、そのほとんどは関連性がありません。2 回アクセスされます (ユーザーにとって、空の監視員の数は、そのユーザーが読んだコメント ユーザーの数とみなすことができます)。これも合理的な説明になります。単一の KV 構造キャッシュのパフォーマンスのため。

クエリレイヤーキャッシュbloom_filter+kv

多数の空のセンチネル シーンの場合、その上にブルーム フィルターのレイヤーを配置することが認識されており、合理的な解決策です。ユーザーごとにブルーム フィルターを維持し、まずストック内のすべての関係チェーンをブルーム フィルターに追加し、新しい書き込み関係イベントを消費してブルーム フィルターを更新して常駐キャッシュ フィルター デバイスにすることにしました。ブルーム フィルターにヒットする可能性は 3 つあります。

  1. 今も関係あるよ

  2. 昔は関係あったけど今は大丈夫

  3. 関係はありませんでしたが、前の 2 つのケースでハッシュの衝突が発生しました

ブルーム フィルターにヒットしたものだけが下位レベルの kv キャッシュに移動し、空のセンチネルの問題のほとんどが解決されます。具体的なフローチャートは次のとおりです。

写真

(図: ブルームフィルター + Redis KV アーキテクチャの下での 1 対多関係のクエリ ビジネス プロセス)

現在、リレーションシップ チェーン シーンのトラフィックの 100% が Bloom の新しいアーキテクチャに切り替えられています。Bloom のヒット率は 80% 以上に達し、古いハッシュ アーキテクチャはオフラインになっています。この技術的変革は、リレーションシップ チェーンの上限 考えられる問題、2 次リレーションシップ アラーム ノイズ、「多対 1」逆クエリのサポートの困難、およびキャッシュ リソースの節約が期待されます。

4. リスクは近づいています - ホットスポット災害復旧

関係チェーンの主なシナリオは、「ユーザー A」と他のユーザーの間の次の関係をクエリすることです。同時刻のリクエストにおいて、「ユーザーA」のリクエストが分散すると、Redisへの負荷がクラスタ内の数十のインスタンスで分担されることになり、このときシステムが耐えられる最大の負荷は、クラスター内の各インスタンスの合計 ; 極端な場合、「ユーザー A」が少数のユーザーに集中している場合、圧力は少数の Redis インスタンスに集中し、バレル ショートボード効果は非常に明白になります。

昨年のあるホットなシーンに戻りますが、トラフィックは World Wide Web などの人気アップの動的な詳細ページや原稿再生ページに集中しており、これらのページはアップの所有者とアップの関係をリアルタイムでクエリすることに依存していました。多数のユーザーが同時にロードした場合、コメント時にアップマスターのクエリホットスポットが形成されます。

当時、リレーションシップ チェーン サービスの構造はホットスポットの処理が比較的遅れており、ホットスポット アップ マスターが見つかった (またはホットスポット アップ マスターが事前にわかっていた) 場合、ホットスポット リストに手動で設定されていました。ホットスポット ユーザーの場合、Redis をリクエストする前に、ローカル Localcache が最初にクエリされます (Localcache に保存されているアップ マスター関係リスト データは 10 秒ごとに更新されます)。この 10 秒以内にデータの不整合が発生する可能性がありますが、実際のビジネスの観点から見ると、ホット リクエストをトリガーするのは大きなアップ マスターであり、これらのアップ マスターの関係リストはほとんど変更されないため、ユーザー エクスペリエンスへの影響はほとんどありません。 . 影響を与えます。

その夜にホットスポット リクエストが発生すると、ユーザーの増加に伴い、Redis クラスターのいくつかのインスタンスの CPU 使用率が徐々に 70% を超え、一部のインスタンスでは 90% を超えました。

写真

(図: ホットスポット イベント時の Redis 単一インスタンスの CPU 使用率アラーム)

ホットスポット検出機能がないため、運用およびメンテナンス担当者は、アラームを確認した後、現在のホットスポット キーを手動で取得する必要があります (Redis インスタンスの CPU 使用率がほぼフルであるという前提の下で、インスタンスに直接接続してカウントする)キーは高リスクの操作です) を使用してストレージを手動で構成すると、Redis への負荷が急激に低下しました。起こり得るリスクを回避するために、フォローアップでは他の公式メディア アカウントが一時的に人気ユーザーのリストに追加され、リレーションシップ チェーン サービスはトラフィックのピーク時もリスクなく存続しました。

写真

(図: ローカル キャッシュの構成前後の単一インスタンス Redis QPS)

その後、ビジネス アーキテクチャは、ホットスポットを自動的にカウントし、アクセス後に設定されたしきい値に基づいてローカル キャッシュを一時的に使用できるホットスポット検出ツールを提供し、今年の初めに、ホットスポット検出ツールとローカル キャッシュ SDK が統合されました (*)ローカル キャッシュの例については、この記事を参照してください: Bilibili ダイナミック アウトボックス ローカル キャッシュの最適化)、ホットスポットの自動検出と自動ダウングレードがより便利になり、ビジネス側はローコードになるようにローカル キャッシュ タイプを変更するだけで済みます。アンチホットスポット機能。リーグ オブ レジェンド S12 とニューイヤー フェスティバルの検証後、上記の活動中、リレーションシップ チェーン サービスの指標は比較的安定していました。

写真

(写真: ある日の正午に自動的に検知されキャッシュされるホットキーの数を監視)

5. 長期計画 - 関係の拡大

リレーションシップ チェーンの機能を使用して上位レベルのビジネスに力を与える方法や、リレーションシップ チェーンの基本サービスの信頼性を高める方法も、引き続き検討する必要がある問題です。以下にいくつかの手順を示します。

  • ビジネスの実現: マルチテナント方式で、関係チェーン サービスの既存のコード セットを通じて、基本的な関係機能 (フォロー/購読、フォロー解除/購読、フォロー リスト、ファン リスト) を提供して、新しいビジネス システムに迅速にアクセスできます。二次発展を避けるため。

  • コミュニティに力を与える: 関係チェーンのプラットフォーム サービスをより一般化する方法。たとえば、動的フィード シーンで、ユーザーの汎サブスクリプション関係シーン (アップ マスターなど) を統合して、関係のオブジェクトを一般化することを試みることができます。 、コレクション、漫画、ドラマ、教室など)。

  • 安定性の向上: リレーションシップ チェーン サービスには多くのビジネス パーティが接続されていますが、0 トラストおよび 100% クォータ構成により、ビジネス間の相互干渉が回避され、特に通常のビジネス トラフィックの急増によるコア ビジネスへの影響が回避されます。

おすすめ

転載: blog.csdn.net/2301_78588786/article/details/132008456