背景

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

snowflake ID生成器的更多相关文章

  1. twitter的ID生成器的snowFlake算法的自造版

    snowFlake算法在生成ID时特别高效,可参考:https://segmentfault.com/a/1190000011282426 SnowFlake算法生成id的结果是一个64bit大小的整 ...

  2. snowflake 分布式唯一ID生成器

    本文来自我的github pages博客http://galengao.github.io/ 即www.gaohuirong.cn 摘要: 原文参考运维生存和开源中国上的代码整理 我的环境是pytho ...

  3. id生成器,分布式ID自增算法(Snowflake 算法)

    接口: /** * id生成器 */ public interface IdGenerator { String next(); } 实现类: /** * 分布式ID自增算法<br/> * ...

  4. 基于Spring Boot的可直接运行的分布式ID生成器的实现以及SnowFlake算法详解

    背景 最近对snowflake比较感兴趣,就看了一些分布式唯一ID生成器(发号器)的开源项目的源码,例如百度的uid-generator,美团的leaf.大致看了一遍后感觉uid-generator代 ...

  5. 全局唯一ID生成器(Snowflake ID组成) 分析

    Snowflake ID组成 Snowflake ID有64bits长,由以下三部分组成: time—42bits,精确到ms,那就意味着其可以表示长达(2^42-1)/(1000360024*365 ...

  6. 分布式的Id生成器

    项目中需要一个分布式的Id生成器,twitter的Snowflake中这个既简单又高效,网上找的Java版本 package com.cqfc.id; import org.slf4j.Logger; ...

  7. 分布式ID生成器 zz

    简介 这个是根据twitter的snowflake来写的.这里有中文的介绍. 如上图所示,一个64位ID,除了最左边的符号位不用(固定为0,以保证生成的ID都是正数),还剩余63位可用. 下面的代码与 ...

  8. 业务系统需要什么样的ID生成器

    业务系统需要什么样的ID生成器 ID 生成器在微博我们一直叫发号器,微博就是用这样的号来存储,而我微博里讨论的时候也都是以发号器为标签.它的主要目的确如平常大家理解的“为一个分布式系统的数据objec ...

  9. c#分布式ID生成器

    c#分布式ID生成器   简介 这个是根据twitter的snowflake来写的.这里有中文的介绍. 如上图所示,一个64位ID,除了最左边的符号位不用(固定为0,以保证生成的ID都是正数),还剩余 ...

随机推荐

  1. 使用 Xbox Game 录制桌面视频(录制音频)

    使用 Xbox Game 录制桌面视频(附带音频) 前言:可能自己音频输出的问题,一直无法用工具录制桌面的音频,而最后发现利用 Xbox Game 录制游戏视频的功能很好地解决我们的问题. 1)打开游 ...

  2. yii2.0的学习之旅(二)

    前言:上一次我们简单认识了一下yii2.0安装,模型基本(增,删,改,查)操作 一.前后台数据交互 *如果你觉得默认的top样式太丑,可以这样关掉* *底部也可以这样关掉* (1)mvc合作操作数据 ...

  3. dubbo(提供者、消费者)基于java的实现

    1.安装好jdk.zookeeper以后可以尝试开发代码进行dubbo的学习和练习. 首先创建Dubbo的Provider配置.创建一个maven project工程. RPC框架,不希望Consum ...

  4. 分享windows 10 下部署 elasticsearch 和 logstash (二)

    接上一篇,es部署很简单,很快就弄好了. 但是还是有很多不玩美. 比如说:主机是本地的IP或机器名,端口是固定的9200. 而且是只有一个节点,我要在一台机器上部署多个节点呢. 经过一段时间的摸索,做 ...

  5. ASP.NET中的请求验证

    这两天做项目的时候偶然发现项目中的保存功能的时候出现这样的异常:异常详细信息: System.Web.HttpRequestValidationException: 从客户端(XXXX)中检测到有潜在 ...

  6. Locust 接口性能测试 - 转载一 (后期熟悉实践自己出一套完整的)

    转载大佬   ,.. 另外一篇:https://www.cnblogs.com/imyalost/p/9758189.html记录一下接口性能测试的学习 先熟悉一下概念: Locust是使用Pytho ...

  7. 解决使用elementUI框架el-upload跨域上传时session丢失问题

    解决方法一: 1.使用elementUI框架el-upload跨域上传时,后端获取不到cookie,后端接口显示未登录,在添加了 with-credentials="true"后依 ...

  8. Telegram APIs中文介绍

    Telegram APIs 我们为开发者提供了两种API,Bot API (机器人API) 允许你很轻松地用Telegram的接口创建程序,Telegram API 和DLib 允许你创建定制自己的T ...

  9. H3C路由器设置NAT回环、端口回流

    起因 当用本地服务器作为frp的服务端时,需要在路由器上设置端口映射,将公网ip和本地ip映射起来,用于作为frps的公网,这一步很简单一般都会有可视化界面来实现,但实际测试时发现问题: 当非局域网内 ...

  10. Centos7服务器搭建网络家园和论坛

    环境:Centos7 工具:mysql,php,httpd 目的:熟练掌握httpd服务器搭建和个服务器之间的配合. 有兴趣的朋友可以来实践一下,我会提供各种源码进行搭建. 网络家园和论坛源码:htt ...