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中的串行化变量和序列化对象
Sep 05 PHP
php 无限极分类
Mar 27 PHP
PHP编程过程中需要了解的this,self,parent的区别
Dec 30 PHP
php中防止SQL注入的最佳解决方法
Apr 25 PHP
PHP设计模式之解释器模式的深入解析
Jun 13 PHP
php中让上传的文件大小在上传前就受限制的两种解决方法
Jun 24 PHP
PHP图像处理类库MagickWand用法实例分析
May 21 PHP
简单谈谈PHP vs Node.js
Jul 17 PHP
php实现socket推送技术的示例
Dec 20 PHP
TP5(thinkPHP5框架)基于bootstrap实现的单图上传插件用法示例
May 29 PHP
tp5框架前台无限极导航菜单类实现方法分析
Mar 29 PHP
tp5使用layui实现多个图片上传(带附件选择)的方法实例
Nov 17 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
PHP内核探索:变量概述
2014/01/30 PHP
分享下php5类中三种数据类型的区别
2015/01/26 PHP
php使用curl模拟浏览器表单上传文件或者图片的方法
2018/11/10 PHP
[IE&amp;FireFox兼容]JS对select操作
2007/01/07 Javascript
子窗口、父窗口和Silverlight之间的相互调用
2010/08/16 Javascript
jQuery ajax 路由和过滤器使用说明
2011/08/02 Javascript
网页右下角弹出窗体实现代码
2014/06/05 Javascript
js实现回放拖拽轨迹从过程上进行分析
2014/06/26 Javascript
基于jQuery实现复选框是否选中进行答题提示
2015/12/10 Javascript
AngularJS通过$sce输出html的方法
2016/09/22 Javascript
基于slideout.js实现移动端侧边栏滑动特效
2016/11/28 Javascript
Ionic + Angular.js实现图片轮播的方法示例
2017/05/21 Javascript
js的继承方法小结(prototype、call、apply)(推荐)
2019/04/17 Javascript
使用watch在微信小程序中实现全局状态共享
2019/06/03 Javascript
前端Electron新手入门教程详解
2019/06/21 Javascript
微信小程序实现Swiper轮播图效果
2019/11/22 Javascript
JavaScript简单编程实例学习
2020/02/14 Javascript
JS如何实现手机端输入验证码效果
2020/05/13 Javascript
JS 数组和对象的深拷贝操作示例
2020/06/06 Javascript
详解js中的原型,原型对象,原型链
2020/07/16 Javascript
Vue实现多页签组件
2021/01/14 Vue.js
[01:10]DOTA2英雄背景故事第四期之混沌法则混沌骑士
2020/07/16 DOTA
Python的for和break循环结构中使用else语句的技巧
2016/05/24 Python
PyGame贪吃蛇的实现代码示例
2018/11/21 Python
Python企业编码生成系统之主程序模块设计详解
2019/07/26 Python
python 实现单例模式的5种方法
2020/09/23 Python
html5跨域通讯之postMessage的用法总结
2013/11/07 HTML / CSS
澳大利亚新奇小玩意网站:Yellow Octopus
2017/12/28 全球购物
波兰运动鞋网上商店:Distance.pl
2020/07/30 全球购物
Java的接口和C++的虚类的相同和不同处
2014/03/27 面试题
医学院毕业生自荐信
2013/11/08 职场文书
新闻编辑专业自荐信
2014/07/02 职场文书
2014预防青少年违法犯罪工作总结
2014/12/10 职场文书
英文自荐信范文
2015/03/25 职场文书
2019思想汇报范文
2019/05/21 职场文书
Python编程根据字典列表相同键的值进行合并
2021/10/05 Python