私は通常、過去にデータベースシーケンスを使用してシーケンス番号生成を実装しました。
例:Postgres SERIALタイプの使用http://www.neilconway.org/docs/sequences/
データベースがない大規模な分散システム用のシーケンス番号を生成する方法として私は興味があります。複数のクライアントに対してスレッドセーフな方法でシーケンス番号を生成するためのベストプラクティスの経験や提案はありますか?
私は通常、過去にデータベースシーケンスを使用してシーケンス番号生成を実装しました。
例:Postgres SERIALタイプの使用http://www.neilconway.org/docs/sequences/
データベースがない大規模な分散システム用のシーケンス番号を生成する方法として私は興味があります。複数のクライアントに対してスレッドセーフな方法でシーケンス番号を生成するためのベストプラクティスの経験や提案はありますか?
回答:
はい、これは非常に古い質問です。私が最初に目にするのはこの質問です。
特定の基準(通常は生成時間)によって(オプションで)緩やかにソートできるシーケンス番号と一意のIDを区別する必要があります。真のシーケンス番号は、他のすべてのワーカーが行ったことの知識を暗示しているため、共有状態が必要です。これを分散した大規模な方法で簡単に行う方法はありません。ネットワークブロードキャスト、各ワーカーのウィンドウ範囲、一意のワーカーIDの分散ハッシュテーブルなどを調べることもできますが、これは大変な作業です。
一意のIDは別の問題です。分散IDで一意のIDを生成するには、いくつかの優れた方法があります。
a)TwitterのSnowflake IDネットワークサービスを使用できます。スノーフレークは:
b)UUIDとSnowflakeのIDの作成方法から派生したアプローチを使用して、クライアント自体で一意のIDを生成できます。複数のオプションがありますが、次のようなものがあります。
最上位の40ビット程度:タイムスタンプ。IDの生成時間。(タイムスタンプに最上位ビットを使用して、IDを生成時間でソートできるようにしています。)
次の14ビット程度:ジェネレータごとのカウンタ。各ジェネレータは、新しいIDが生成されるたびに1ずつ増分します。これにより、同時に生成されたID(同じタイムスタンプ)が重複しないようになります。
最後の10ビット程度:各ジェネレーターの一意の値。この値を使用すると、すべてのジェネレーターが重複しないIDを生成するため、これを使用して、ジェネレーター間で同期を行う必要はありません(これは非常に困難です)。
c)タイムスタンプとランダムな値だけを使用して、クライアントでIDを生成できます。これにより、すべてのジェネレータを知って、各ジェネレータに一意の値を割り当てる必要がなくなります。反対に、そのようなIDはグローバルに一意であるとは限りません。一意である可能性が非常に高いだけです。(衝突するためには、1つ以上のジェネレーターがまったく同じランダム値を同時に作成する必要があります。)次のようなもの:
d)簡単な方法は、UUID / GUIDを使用することです。
twitter/snowflake
メンテナンスされなくなりました
今より多くのオプションがあります。
この質問は「古い」のですが、私はここにたどり着いたので、私が知っている(これまでのところ)オプションを残しておくと便利だと思います。
乾杯
各ノードに一意のID(いずれにせよ)を持たせ、それをシーケンス番号の前に付加することができます。
たとえば、ノード1はシーケンス001-00001 001-00002 001-00003などを生成し、ノード5は005-00001 005-00002を生成します
ユニーク:-)
あるいは、ある種の集中システムが必要な場合は、シーケンスサーバーをブロックで提供することを検討できます。これにより、オーバーヘッドが大幅に削減されます。たとえば、割り当てる必要があるIDごとに中央サーバーから新しいIDを要求する代わりに、中央サーバーから10,000のブロックでIDを要求し、不足したときに別のネットワーク要求を実行するだけで済みます。
これはRedissonで実行できます。分散型でスケーラブルなバージョンのを実装していAtomicLong
ます。次に例を示します。
Config config = new Config();
config.addAddress("some.server.com:8291");
Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
それが本当に一意でなく、グローバルにシーケンシャルである必要がある場合は、これらの数値を分配するための単一の単純なサービスを作成することを検討します。
分散システムは、相互作用する小さなサービスの多くに依存しています。この単純な種類のタスクについて、他の複雑な分散ソリューションが本当に必要ですか、それとも本当にメリットがありますか?
いくつかの戦略があります。しかし、私が知っているものはどれも実際に配布され、実際のシーケンスを与えることができません。
memcached
高速アトミックカウンターを備えています。ほとんどの場合、クラスター全体に対して十分な速度です。個人的には、UUID、またはほとんど連続したスペースが必要な場合はmemcachedを使用します。
(スレッドセーフな)UUIDジェネレーターを使用しないのはなぜですか?
私はおそらくこれを拡張する必要があります。
UUIDはグローバルに一意であることが保証されています(乱数に基づくものを避け、一意性が非常に高い場合)。
各UUIDのグローバルな一意性により、使用するUUIDジェネレーターの数に関係なく、「分散」要件が満たされます。
「スレッドセーフ」UUIDジェネレーターを選択すると、「スレッドセーフ」要件を満たすことができます。
「シーケンス番号」要件は、各UUIDの保証されたグローバルな一意性によって満たされると想定されています。
多くのデータベースシーケンス番号の実装(Oracleなど)では、(「接続」ごとに)単調に増加する、または(偶数)増加するシーケンス番号を保証しないことに注意してください。これは、シーケンス番号の連続したバッチが接続ごとに「キャッシュされた」ブロックに割り当てられるためです。これにより、グローバルな一意性が保証され、適切な速度が維持されます。しかし、実際に(時間とともに)割り当てられたシーケンス番号は、複数の接続によって割り当てられている場合、ごちゃまぜになる可能性があります。
これは古い質問であることはわかっていますが、私たちも同じニーズに直面しており、私たちのニーズを満たすソリューションを見つけることができませんでした。私たちの要件は、IDの一意のシーケンス(0、1、2、3 ... n)を取得することでしたので、スノーフレークは役に立ちませんでした。Redisを使用してIDを生成する独自のシステムを作成しました。Redisはシングルスレッドであるため、そのリスト/キューメカニズムにより、常に一度に1つのポップが提供されます。
私たちがしていることは、IDのバッファーを作成することです。最初、キューには0〜20個のIDがあり、要求されたときにディスパッチする準備ができています。複数のクライアントがIDを要求でき、redisは一度に1つのIDをポップします。左からポップするたびに、右側にBUFFER + currentIdを挿入します。これにより、バッファーリストが継続します。ここでの実装
データベースを使用すると、1つのコアで1秒あたり1.000以上の増分に到達できます。とても簡単です。独自のデータベースをバックエンドとして使用して、その数を生成できます(DDDの用語では、独自の集計である必要があります)。
似たような問題がありました。いくつかのパーティションがあり、それぞれのオフセットカウンターを取得したいと考えました。私はこのようなものを実装しました:
CREATE DATABASE example;
USE example;
CREATE TABLE offsets (partition INTEGER, offset LONG, PRIMARY KEY (partition));
INSERT offsets VALUES (1,0);
次に、次のステートメントを実行しました。
SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+1 WHERE partition=1;
アプリケーションで許可されている場合は、一度にブロックを割り当てることができます(これは私の場合です)。
SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+100 WHERE partition=1;
さらにスループットが必要な場合は、事前にオフセットを割り当てることができません。リアルタイム処理にFlinkを使用して独自のサービスを実装できます。パーティションごとに約100Kの増分を得ることができました。
それが役に立てば幸い!
問題は次のようなものです。iscsiの世界では、各LUN /ボリュームは、クライアント側で実行されているイニシエーターによって一意に識別可能でなければなりません。iscsi規格では、最初の数ビットはストレージプロバイダー/メーカーの情報を表す必要があり、残りは単調に増加していると述べています。
同様に、ノードの分散システムで初期ビットを使用してnodeIDを表すことができ、残りは単調増加します。