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新手入门学习方法
May 08 PHP
深入php-fpm的两种进程管理模式详解
Jun 03 PHP
php自动加载机制的深入分析
Jun 08 PHP
浅析PHP微信支付通知的处理方式
May 25 PHP
destoon供应信息title调用出公司名称的方法
Aug 22 PHP
php使用fgetcsv读取csv文件出现乱码的解决方法
Nov 08 PHP
一个比较不错的PHP日历类分享
Nov 18 PHP
Yii实现多按钮保存与提交的方法
Dec 03 PHP
基于PHP实现的事件机制实例分析
Jun 18 PHP
PHP实现数据分页显示的简单实例
May 26 PHP
Laravel框架分页实现方法分析
Jun 12 PHP
php提取微信账单的有效信息
Oct 01 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类的使用 实例代码讲解
2009/12/28 PHP
关于mysql字符集设置了character_set_client=binary 在gbk情况下会出现表描述是乱码的情况
2013/01/06 PHP
php实现webservice实例
2014/11/06 PHP
phpmailer绑定邮箱的实现方法
2016/12/01 PHP
PHP实现的折半查找算法示例
2017/12/19 PHP
php实现微信支付之企业付款
2018/05/30 PHP
通过Mootools 1.2来操纵HTML DOM元素
2009/09/15 Javascript
关于UTF-8的客户端用AJAX方式获取GB2312的服务器端乱码问题的解决办法
2010/11/30 Javascript
读jQuery之十三 添加事件和删除事件的核心方法
2011/08/23 Javascript
javascript的渐进增强与平稳退化浅谈
2013/11/12 Javascript
js 判断浏览器使用的语言示例代码
2014/03/22 Javascript
js轮播图代码分享
2016/07/14 Javascript
探索Vue.js component内容实现
2016/11/03 Javascript
Angular中使用ng-zorro图标库部分图标不能正常显示问题
2019/04/22 Javascript
bootstrap table列和表头对不齐的解决方法
2019/07/19 Javascript
在vue项目实现一个ctrl+f的搜索功能
2020/02/28 Javascript
微信小程序实现列表的横向滑动方式
2020/07/15 Javascript
[02:41]辉夜杯现场一家三口 “我爸玩风行 我玩血魔”
2015/12/27 DOTA
[02:29]大剑、皮鞭、女装,这届DOTA2勇士令状里都有
2020/07/17 DOTA
python装饰器使用方法实例
2013/11/21 Python
python 爬虫 批量获取代理ip的实例代码
2018/05/22 Python
详解用python实现基本的学生管理系统(文件存储版)(python3)
2019/04/25 Python
解决python文件双击运行秒退的问题
2019/06/24 Python
TensorBoard 计算图的可视化实现
2020/02/15 Python
python 实现分组求和与分组累加求和代码
2020/05/18 Python
pycharm中如何自定义设置通过“ctrl+滚轮”进行放大和缩小实现方法
2020/09/16 Python
Maje德国官网:法国女性成衣品牌
2017/02/10 全球购物
澳大利亚正品化妆品之家:Cosmetic Capital
2017/07/03 全球购物
加拿大著名的奢侈品购物网站:SSENSE(支持中文)
2020/06/25 全球购物
解除劳动合同协议书范本
2014/04/14 职场文书
党员个人自我剖析材料
2014/10/08 职场文书
银行开户授权委托书格式
2014/10/10 职场文书
python批量更改目录名/文件名的方法
2021/04/18 Python
MySQL数据库必备之条件查询语句
2021/10/15 MySQL
Python Pygame实战在打砖块游戏的实现
2022/03/17 Python
Linux下使用C语言代码搭建一个简单的HTTP服务器
2022/04/13 Servers