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


103

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

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

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


この質問は古いですが、PLSのは私の新しい答えを参照stackoverflow.com/questions/2671858/...を
ジェスパーM

nextval.orgをどのように使用しますか?ウェブサイトは少し奇妙で、何が原因なのかわかりません。Unixコマンドですか?またはいくつかのクラウドサービス?
diegosasw 2018年

回答:


116

はい、これは非常に古い質問です。私が最初に目にするのはこの質問です。

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

一意のIDは別の問題です。分散IDで一意のIDを生成するには、いくつかの優れた方法があります。

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

  • ネットワークサービス。つまり、一意のIDを取得するためにネットワーク呼び出しを行います。
  • 生成時間順に並べられた64ビットの一意のIDを生成します。
  • また、サービスはスケーラビリティが高く、(潜在的に)可用性が高い。各インスタンスは毎秒数千のIDを生成でき、LAN / WANで複数のインスタンスを実行できます。
  • Scalaで書かれ、JVM上で実行されます。

b)UUIDとSnowflakeのIDの作成方法から派生したアプローチを使用して、クライアント自体で一意のIDを生成できます複数のオプションがありますが、次のようなものがあります。

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

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

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

c)タイムスタンプとランダムな値だけを使用して、クライアントでIDを生成できます。これにより、すべてのジェネレータを知って、各ジェネレータに一意の値を割り当てる必要がなくなります。反対に、そのようなIDはグローバルに一意であるとは限りませ。一意である可能性非常に高いだけです。(衝突するためには、1つ以上のジェネレーターがまったく同じランダム値を同時に作成する必要があります。)次のようなもの:

  • 最上位の32ビット:IDの生成時間であるタイムスタンプ
  • 最下位32ビット:32ビットのランダム性。IDごとに新た生成されます。

d)簡単な方法は、UUID / GUIDを使用することです。


Cassandraはカウンター(cassandra.apache.org/doc/cql3/CQL.html#counters)をサポートしますが、いくつかの制限があります。
Piyush Kansal、2015年

シーケンス番号はビットマップインデックスの位置を設定するのは簡単ですが、一意のIDが長すぎる(64ビットまたは128ビット)場合があります。一意のIDをビットマップインデックスの位置にマッピングするにはどうすればよいですか。ありがとう。
ブルースナン2015年

2
本当に好きなオプション#b .....それは大規模を可能にし、同時実行性の問題の多くを引き起こさないかもしれません
puneet

2
twitter/snowflakeメンテナンスされなくなりました
Navin、

オプションBのApache2ライセンスの実装が必要な場合は、bitbucket.org / pythagorasio / common-libraries / src / master / …を確認してください。mavenio.pythagoras.common:distributed-sequence-id-generator:1.0から入手することもできます。 .0
Wpigott

16

今より多くのオプションがあります。

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

  • あなたはヘーゼルキャストを試すことができます。その1.9リリースでは、java.util.concurrent.AtomicLongの分散実装が含まれています
  • Zookeeperを使用することもできます。シーケンスノードを作成するためのメソッドを提供します(ノードのバージョン番号を使用することをお勧めしますが、znode名に追加されます)。ただし、これには注意してください。シーケンスで見逃した数字が不要な場合は、必要な数ではない可能性があります。

乾杯


3
Zookeeperは私が行ったオプションでした、私が始めたメーリングリスト-mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
Jon

ジョン、そのスレッドを指摘してくれてありがとう、それはまさに私が考えていたタイプのソリューションです。ところで、MAX_INTの制限を克服するコードを作成しましたか?
Paolo

15

各ノードに一意のID(いずれにせよ)を持たせ、それをシーケンス番号の前に付加することができます。

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

ユニーク:-)

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


1
バッチID生成についてのあなたのポイントは好きですが、リアルタイム計算の可能性を制限するだけです。
ishan

同様のメカニズムを実装しました。その中で、クライアントがシーケンスのブロックをキャッシュすることに加えて、シーケンスのブロックをキャッシュするいくつかのサーバーホストを追加しました。(単一の)マスタージェネレーターは、いくつかの高可用性ストレージまたは単一マスターホストで維持され、サーバーホストのフリートのみにアクセスできます。サーバーキャッシングは、単一のマスターが一時的にダウンしても、稼働時間を長くするのに役立ちます。
Janakiram、2015

11

これはRedissonで実行できます。分散型でスケーラブルなバージョンのを実装していAtomicLongます。次に例を示します。

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

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

8

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

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


3
...そしてそのサービスを実行しているサーバーがダウンするとどうなりますか?
Navin、

誰かに別のものを開始するように警告するアラートがありますか?時々それはうまくいくでしょう。その答えは、「物事を視点に置いて」と言うことだと思います。完璧な分散型ソリューションには固有の欠点があり、時にはシンプルな方が良い場合があります。
nicフェリア

6

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

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

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


5

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

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

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

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

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

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

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


1
UUIDは機能しますが、生成されたキーに最終的にインデックスを付ける必要がある場合は、UUIDの保存方法に注意する必要があるという問題があります。また、通常、単調に増加するシーケンスよりも多くのスペースを使用します。MySQLでの保存に関する説明については、percona.com / blog / 2014/12/19 / store-uuid-optimized-wayを参照してください。
Pavel、

2

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


2

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

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


0

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


0

データベースを使用すると、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の増分を得ることができました。

それが役に立てば幸い!


0

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

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


1
詳細をいくつか追加してください
Ved Prakash 2017

0

まともな解決策の1つは、長時間ベースの生成を使用することです。これは、分散データベースのバッキングで実行できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.