徹底的スノーフレークアルゴリズムのBaiduと米国企業のベストプラクティスを知ってもらいます

EDITORIAL言葉

分散ID自動的に生成されたプログラムを言及し、私はあなたが非常に精通している確信している、とすぐに自分の利益のいくつかのオプションを指示することができ、実際に、システムIDデータは自明の重要性を特定することが重要であるとして、だけでなく、各種のプログラム最適化の複数の世代の後、私は自動的に配布生成方式のIDを分類するために、この視点を使用できるようにしてください。

実装

  • データソース完全に依存した方法

生成ルールIDが完全に制御されたデータソースコントロール、共通の自己IDデータベース増殖、シリアル番号、またはRedisのINCR / INCRBY原子世代シーケンス番号によって読み取られます。

  • 半依存的データ・ソース

ID生成規則は、このような雪片アルゴリズムとして、成長因子データソース(または、構成情報)を制御する必要性によって一部です。

  • ソース・データに依存しない方法

などの一般的なUUID、GUID、などの任意のデータ記録構成情報に依存しない機械完全に独立した情報により算出されたID生成ルール、

プラクティス

上記3つの方法に適用可能なプラクティスは、システムのスループットを改善するために設計されたこれら3つの実装、を補完するものとして使用することができるが、元の実装の制限が残ります。

  • リアルタイムアクセスプログラム

名前が示すように、IDを取得するたびに、リアルタイムに生成されました。
簡単かつ迅速、IDは中断されないが、最高のスループットではないかもしれません。

  • 前世代のスキーム

IDプールの数の事前生成されたデータは、単に成長から生成することができる、ステップサイズは、バッチを生成し、提供することができ、これらは、貯蔵容器(JVMメモリ、Redisのに予めデータで生成される必要がある、データベーステーブルとすることができます)。
劇的に比べてスループットを向上させることができますが、停電はIDを持っていた後、一時的に記憶するためのスペースを開放する必要がIDが中断される可能性があります、失われることがあります。

プログラムについて

以下、現在人気のある分散型IDプログラムは、簡単な紹介を行います

  1. 成長のIDデータベースので、

方法完全に依存データソースに属する、すべてのデータベースIDに格納され、IDの生成は最も一般的な方法で、モノマーのアプリケーションの期間で最も広くAUTO_INCREMENTプライマリ・キーを運ぶときにデータテーブルを使用してデータベースを構築するために使用されている、又はこれは、IDから他のシーンでは需要の伸びの数を完了するために、シーケンスを使用することです。

  • 長所:非常に、シンプル増分と秩序、便利なページングやソート。
  • 欠点は:サブライブラリーサブテーブル、同じデータテーブル容易反復インクリメントIDを直接使用することができない(ステップを設けてもよいが、それは明らかなように制限)、全体性能の低いスループット、単一の分散データベースを達成するように設計された場合ユニークなデータであっても事前に生成方式を使用したアプリケーションも、トランザクションのロックの問題ので、高い並行性のボトルネックシーンの単一のポイントを簡単に。
  • アプリケーションシナリオ:単一のデータベース・テーブル・インスタンスID(シーン含むマスタ - スレーブ同期)、シリアル番号の一部とカウント日によってのような、シーンのサブライブラリーサブテーブル、適用されないシーンのシステム全体で一意のID。
  1. ジェネレーションIDのRedis

また、生成された一意のIDは、自然のデータベースと一致する実装を注文しなければならないことを確実にするために、RedisのINCR / INCRBYアトミックインクリメント動作コマンドによってデータソース全く依存的に属しています。

  • 長所:データベースより高い全体のスループット。
  • 短所:Redisのインスタンスまたはクラスタのダウンタイムや新しいID値が少し難しいです。
  • アプリケーションシナリオ:比較するなど、ユーザーの訪問、シリアル番号順(日付+シリアル番号)など、シーンを数えます。
  1. UUID、GUID生成ID

UUID:定められた基準に基づいて算出OSF、イーサネットカードアドレス、ナノ秒の時間、チップのIDコードと可能な番号の数を使用します。現在の日付と時刻が(数秒、あなたはUUIDを生成し、その後場合は、時間関連の最初の部分をUUIDとUUIDを生成し、異なる、同じままの最初の部分)、クロック:以下の部品の組み合わせ配列、IEEEグローバルに一意の機械識別番号(カードから得られる任意のカードは、カードが他に得られていない場合)

GUID:UUIDの標準のMicrosoftの実装。他の様々な実装のUUID、それらを表示しないのGUIDつ以上があります。

どちらも、途中に依存しないデータ・ソースに属し、真にグローバルに一意なID

  • 長所:任意のデータソース、独自の計算に依存しない、何のネットワークID、超高速スピードとグローバルに一意ではありません。
  • 短所:なしシーケンシャルと長い(128ビット)、データベースの主キーインデックスインデックスとしては、効率の低下につながるより多くのスペースを取るでしょう。
  • アプリケーションシナリオ:要求なしストレージ・スペースは、このようなログ保存を追跡するさまざまなリンクとして、適用することはできない限り。

4、スノーフレークアルゴリズム(アルゴリズム雪)IDを生成し

半依存データソースモード、原理は、ロング(64)を使用することで、特定のルールに従って充填されている:(ミリ秒)+クラスタID +マシンID +シリアル番号、各部分は、ビットは、前記実際のに従って割り当てする必要があるかもしれない占有クラスタID、マシンIDの実用的なアプリケーション・シナリオに依存するパラメータまたは外部のデータベースレコード内の2つの部分、。

  • 長所:高性能、低遅延、中央に、時系列順に
  • 短所:マシンクロック同期要件(第二段階缶)
  • 該当するシーン:主キーのデータの分散アプリケーション環境

雪のIDアルゴリズムは、分散アーキテクチャのシーンのために特に適して鳴りませんか?現在の状況によると、その後、私たちはその原則とベストプラクティスを説明するに焦点を当て、です。

スノーフレークアルゴリズム原理

スノーフレークアルゴリズムはスリフトRPCインターフェイスのコールを達成するためのフレームワークを使って、Scalaの言語を使用して、ツイッターから来て、最初のプロジェクトの原因はカサンドラにMySQLのデータベースから移行され、カサンドラは、容易に入手可能なIDの生成メカニズムではありません、それはこのプロジェクトを出産した、既存のgithubのソースは行くと見ることができます興味を持っています。

スノーフレーク特性がシーケンシャルアルゴリズム、ユニークであり、分散環境であることが、高性能、低レイテンシ(各マシン毎秒生成されたデータの少なくとも10K、および2ミリ秒未満の応答時間)を、必要とする(マルチクラスタ、インター室)アルゴリズムIDスノーフレークセグメントから構成されているので、使用されます。

  • 指定された日付と時間差(ミリ秒)、41、69年は十分
  • クラスタID +マシンIDは、10ビットマシンは、1024年までサポート
  • シーケンス、図12に示すように、各配列番号で4096ミリ秒までを生成する各マシン

図示のように:
スノーフレーク構造

  • 1ビット:符号ビットは、すべてが正の整数IDであることを示し、0に固定されています。
  • 41bit:ミリ秒数の時間差が、69年のための十分な指定された日から数えて、私たちはタイムスタンプが00:00:00我々はタイムスタンプすることができます初めから数えロング1970-01-01で型を示すことを知っていますなど2019年10月23日午前0時00分00秒として指定した日付、
  • 10ビット:部屋のまわり予定ラインの下に必要なマシンID、オフサイトの展開、マルチクラスタも構成することができ、各クラスタは、各インスタンスのID番号
  • 12ビット:シーケンスIDは、その後、同じの前で、それは4096までサポートすることができます

唯一の公式勧告を割り当てられるビット数よりも、私たちは自分自身の分布の実際のニーズに応じて、例えば、我々は最も数十のマシンの数が、同時の多数を使用し、我々は一部から、8ビットのシーケンスを10ビットを減らすことができなどの12ビットの14ビットは増加しました

容器の展開のクラウド・コンピューティング環境は、任意の時間拡張で、IDの平面構成例にラインを介して機械操作の減少は、現実的ではない場合はもちろん、各部分の意味は自由に、マシンIDの中間部として、置換されていてもよいです例では、成長部分のコンテンツとして、自己IDを取るために、それぞれの再起動時間に置き換えることができ、それは以下に説明します。

Javaはスノーフレーク最も基本的な実装をしたにgithubの神でも大きな存在し、ここで直接ソースコードを表示:
Javaソースコードのスノーフレークバージョンを

/**
 * twitter的snowflake算法 -- java实现
 * 
 * @author beyond
 * @date 2016/11/26
 */
public class SnowFlake { /** * 起始的时间戳 */ private final static long START_STMP = 1480166465631L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATACENTER_BIT = 5;//数据中心占用的位数 /** * 每一部分的最大值 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastStmp = -1L;//上一次时间戳 public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } /** * 产生下一个ID * * @return */ public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { currStmp = getNextMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 | datacenterId << DATACENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } public static void main(String[] args) { SnowFlake snowFlake = new SnowFlake(2, 3); for (int i = 0; i < (1 << 12); i++) { System.out.println(snowFlake.nextId()); } } }

実質的変位動作によって、各セグメントの値を意味することは、計算機IDが+、従って、左機ID 12、即ち、その位置、データセンター構成データセンターから機器IDとして、対応する位置に移動します左側の番号17は、タイムスタンプ22の値だけ左に、それらの位置のそれぞれ占める部分は、非干渉の各々は、それによって完全なID値を形成します。

ジャワの幾分基本的な知識が情報を確認する提案を思い出すことができない場合、ここでの原理は、(全て1である)0xFFFFである、<<、左シフト演算を表す-1 5に等しい<<バイナリ-1のように、スノーフレーク最も基本的です-32、-1 ^ XOR演算(-1 << 5)31等でした。

スノーフレークは、クラウドコンピューティング、コンテナ技術のさまざまなを借りて、良いマシンが、現在の分散型生産環境を識別するために、事前に計画することによって実現することができる基本的な実装の原理を理解し、随時変更のインスタンスの数は、また、サーバインスタンスのクロックを処理する必要がありますコールバックの問題、そして、スノーフレークシーンの実現可能性を設定することにより、固定計画IDを使用して、高通常、自動起動し、あなたには、いくつかの雪片の変換は、本番環境に良く適用することができようにする必要がありので、停止機械の増減はありません。

BaiduのUID-発電プロジェクト

以下に示すような原理実装スノーフレーク、マシンIDのみ修正された部分に基づいてUidGenerator項目は、公式の分布をデフォルトの(再起動のインスタンスの数)、および64ビットのビット割り当て構成のサポートを定義されます。

Baiduは実装され、デフォルトの雪の結晶構造

スノーフレークアルゴリズム記述:指定&マシン&同時に複雑なシーケンスがユニークです。従って64ビットは、固有のID(長い)を生成してもよいです。

  • サイン(1ビット)、1ビットの固定識別記号、すなわち生成UIDは正の数です。
  • デルタ秒(28ビット)
    、現在の時刻、時間点に対して「2016年5月20日」インクリメンタル値、単位:秒は、およそ87年までサポートすることができます。
  • 作業者ID(22ビット)マシンIDは、マシンが起動約420ワット回までサポートすることができます。ディストリビューションデータベースから起動する場合に達成するために構築され、既定の割り当てポリシーは、その後の再利用戦略を提供することができ、使い捨てです。
  • 配列複雑な配列の下に毎秒(13ビット)、毎秒13ビットが同時8192をサポートすることができます。

具体的な実施、一方がリアルタイムIDで生成される二つあり、別の実施形態では、予め生成されたIDであります

  1. DefaultUidGenerator
  • データベーステーブルに挿入現在の例は、IP、ポート、および他の情報、ID部として機器IDから得られたデータのその後の成長を開始WORKER_NODE。
    簡体フローチャートは次のとおりです。

UidGeneratorのブートプロセス

  • IDを取得するための方法を提供し、クロックのコールバックは、コールバック現象が直接スロー例外を持っているかどうかを検出するには、現在のバージョンでは、ダイヤルに沿ってクロックドリフト後の動作をサポートしていません。簡体フローチャートは次のとおりです。

UidGenerator生成プロセス

コアのコードは次の通りであります:

    /**
     * Get UID
     *
     * @return UID
     * @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
     */
    protected synchronized long nextId() { long currentSecond = getCurrentSecond(); // Clock moved backwards, refuse to generate uid if (currentSecond < lastSecond) { long refusedSeconds = lastSecond - currentSecond; throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds); } // At the same second, increase sequence if (currentSecond == lastSecond) { sequence = (sequence + 1) & bitsAllocator.getMaxSequence(); // Exceed the max sequence, we wait the next second to generate uid if (sequence == 0) { currentSecond = getNextSecond(lastSecond); } // At the different second, sequence restart from zero } else { sequence = 0L; } lastSecond = currentSecond; // Allocate bits for UID return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence); }
  1. CachedUidGenerator

クライアントは、利用可能なデータが閾値未満である場合、使用再びと共に属する呼び出しバッチ法を生成するために、リングバッファ環状アレイの一方に機械と同じID、予め生成されたグループIDを取得する方法時間的なアプローチのためのスペースは、全体のIDのスループットを向上させることができます。

  • 次のようにDefaultUidGenerator、論理スタッフィングリングバッファ環状アレイの初期化の複数と比較して、簡単なフローチャートです。

CachedUidGeneratorのブートプロセス

コアコード:

/**
     * Initialize RingBuffer & RingBufferPaddingExecutor
     */
    private void initRingBuffer() { // initialize RingBuffer int bufferSize = ((int) bitsAllocator.getMaxSequence() + 1) << boostPower; this.ringBuffer = new RingBuffer(bufferSize, paddingFactor); LOGGER.info("Initialized ring buffer size:{}, paddingFactor:{}", bufferSize, paddingFactor); // initialize RingBufferPaddingExecutor boolean usingSchedule = (scheduleInterval != null); this.bufferPaddingExecutor = new BufferPaddingExecutor(ringBuffer, this::nextIdsForOneSecond, usingSchedule); if (usingSchedule) { bufferPaddingExecutor.setScheduleInterval(scheduleInterval); } LOGGER.info("Initialized BufferPaddingExecutor. Using schdule:{}, interval:{}", usingSchedule, scheduleInterval); // set rejected put/take handle policy this.ringBuffer.setBufferPaddingExecutor(bufferPaddingExecutor); if (rejectedPutBufferHandler != null) { this.ringBuffer.setRejectedPutHandler(rejectedPutBufferHandler); } if (rejectedTakeBufferHandler != null) { this.ringBuffer.setRejectedTakeHandler(rejectedTakeBufferHandler); } // fill in all slots of the RingBuffer bufferPaddingExecutor.paddingBuffer(); // start buffer padding threads bufferPaddingExecutor.start(); }
public synchronized boolean put(long uid) { long currentTail = tail.get(); long currentCursor = cursor.get(); // tail catches the cursor, means that you can't put any cause of RingBuffer is full long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor); if (distance == bufferSize - 1) { rejectedPutHandler.rejectPutBuffer(this, uid); return false; } // 1. pre-check whether the flag is CAN_PUT_FLAG int nextTailIndex = calSlotIndex(currentTail + 1); if (flags[nextTailIndex].get() != CAN_PUT_FLAG) { rejectedPutHandler.rejectPutBuffer(this, uid); return false; } // 2. put UID in the next slot // 3. update next slot' flag to CAN_TAKE_FLAG // 4. publish tail with sequence increase by one slots[nextTailIndex] = uid; flags[nextTailIndex].set(CAN_TAKE_FLAG); tail.incrementAndGet(); // The atomicity of operations above, guarantees by 'synchronized'. In another word, // the take operation can't consume the UID we just put, until the tail is published(tail.incrementAndGet()) return true; }
  • リングバッファは、バッチを生成する際に、取得したIDリングバッファから直接採取によるリングバッファの存在下にID取得ロジック、バッファアレイは、それ自体を確認することができ、その後、ここで取得した有意差値DefaultUidGenerator ID、取得DefaultUidGeneratorトリガを再ことができID、タイムスタンプは、現在の時間の一部であり、時間を埋める際にタイムスタンプを取得中CachedUidGeneratorが取得されていませんが、行うことはほとんどないが、それを用いた、繰り返されていません。簡体フローチャートは次のとおりです。

CachedUidGenerator取得処理

コアコード:

public long take() { // spin get next available cursor long currentCursor = cursor.get(); long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1); // check for safety consideration, it never occurs Assert.isTrue(nextCursor >= currentCursor, "Curosr can't move back"); // trigger padding in an async-mode if reach the threshold long currentTail = tail.get(); if (currentTail - nextCursor < paddingThreshold) { LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail, nextCursor, currentTail - nextCursor); bufferPaddingExecutor.asyncPadding(); } // cursor catch the tail, means that there is no more available UID to take if (nextCursor == currentCursor) { rejectedTakeHandler.rejectTakeBuffer(this); } // 1. check next slot flag is CAN_TAKE_FLAG int nextCursorIndex = calSlotIndex(nextCursor); Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status"); // 2. get UID from next slot // 3. set next slot flag as CAN_PUT_FLAG. long uid = slots[nextCursorIndex]; flags[nextCursorIndex].set(CAN_PUT_FLAG); // Note that: Step 2,3 can not swap. If we set flag before get value of slot, the producer may overwrite the // slot with a new UID, and this may cause the consumer take the UID twice after walk a round the ring return uid; }

直接使用AtomicLong型ネイティブ、尾とカーソルがよりに同じキャッシュラインにキャッシュすることができるならば、あなたが見つけることができる詳細があるほか、リングバッファのデータを格納する配列を使用している、CPUのキャッシュの構造、尾やカーソル変数を考えますスレッドは、変数がRFOの要求キャッシュラインを引き起こすが、パフォーマンスに影響を与え、偽共有の問題を防ぐために、意図的価値メンバ変数のロングタイプで、long型の6つのメンバ変数を満たし、ちょうどキャッシュラインを埋めることが読み、これは、我々は次のようにソースコードを見ることができます興味を持っている、キャッシュラインが満たされたと呼ばれている(Javaのは、同様に8バイトのヘッドをオブジェクトとしてオブジェクト):

public class PaddedAtomicLong extends AtomicLong { private static final long serialVersionUID = -3415778863941386253L; /** Padded 6 long (48 bytes) */ public volatile long p1, p2, p3, p4, p5, p6 = 7L; /** * Constructors from {@link AtomicLong} */ public PaddedAtomicLong() { super(); } public PaddedAtomicLong(long initialValue) { super(initialValue); } /** * To prevent GC optimizations for cleaning unused padded references */ public long sumPaddingToPreventOptimization() { return p1 + p2 + p3 + p4 + p5 + p6; } }

これらを中心に説明されているBaiduのUID-発電プロジェクトは、私たちは主に、マシンIDの取得に上陸でいくつかの変更、以下特に分散クラスタ環境、自動開閉式の例では、ドッキングウィンドウの一部のコンテナがあること、雪の結晶のアルゴリズムを見ることができます静的な設定項目ID、インスタンスIDの可能性が高くならないように技術、これらの変換によって開始の数が識別されます。

米国のグループECP-UIDプロジェクト

uidGeneratorの点では、米国のグループプロジェクトのソースコードのソースは直接例えば、いくつかのネイティブJava構文に少しラムダ式、Baiduの統合しました:

// com.myzmds.ecp.core.uid.baidu.impl.CachedUidGenerator类的initRingBuffer()方法
// 百度源码
this.bufferPaddingExecutor = new BufferPaddingExecutor(ringBuffer, this::nextIdsForOneSecond, usingSchedule); // 美团源码 this.bufferPaddingExecutor = new BufferPaddingExecutor(ringBuffer, new BufferedUidProvider() { @Override public List<Long> provide(long momentInSecond) { return nextIdsForOneSecond(momentInSecond); } }, usingSchedule);

機械ID、飼育係の導入、Redisのこれらの成分を生成し、マシンIDを取得富化生成するために、インスタンス番号を繰り返し、もはや単調データベースのこの種類を増加しない、記憶されている使用することができます。

結論

Benpianが原則と床の改修プロセススノーフレークアルゴリズムを紹介し、本研究で優れたオープンソースのコードとは、簡単な例の一部を選び出す、ECP-uidのプロジェクト米国のグループは、Baiduの既存UidGeneratorを統合するだけでなくアルゴリズム、スノーフレークアルゴリズムネイティブも網羅的な説明ではない長さを考慮すると、優れた葉セグメントアルゴリズムを含んでいます。記事ではどんな正しくないか徹底的な場所を持っている、あなたに感謝し、指摘メッセージを残してください。

 

おすすめ

転載: www.cnblogs.com/cider/p/11776088.html