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 网页游戏开发入门教程一(webgame+design)
Oct 26 PHP
THINKPHP+JS实现缩放图片式截图的实现
Mar 07 PHP
百度站点地图(百度sitemap)生成方法分享
Jan 09 PHP
PHP mkdir()无写权限的问题解决方法
Jun 19 PHP
ThinkPHP的模版中调用session数据的方法
Jul 01 PHP
Zend Framework创建自己的动作助手详解
Mar 05 PHP
php版微信自动获取收货地址api用法示例
Sep 22 PHP
Ajax实现对静态页面的文章访问统计功能示例
Oct 10 PHP
thinkPHP框架实现图像裁剪、缩放、加水印的方法
Mar 14 PHP
PHP中TP5 上传文件的实例详解
Jul 31 PHP
php使用curl模拟浏览器表单上传文件或者图片的方法
Nov 10 PHP
解决在laravel中leftjoin带条件查询没有返回右表为NULL的问题
Oct 15 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
实现分十页分向前十页向后十页的处理
2006/10/09 PHP
树型结构列出指定目录里所有文件的PHP类
2006/10/09 PHP
使用PHP实现二分查找算法代码分享
2011/06/24 PHP
thinkphp文件引用与分支结构用法实例
2014/11/26 PHP
是 WordPress 让 PHP 更流行了 而不是框架
2016/02/03 PHP
php基于curl主动推送最新内容给百度收录的方法
2016/10/14 PHP
js 数组操作代码集锦
2009/04/28 Javascript
jquery.cookie.js 操作cookie实现记住密码功能的实现代码
2011/04/27 Javascript
判断对象是否Window的实现代码
2012/01/10 Javascript
javascript针对DOM的应用分析(二)
2012/04/15 Javascript
Javascript操作URL函数修改版
2013/11/07 Javascript
jquery和雅虎的yql服务实现天气预报服务示例
2014/02/08 Javascript
JS IOS/iPhone的Safari浏览器不兼容Javascript中的Date()问题如何解决
2016/11/11 Javascript
网络传输协议(http协议)
2016/11/18 Javascript
浅析jQuery操作select控件的取值和设值
2016/12/07 Javascript
vue移动端裁剪图片结合插件Cropper的使用实例代码
2017/07/10 Javascript
Node.js利用断言模块assert进行单元测试的方法
2017/09/28 Javascript
在Mac下彻底卸载node和npm的方法
2018/05/16 Javascript
微信小程序实现城市列表选择
2018/06/05 Javascript
详解几十行代码实现一个vue的状态管理
2019/01/28 Javascript
深入了解js原型模式
2019/05/30 Javascript
python实现数独游戏 java简单实现数独游戏
2018/03/30 Python
解读python如何实现决策树算法
2018/10/11 Python
深入浅析Python2.x和3.x版本的主要区别
2018/11/30 Python
详解python中list的使用
2019/03/15 Python
python并发编程 Process对象的其他属性方法join方法详解
2019/08/20 Python
YII2 全局异常处理深入讲解
2021/03/24 PHP
个人查摆剖析材料
2014/02/04 职场文书
高中生家长寄语大全
2014/04/03 职场文书
小学生读书活动总结
2014/06/30 职场文书
党员演讲稿
2014/09/04 职场文书
2014领导班子四风问题对照检查材料思想汇报
2014/09/21 职场文书
入党现实表现材料
2014/12/23 职场文书
2015年导购员工作总结
2015/04/25 职场文书
使用feign服务调用添加Header参数
2021/06/23 Java/Android
世界各国短波电台对东亚播送时间频率表(SW)
2021/06/28 无线电