web-dev-qa-db-ja.com

分散シーケンス番号の生成?

私は通常、過去にデータベースシーケンスを使用してシーケンス番号生成を実装しました。

例えばPostgres SERIALタイプの使用 http://www.neilconway.org/docs/sequences/

しかし、データベースがない大規模な分散システムのシーケンス番号を生成する方法については興味があります。複数のクライアントに対してスレッドセーフ方式でシーケンス番号生成を達成するためのベストプラクティスの経験や提案はありますか?

93
Jon

OK、これは非常に古い質問で、私が最初に見たものです。

シーケンス番号ユニークIDを区別する必要があります(オプション)特定の基準(通常は生成時間)で大まかにソートできます。真のシーケンス番号は、他のすべてのワーカーが行ったことの知識を意味するため、共有状態が必要です。分散した大規模な方法でこれを行う簡単な方法はありません。ネットワークブロードキャスト、各ワーカーのウィンドウ範囲、および 一意のワーカーIDの分散ハッシュテーブル などを調べることができますが、それは多くの作業です。

一意のIDは別の問題であり、分散IDで一意のIDを生成するいくつかの良い方法があります:

a)次を使用できます TwitterのSnowflake IDネットワークサービスSnowflakeは:

  • ネットワークサービス。つまり、一意のIDを取得するためにネットワーク呼び出しを行います。
  • 生成時間順に並べられた64ビットの一意のIDを生成します。
  • また、サービスは非常にスケーラブルであり、(潜在的に)高可用性です。各インスタンスは1秒あたり何千ものIDを生成でき、LAN/WANで複数のインスタンスを実行できます。
  • scalaで記述され、JVM上で実行されます。

b) how UUIDs およびSnowflakeのIDが作成されるアプローチを使用して、クライアント自体で一意のIDを生成できます。複数のオプションがありますが、次のようなものがあります:

  • 最上位の40ビットほど:タイムスタンプ;IDの生成時間。 (IDを生成時間でソートできるようにするために、タイムスタンプに最上位ビットを使用しています。)

  • 次の14ビットほど:ジェネレータごとのカウンタ、。各ジェネレータは、生成される新しいIDごとに1ずつ増加します。これにより、同時に生成されたID(同じタイムスタンプ)が重複しないことが保証されます。

  • 最後の10ビット程度:各ジェネレーターの一意の値。これを使用すると、ジェネレーター間の同期を行う必要がありません(極端にハード)、すべてのジェネレーターがこの値のために重複しないIDを生成するため。

c)タイムスタンプとランダム値を使用して、クライアント上でIDを生成できます。これにより、すべてのジェネレーターを知る必要がなくなり、各ジェネレーターを割り当てることができます。一意の値。逆に言えば、そのようなIDはguaranteedでグローバルに一意ではなく、唯一の非常に高い確率で一意です。 (衝突するには、1つまたは複数のジェネレーターがまったく同じ時間に同じランダム値を作成する必要があります。)次の行に沿った何か:

  • 最上位32ビット:Timestamp、IDの生成時間。
  • 最下位32ビット:32ビットのランダム性、各IDに対して新たに生成されます。

d)簡単な方法、 ID/GUIDを使用

111

各ノードに一意のID(とにかく持っているかもしれません)を持たせ、それをシーケンス番号の前に追加することができます。

たとえば、ノード1はシーケンス001-00001 001-00002 001-00003などを生成し、ノード5は005-00001 005-00002を生成します

ユニーク:-)

あるいは、何らかの集中システムが必要な場合は、シーケンスサーバーをブロック単位で配布することを検討できます。これにより、オーバーヘッドが大幅に削減されます。たとえば、割り当てる必要のある各IDに対して中央サーバーから新しいIDを要求する代わりに、中央サーバーから10,000のブロック単位でIDを要求し、次に実行するときに別のネットワーク要求を行うだけです。

15

現在、さらに多くのオプションがあります。

あなたはこの質問が「古い」ので、私はここに来たので、私が知っているオプションを(これまでのところ)残しておくと便利だと思います:

  • Hazelcast を試すことができます。 1.9リリースでは、Java.util.concurrent.AtomicLongの分散実装が含まれています。
  • Zookeeper を使用することもできます。シーケンスノードを作成する方法を提供します(znode名に追加されます。ノードのバージョン番号を使用することをお勧めします)。これに注意してください:シーケンス内の番号を見逃したくない場合、それはあなたが望むものではないかもしれません。

乾杯

14
Paolo

Redisson で実行できます。 AtomicLongの分散およびスケーラブルバージョンを実装します。以下に例を示します。

Config config = new Config();
config.addAddress("some.server.com:8291");

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
11

単に一意ではなく、グローバルにシーケンシャルにする必要がある場合は、これらの番号を分配するための単一のシンプルなサービスを作成することを検討します。

分散システムは、相互作用する多くの小さなサービスに依存していますが、このような単純なタスクのために、他の複雑な分散ソリューションから本当に必要なのですか、それとも本当にメリットがあるのでしょうか?

8
wsorenson

いくつかの戦略があります。しかし、私が知っているものはどれも実際に配布され、実際のシーケンスを与えることはできません。

  1. 中央番号ジェネレーターがあります。大きなデータベースである必要はありません。 memcachedには高速アトミックカウンターがあり、ほとんどの場合、クラスター全体に十分な速度です。
  2. 各ノードの整数範囲を分離します( Steven Schlanskter's answer など)
  3. 乱数またはUUIDを使用する
  4. ノードのIDとともにデータの一部を使用し、すべてをハッシュします(または hmac it)

個人的には、UUIDに頼るか、ほとんど連続したスペースが必要な場合はmemcachedを使います。

6
Javier

(スレッドセーフな)UUIDジェネレーターを使用しないのはなぜですか?

おそらくこれを拡張する必要があります。

UUIDはグローバルに一意であることが保証されます(乱数に基づくものを避けた場合、一意性が非常に高い可能性があります)。

使用するUUIDジェネレータの数に関係なく、各UUIDのグローバルな一意性により、「分散」要件が満たされます。

「スレッドセーフ」UUIDジェネレーターを選択すると、「スレッドセーフ」要件を満たすことができます。

「シーケンス番号」の要件は、各UUIDの保証されたグローバルな一意性によって満たされると想定されます。

多くのデータベースシーケンス番号の実装(Oracleなど)は、単調に増加することも(偶数)増加するシーケンス番号も(「接続」ごとに)保証しないことに注意してください。これは、接続ごとに連続した一連のシーケンス番号が「キャッシュ」ブロックに割り当てられるためです。これにより、グローバルな一意性が保証されますandは適切な速度を維持します。ただし、複数の接続によって割り当てられている場合、実際に(時間とともに)割り当てられるシーケンス番号は混乱する可能性があります。

4
Phil

これは古い質問ですが、私たちも同じニーズに直面しており、私たちのニーズを満たすソリューションを見つけることができませんでした。私たちの要件は、IDの一意のシーケンス(0,1,2,3 ... n)を取得することでした。したがって、スノーフレークは役に立ちませんでした。 Redisを使用してIDを生成する独自のシステムを作成しました。 Redisはシングルスレッドなので、そのリスト/キューメカニズムは常に一度に1ポップを与えます。

行うことは、idのバッファを作成することです。最初は、キューには0〜20個のidがあり、要求されたときにディスパッチする準備ができています。複数のクライアントがidを要求でき、redisは一度に1つのidをポップします。左からポップするたびに、BUFFER + currentIdを右側に挿入します。これにより、バッファーリストが維持されます。実装 ここ

2
Zohair

分散ID生成は、RedisとLuaを使用してアーカイブできます。 Github で利用可能な実装。分散されたkソート可能な一意のIDを生成します。

1
SANN3

私は、半一意の非シーケンシャル64ビット長の数値を生成できる単純なサービスを作成しました。冗長性とスケーラビリティのために複数のマシンに展開できます。メッセージングにZeroMQを使用します。動作の詳細については、githubページをご覧ください: zUID

0
Majid Azimi

データベースを使用すると、単一のコアで毎秒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を使用して独自のサービスを実装できます。パーティションごとに約10万の増分を取得できました。

それが役に立てば幸い!

0
user2108278

適切な解決策の1つは、時間ベースの長い世代を使用することです。分散データベースのバッキングを使用して実行できます。

0
refuess

問題は次のようになります。iscsiの世界では、クライアント側で実行されているイニシエーターが各LUN /ボリュームを一意に識別できる必要があります。 iscsi規格では、最初の数ビットはストレージプロバイダー/メーカーの情報を表す必要があり、残りは単調に増加することを示しています。

同様に、ノードの分散システムの初期ビットを使用してnodeIDを表すことができ、残りは単調に増加します。

0
user1860223