DUICUO

Meituan Leaf IDジェネレータオープンソースソリューションの詳細な理解

みなさんこんにちは。Brother Tree です。

以前、「分散 ID ジェネレーターの設計方法」について説明しましたが、これには 4 つのソリューションが含まれていました。

  • UUID
  • スノーフレークのようなアルゴリズム
  • データベースの自動増分主キー
  • Redis アトミックインクリメント

Meituanはソリューション2と3をベースに、分散ID生成ソリューション「Leaf」を開発し、オープンソース化しました。このプロジェクトのソースコードとドキュメントはGitHubで公開されています:Meituan-Dianping/Leaf: 分散ID生成サービス。

本日は、Leaf の設計理念を学び、大企業が大規模なミドルウェアをどのように設計しているかを見学することで、私たち自身のシステム設計能力をさらに向上させていきたいと思います。

データベースの自動増分主キー

「分散IDジェネレータの設計方法」の記事では、データベースの自動増分主キーに基づいてIDジェネレータを設計できることを説明しました。しかし、このアプローチには以下の2つの問題があることも指摘しました。

  • パフォーマンスを向上させる唯一の方法はマシン数を増やすことです。しかし、リクエスト数が再び増加した場合、マシン数を無制限に増やすことしかできず、これは一種の物理的な防御策のように思えます。
  • 水平スケーリングは困難です。マシンの追加は非常に面倒なプロセスです。まず、新しいサーバーをデプロイし、新しいステップサイズを設定し、最初は達成不可能な値を設定する必要があります。新しいサーバーをデプロイした後、古いサーバーを1台ずつ処理しなければなりません。これは非常に骨の折れる作業であり、実質的に手作業によるメンテナンスが必要になります。

簡単に言えば、データベース内の自動増分主キーに基づいており、その数値生成効率は単一の物理マシンでは制限されます。低いQPSではサポートできますが、高いQPSではサポートできません。ステップサイズを設計することでマシンの数を増やすことはできますが、メンテナンスコストは非常に高くなります。

このようなビジネス環境を踏まえ、MeituanはオープンソースプラットフォームであるLeafをさらに最適化し、データベースとビジネスサービスの間にミドルウェア層を追加しました。以前は、ビジネスシステムはデータベースに直接データを要求していましたが、現在はデータベースに直接要求するのではなく、Leafミドルウェア層にデータを要求し、Leafミドルウェア層がデータベースからデータを取得します。

Leafのミドルウェアは、データベースからIDを取得する際に、単一のIDを取得するのではなく、複数のID番号範囲を一括して取得します。サービス全体の具体的な処理フローは以下のとおりです。

リーフセグメント処理フロー - Meituan Technology Team Blogより画像

上図に示すように、グリーンビジネスシステムがリーフID発行サービスにリクエストを送信する際、ビジネスコードマーカーが付与されます。リーフID発行サービスは、ビジネスIDの種類に基づいて、次の一意のIDを返します。リーフID発行サービスは、そのたびにデータベースから1000個のIDを取得し、データベースはテーブルにレコードを挿入します。このレコードは、これらの1000個のIDが特定のビジネスに割り当てられたことを示します。

これにより、従来は業務システムから1,000個のIDを取得するために1,000回のデータリクエストが必要だったものが、1回のリクエストで済むようになり、業務効率が大幅に向上します。さらに、業務システムはメモリからIDを取得するため、第三者にリクエストする必要がなくなり、レスポンスタイムも大幅に向上します。

上記の方法はデータベース層の逼迫問題を解決しますが、いくつかの問題も抱えています。例えば、ID番号の範囲が使い果たされると、データベースへのリクエストを実行する必要があり、リクエストにかかる時間が急激に増加し、システム監視のレイテンシが急増します。

この問題に対処するため、Meituan Leafは「デュアルバッファ+プリロード」戦略を採用しました。これは、メモリ内に2つのIDセグメントを保持し、最初のIDセグメントの使用率が10%に達した時点でプリロードするというものです。これは、アプリ内のウォーターフォールレイアウトで使用されるプリロードメカニズムに似ており、基本的な考え方は同じです。設計コンセプトを下図に示します。

ダブルバッファ + プリロード - Meituan Technology Team Blog からの画像

Leafはプリロードにより、アクセス急増の問題に対処し、データベースのダウンタイム中に一定の高可用性を提供します。データベースに障害が発生した場合でも、短期間で復旧できれば、Leafは一定期間サービスを提供し続けることができます。

公式ブログによると、このソリューションはリリースから6か月後に別の問題に直面しました。ID範囲は固定されていたものの、トラフィックは固定されていなかったのです。トラフィックが10倍に増加しても、持続時間は非常に短く、データベースに大きな負荷がかかる可能性があることが判明しました。

この問題に対処するため、動的番号セグメント方式が採用されました。つまり、次に配布される番号セグメントの長さは、前のセグメントの長さと配布時間に基づいて計算されます。番号の長さの計算ルールは以下のとおりです。

  • T < 15分、次のステップ = ステップ * 2
  • 15分 < T < 30分、nextStep = ステップ
  • T > 30分、nextStep = ステップ / 2

簡単に言うと、所要時間が15分未満の場合、次に発行される番号区間の長さが2倍になります。15分から30分の場合、次に発行される番号区間の長さは変わりません。所要時間が30分を超える場合、次に発行される番号区間の長さは半分になります。

この設計アプローチは、実際には、ロックを取得するときに Hotspot 仮想マシンで使用される適応型スピン ロックに多少似ています。おそらく、これを「適応型長さ」と呼ぶことができるでしょう。

Leaf が行ったすべての最適化にもかかわらず、次のような厳しい環境ではシステム障害が発生する可能性があります。

  • プライマリ データベースが突然クラッシュし、短時間で回復できず、システムが使用できなくなります。
  • ビジネス トラフィックが数十倍に急増し、バッチ分散を使用しても、単一のデータベース マシンではこのような大量の書き込み要求をサポートできませんでした。

このようなシナリオでも、システムは依然として高可用性を保証することはできません。根本的な原因は、Leafがデータベースに強く依存していることです。そのため、高可用性の保証はデータベースレベルで実装する必要があります。

Leaf公式ブログで概説されているアプローチに従い、Leafは現在、準同期方式でデータを同期し、マスター・スレーブ間のフェイルオーバーを実装しています。これにより、マスターデータベースのダウンタイムの問題が解決され、高可用性がさらに向上します。

MySQLの準同期レプリケーションは、Kafkaのレプリカ同期に似ています。トランザクションをコミットする前にスレーブがバイナリログを受信する必要があるため、ある程度の一貫性が確保され、数値範囲の再送信リスクが軽減されます。

しかし、準同期方式では強力なデータ整合性を保証できないため、極端なケースでは、たとえリスクは低いとはいえ、セグメントの再送信が発生するリスクが依然として存在します。完全な強力な整合性が必要な場合は、MySQLのグループレプリケーション機能を検討する必要があります。ただし、Meituanの内部データベースの強力な整合性機能はまだ開発中であるため、Leafではまだ強力なデータ整合性を実装していません。

スノーフレークのようなアルゴリズム

「分散IDジェネレータの設計方法」の記事で、Snowflakeアルゴリズムは優れた分散ID生成アルゴリズムであると述べました。しかし、このアルゴリズムにはクロックロールバック問題という欠点があります。MeituanのオープンソースソフトウェアLeafも、Snowflakeアルゴリズムに基づく分散ID生成機能を実装し、クロックロールバック問題を解決しています。

Meituan Leafは、クロックロールバック問題を解決するためにZooKeeperを導入しました。基本的な考え方は、各Leafインスタンスが定期的にZooKeeperにタイムスタンプを報告するというものです。Leafサービスは起動するたびに、まずローカルタイムを最後のID送信時刻と比較し、次にZooKeeper内のすべてのノードの平均タ​​イムスタンプと比較します。いずれかの段階で異常が見られた場合、起動失敗アラートがトリガーされます。

この解決策は比較的理解しやすく、最後のID発行時刻を他のマシンの平均時刻と比較します。タイムロールバックの問題に加え、マシンの数が増加すると、スノーフレークアルゴリズムにおけるワーカーIDの維持が困難になります。

そのため、Leaf も ZooKeeper をミドルウェアとして使用し、各サーバーの IP アドレスとポートをキーとしてノードを登録し、workerId として整数ノードを取得します。

要約

Meituan のオープンソース Leaf は、2 つの ID 生成方法を提供します。

  • 番号セグメントモード。データベースの自動インクリメントコンポーネントに基づいて、IDは低い数字から徐々に増加し、MySQLの短期的な利用不能状態を許容します。
  • スノーフレークアルゴリズムに似たパターン。スノーフレークアルゴリズムに基づいて、タイムロールバックの課題や、多数のマシンのworkIdの維持といった問題を解決します。

番号セグメントモードでは、従来の自動増分IDにプロキシモードを追加することで、番号セグメントモードを提案しています。次に、「ダブルバッファ+プリロード」アプローチを採用することで、トラフィックスパイクの問題に対処しています。さらに、トラフィックの急増に対応するために、適応型番号セグメント長の最適化アプローチを採用しています。最後に、データベースの高可用性を実現するために、MySQLの準同期レプリケーション+マスタースレーブフェイルオーバーを採用し、ある程度の高可用性を確保しています。

スノーフレーク型アルゴリズムパターンでは、多数のマシンのワーカーIDを生成する方法としてZooKeeperが導入されています。次に、タイムスタンプの問題は、「タイムスタンプをローカルに保存し、定期的にタイムスタンプを報告する」という組み合わせによって解決されます。

さて、今日のシェアはこれで終わりです。