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 10 PHP
php设计模式 Mediator (中介者模式)
Jun 26 PHP
PHP多个版本的分析解释
Jul 21 PHP
php中json_encode中文编码问题分析
Sep 13 PHP
关于访问控制的一首PHP面试题(对属性或方法的访问控制)
Sep 13 PHP
php读取目录及子目录下所有文件名的方法
Oct 20 PHP
PHP 实现判断用户是否手机访问
Jan 21 PHP
php通过修改header强制图片下载的方法
Mar 24 PHP
PHP基于cookie与session统计网站访问量并输出显示的方法
Jan 15 PHP
PHP定时任务获取微信access_token的方法
Oct 10 PHP
PHP的mysqli_select_db()函数讲解
Jan 23 PHP
php如何实现数据库的备份和恢复
Nov 30 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下使用strpos需要注意 === 运算符
2010/07/17 PHP
新手学习PHP的一些基础知识分享
2011/07/27 PHP
php通过记录IP来防止表单重复提交方法分析
2014/12/16 PHP
WordPress中注册菜单与调用菜单的方法详解
2015/12/18 PHP
解决laravel id非自增 模型取回为0 的问题
2019/10/11 PHP
javascript 鼠标拖动图标技术
2010/02/07 Javascript
使用JS画图之点、线、面
2015/01/12 Javascript
jquery动态改变div宽度和高度
2015/02/09 Javascript
jquery表单验证需要做些什么
2015/11/17 Javascript
分享纯手写漂亮的表单验证
2015/11/19 Javascript
Markdown与Bootstrap相结合实现图片自适应属性
2016/05/04 Javascript
JS弹出窗口插件zDialog简单用法示例
2016/06/12 Javascript
Google Maps基础及实例解析
2016/08/06 Javascript
原生js轮播(仿慕课网)
2017/02/15 Javascript
.net MVC+Bootstrap下使用localResizeIMG上传图片
2017/04/21 Javascript
如何重置vue打印变量的显示方式
2017/12/06 Javascript
重学JS 系列:聊聊继承(推荐)
2019/04/11 Javascript
详解vue-cli3开发Chrome插件实践
2019/05/29 Javascript
基于Express框架使用POST传递Form数据
2019/08/10 Javascript
Vue的编码技巧与规范使用详解
2019/08/28 Javascript
Vue的生命周期操作示例
2019/09/17 Javascript
Vue数据双向绑定原理实例解析
2020/05/15 Javascript
小程序组件传值和引入sass的方法(使用vant Weapp组件库)
2020/11/24 Javascript
[04:09]2014DOTA2国际邀请赛Ti西雅图 历届冠军相继出局 BBC综述今日比赛
2014/07/20 DOTA
Python操作串口的方法
2015/06/17 Python
Python基于FTP模块实现ftp文件上传操作示例
2018/04/23 Python
python安装gdal的两种方法
2019/10/29 Python
Python实现名片管理系统
2020/02/14 Python
Python通过两个dataframe用for循环求笛卡尔积
2020/04/29 Python
德国狗狗用品在线商店:Schecker
2017/03/17 全球购物
党员个人自我剖析材料
2014/10/08 职场文书
2015年秋季小班开学寄语
2015/05/27 职场文书
挂职锻炼工作总结2015
2015/05/28 职场文书
2019年暑期安全广播稿!
2019/07/03 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL
MySQL详解进行JDBC编程与增删改查方法
2022/06/16 MySQL