• 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

  • 相关阅读:
    进程同步和死锁;事务、悲观锁、乐观锁、表锁、行锁
    快速排序
    oracle索引分类
    数据结构
    MySQL视图
    sql优化的方法
    MySQL索引
    转:关于ROWNUM的使用
    转载:mybatis踩坑之——foreach循环嵌套if判断
    Spring 注意事项
  • 原文地址:https://www.cnblogs.com/fanfan259/p/11731209.html
  • Copyright © 2020-2023  润新知