snowflake ID生成器


背景

Snowflake 是 Twitter 內部的一個 ID 生算法,
可以通過一些簡單的規則保證在大規模分布式情況下生成唯一的 ID 號碼。

其組成為:
第一個 bit 為未使用的符號位。
第二部分由 41 位的時間戳(毫秒)構成,他的取值是當前時間相對於某一時間的偏移量。
第三部分和第四部分的 5 個 bit 位表示數據中心和機器 ID,其能表示的最大值為 2^5 -1 = 31;
最后部分由 12 個 bit 組成,其表示每個工作節點每毫秒生成的序列號 ID,同一毫秒內最多可生成 2^12 -1 即 4095 個 ID。

需要注意的是:

  • 在分布式環境中,5 個 bit 位的 datacenter 和 worker 表示最多能部署 31 個數據中心,每個數據中心最多可部署 31 台節點。
    41 位的二進制長度最多能表示 2^41 -1 毫秒即 69 年,所以雪花算法最多能正常使用 69 年,為了能最大限度的使用該算法,你應該為其指定一個開始時間。
  • 由上可知,雪花算法生成的 ID 並不能保證唯一,如當兩個不同請求同一時刻進入相同的數據中心的相同節點時,而此時該節點生成的 sequence 又是相同時,就會導致生成的 ID 重復。
  • 所以要想使用雪花算法生成唯一的 ID,就需要保證同一節點同一毫秒內生成的序列號是唯一的。基於此,可以有多種方式參考鏈接2
    RandomSequenceResolver(隨機生成)
    RedisSequenceResolver (基於 redis psetex 和 incrby 生成)
    LaravelSequenceResolver(基於 laravel 生成)
    SwooleSequenceResolver(基於 swoole_lock 鎖)
    不同的提供者只需要保證同一毫秒生成的序列號不同,就能得到唯一的 ID

代碼

php實現

/**

  • ID 生成策略

  • 毫秒級時間41位+機器ID 10位+毫秒內序列12位。

  • 0 1 41 46 51 63

  • +-------+-----------+---------+-----------+-----------+

  • |unused |timestamp |workId |machineId |sequence |

  • +-------+-----------+---------+-----------+-----------+

  • 1bit是 未使用的符號位

  • 接着41bits是 微秒為單位的timestamp

  • 接着5bits是 業務線ID

  • 接着5bits是 事先配置好的機器ID

  • 最后12bits是 累加計數器

  • workerId (5bits) 最多只能有32個業務同時產生ID

  • machineId (5bits) 最多只能有32台機器同時產生ID

  • sequence (12bits) 1台機器1ms中最多產生4096個ID
    */
    class Snowflake
    {

    const EPOCH = 1571829625238; // 起始時間戳,毫秒

    const SEQUENCE_BITS = 12; // 序號部分 12位
    const SEQUENCE_MAX = -1 ^ (-1 << self::SEQUENCE_BITS); // 序號最大值

    const WORKER_BITS = 5; // 業務節點部分 5位
    const WORKER_MAX = -1 ^ (-1 << self::WORKER_BITS); // 業務節點最大數值

    const MACHINE_BITS = 5; // 機器部分 5位
    const MACHINE_MAX = -1 ^ (-1 << self::MACHINE_BITS); // 機器數最大值

    const TIME_SHIFT = self::WORKER_BITS + self::MACHINE_BITS + self::SEQUENCE_BITS; // 時間戳部分左偏移量
    const WORKER_SHIFT = self::MACHINE_BITS + self::SEQUENCE_BITS; // 機器部分左偏移量
    const MACHINE_SHIFT = self::SEQUENCE_BITS; // 業務節點部分左偏移量

    protected $timestamp; // 上次ID生成時間戳
    protected $workerId; // 節點ID
    protected $machineId; // 機器ID
    protected $sequence; // 序號

    public function __construct($machineId = 1, \(workerId = 1) { if (\)machineId < 0 || \(machineId > self::MACHINE_MAX) { throw new \Exception("machineId can't be greater than " .self::MACHINE_MAX. " or less than 0"); } if (\)workerId < 0 || $workerId > self::WORKER_MAX) {
    throw new \Exception("workerId can't be greater than " .self::WORKER_MAX. " or less than 0");
    }

    $this->timestamp = 0;
    $this->machineId = $machineId;
    $this->workerId = $workerId;
    $this->sequence = 0;
    

    }

    /**

    • 生成ID
    • @return int
      */
      public function getId()
      {
      $now = \(this->getTimestampM(); if (\)this->timestamp == $now) {
      \(this->sequence ++; if (\)this->sequence > self::SEQUENCE_MAX) {
      // 當前毫秒內生成的序號已經超出最大范圍,等待下一毫秒重新生成
      // 使用 usleep(1) 一樣
      while ($now <= $this->timestamp) {
      $now = $this->getTimestampM();
      }
      }
      } else {
      $this->sequence = 0;
      }
      $this->timestamp = $now; // 更新ID生時間戳
      \(id = ((\)now - self::EPOCH) << self::TIME_SHIFT) | (\(this->workerId << self::WORKER_SHIFT) | (\)this->machineId << self::MACHINE_SHIFT) | $this->sequence;
      return $id;
      }

    /**

    • 返回id生成參數
    • @param $id
    • @return array
      */
      public function restoreId($id)
      {
      \(binary = decbin(\)id);
      return [
      'timestamp' => bindec(substr(\(binary, 0, -self::TIME_SHIFT)) + self::EPOCH, 'workerId' => bindec(substr(\)binary, -self::TIME_SHIFT, self::WORKER_BITS)),
      'machineId' => bindec(substr(\(binary, -self::WORKER_SHIFT, self::MACHINE_BITS)), 'sequence' => bindec(substr(\)binary, -self::SEQUENCE_BITS)),
      ];
      }

    /**

    • 獲取當前毫秒時間戳
    • @return string
      */
      public function getTimestampM()
      {
      $time = explode(' ', microtime());
      \(time2= substr(\)time[0], 2, 3);
      return \(time[1].\)time2;
      }
      }

id的混淆

  • 既然使用的是snowflake方式, 可以使用 原來總結的 進制轉換的方式,轉換為相應的 字符串表示方式
  • 或者是 使用 hashids 現有庫,hashids

補充知識

正數的二進制表示方式: 補碼和原碼相同
負數的二進制表示方式: 以其原碼的補碼形式表示

正數的補碼是其二進制表示,與原碼相同。
負數的補碼,將其原碼除符號位外的所有位取反(0變1,1變0,符號位為1不變)后加1。

-1 ^ (-1 << 4)
就是-1的二進制表示為-1的補碼(其值為 位數上全是1, 11111111)
其實等同於: 2的4次方 - 1

參考鏈接

hashids
參考鏈接1
參考鏈接2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM