PHP实现Snowflake生成分布式唯一ID的方法示例


Posted in PHP onAugust 30, 2020

前言

Twitter 的 snowflake 在分布式生成唯一 UUID 应用还是蛮广泛的,基于 snowflake 的一些变种的算法网上也有不少。使用 snowflake 生成 UUID 很多都是在分布式场景下使用,我看了下网上有其中有几篇 PHP 实现的都没有考虑到线程安全。现在 PHP 有了 Swoole 的锁和协程的加持,对于我们开发线程安全和高并发模拟还是很方便的,这里用 PHP 结合 Swoole 来学习下实现最简单的 snowflake。

先来看以下 snowflake 的结构:

PHP实现Snowflake生成分布式唯一ID的方法示例

生成的数值是 64 位,分成 4 个部分:

  • 第一个 bit 为符号位,最高位为 0 表示正数
  • 第二部分 41 个 bit 用于记录生成 ID 时候的时间戳,单位为毫秒,所以该部分表示的数值范围为 2^41 - 1(69 年),它是相对于某一时间的偏移量
  • 第三部分的 10 个 bit 表示工作节点的 ID,表示数值范围为 2^10 - 1,相当于支持 1024 个节点
  • 第四部分 12 个 bit 表示每个工作节点没毫秒生成的循环自增 id,最多可以生成 2^12 -1 个 id,超出归零等待下一毫秒重新自增。
<?php

class Snowflake
{
  const EPOCH = 1543223810238;  // 起始时间戳,毫秒

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

  const WORKER_BITS = 10; // 节点部分10位
  const WORKER_MAX = -1 ^ (-1 << self::WORKER_BITS); // 节点最大数值

  const TIME_SHIFT = self::WORKER_BITS + self::SEQUENCE_BITS; // 时间戳部分左偏移量
  const WORKER_SHIFT = self::SEQUENCE_BITS;  // 节点部分左偏移量

  protected $timestamp;  // 上次ID生成时间戳
  protected $workerId;  // 节点ID
  protected $sequence;  // 序号
  protected $lock;    // Swoole 互斥锁

  public function __construct($workerId)
  {
    if ($workerId < 0 || $workerId > self::WORKER_MAX) {
      trigger_error("Worker ID 超出范围");
      exit(0);
    }

    $this->timestamp = 0;
    $this->workerId = $workerId;
    $this->sequence = 0;
    $this->lock = new swoole_lock(SWOOLE_MUTEX);
  }

  /**
   * 生成ID
   * @return int
   */
  public function getId()
  {
    $this->lock->lock();  // 这里一定要记得加锁
    $now = $this->now();
    if ($this->timestamp == $now) {
      $this->sequence++;

      if ($this->sequence > self::SEQUENCE_MAX) {
        // 当前毫秒内生成的序号已经超出最大范围,等待下一毫秒重新生成
        while ($now <= $this->timestamp) {
          $now = $this->now();
        }
      }
    } else {
      $this->sequence = 0;
    }

    $this->timestamp = $now;  // 更新ID生时间戳

    $id = (($now - self::EPOCH) << self::TIME_SHIFT) | ($this->workerId << self::WORKER_SHIFT) | $this->sequence;
    $this->lock->unlock(); //解锁

    return $id;
  }

  /**
   * 获取当前毫秒
   * @return string
   */
  public function now()
  {
    return sprintf("%.0f", microtime(true) * 1000);
  }

}

其实逻辑并不复杂,解释一下代码中的位运算:

-1 ^ (-1 << self::SEQUENCE_BITS)
就是-1的二进制表示为1的补码,其实等同于 :
2**self::SEQUENCE_BITS - 1

最后部分左移后或运算:

(($now - self::EPOCH) << self::TIME_SHIFT) | ($this->workerId << self::WORKER_SHIFT) | $this->sequence;

这里主要是对除了第一位符号位以外的三个部分进行左移相应的偏移量使其归位,并通过或运算重新整合成上面 snowflake 的结构,比如我们用 3 部分 4 位来演示一下该归并操作:

0000 0000 0010  --左移0位--> 0000 0000 0010
0000 0000 0100  --左移4位--> 0000 0100 0000 --或操作-->1000 0100 0010
0000 0000 1000  --左移8位--> 1000 0000 0000

总结

到此这篇关于PHP实现Snowflake生成分布式唯一ID的文章就介绍到这了,更多相关PHP Snowflake生成分布式唯一ID内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP编程网上资源导航
Oct 09 PHP
不用GD库生成当前时间的PNG格式图象的程序
Oct 09 PHP
PHP insert语法详解
Jun 07 PHP
php将数据库导出成excel的方法
May 07 PHP
PHP分多步骤填写发布信息的简单方法实例代码
Sep 23 PHP
深入PHP操作MongoDB的技术总结
Jun 02 PHP
php将gd生成的图片缓存到memcache的小例子
Jun 05 PHP
PHP中对于浮点型的数据需要用不同的方法解决
Mar 11 PHP
PHP中使用json数据格式定义字面量对象的方法
Aug 20 PHP
php简单判断两个字符串是否相等的方法
Jul 13 PHP
laravel withCount 统计关联数量的方法
Oct 10 PHP
thinkphp5.1框架实现格式化mysql时间戳为日期的方式小结
Oct 10 PHP
Yii实现微信公众号场景二维码的方法实例
Aug 30 #PHP
Swoole源码中如何查询Websocket的连接问题详解
Aug 30 #PHP
PHP常用header头定义代码示例汇总
Aug 29 #PHP
PHP isset()及empty()用法区别详解
Aug 29 #PHP
PHP实现简单日历类编写
Aug 28 #PHP
PHP实现文件上传与下载
Aug 28 #PHP
PHP实现计算器小功能
Aug 28 #PHP
You might like
ie与session丢失(新窗口cookie丢失)实测及解决方案
2013/07/15 PHP
php实现购物车功能(上)
2020/07/23 PHP
PHP new static 和 new self详解
2017/02/19 PHP
深入解析PHP底层机制及相关原理
2020/12/11 PHP
js图片延迟加载的实现方法及思路
2013/07/22 Javascript
javascript中apply和call方法的作用及区别说明
2014/02/14 Javascript
自己使用jquery写的一个无缝滚动的插件
2014/04/30 Javascript
jQuery学习笔记之 Ajax操作篇(三) - 过程处理
2014/06/23 Javascript
编写简单的jQuery提示插件
2014/12/21 Javascript
jQuery中ajax的load()方法用法实例
2014/12/26 Javascript
jquery实现先淡出再折叠收起的动画效果
2015/08/07 Javascript
javascript实现一个简单的弹出窗
2016/02/22 Javascript
基于Jquery插件实现跨域异步上传文件功能
2016/04/26 Javascript
在Web项目中引入Jquery插件报错的完美解决方案(图解)
2016/09/19 Javascript
Vuejs入门教程之Vue生命周期,数据,手动挂载,指令,过滤器
2017/04/19 Javascript
Angular4 中常用的指令入门总结
2017/06/12 Javascript
javascript 中模板方法单例的实现方法
2017/10/17 Javascript
JS实现分页浏览横向图片(类轮播)实例代码
2017/11/06 Javascript
JavaScript的setter与getter方法
2017/11/29 Javascript
Vue组件中slot的用法
2018/01/30 Javascript
小程序如何写动态标签的实现方法
2020/02/05 Javascript
react 生命周期实例分析
2020/05/18 Javascript
JavaScript对象访问器Getter及Setter原理解析
2020/12/08 Javascript
[02:36]DOTA2亚洲邀请赛小组赛精彩集锦:奇迹哥卡尔秀翻全场
2017/03/28 DOTA
python 生成不重复的随机数的代码
2011/05/15 Python
python类:class创建、数据方法属性及访问控制详解
2016/07/25 Python
python 删除字符串中连续多个空格并保留一个的方法
2018/12/22 Python
举例讲解Python常用模块
2019/03/08 Python
pytorch多GPU并行运算的实现
2019/09/27 Python
如何使用python代码操作git代码
2020/02/29 Python
Python Scrapy多页数据爬取实现过程解析
2020/06/12 Python
英国最大的独立家具零售商:Furniture Village
2016/09/06 全球购物
如何从一个文件档案的尾端新增记录
2016/12/02 面试题
司机岗位职责说明书
2014/07/29 职场文书
推普周活动总结
2014/08/28 职场文书
2014年车间主任工作总结
2014/12/10 职场文书