系统中某些场景少不了全局唯一ID的使用,来保证数据的唯一性。除了通过数据库自带的自增id来保证 id 的唯一性,通常为了保证的数据的可移植性会选择通过程序生成全局唯一 id。
百度了不少php相关的生成方式,得到的大多是随机字符串和内置的 uniqid()函数。不过经过 ab 测试,在并发情况下重复度是很高的。
偶然看到了一篇 Twitter的分布式自增ID算法 snowflake 的文章,得到的全局唯一都是纯数字。这一点对于数据库来说,在此列上创建索引并通过此字段关联查询的时候是比较好的。

需要值得注意的是:snowflake的结构是64位的,所以需要在64位的PHP上运行。可以通过 phpinfo() 查看PHP的位数。一般 Linux 上的 PHP都可以满足。

下面是相关代码:

<?php

/***
 * Author:阿远
 * Class SnowflakeIdWorker
 */
class SnowflakeIdWorker{

    /** 开始时间截 (2018-01-01) */
    const twepoch = 1514736000000;

    /** 机器id所占的位数 */
    const workerIdBits = 10;

    //支持的最大机器id,结果是1023 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    const maxWorkerId = (-1 ^ (-1 << self::workerIdBits));

    //序列在id中占的位数
    const sequenceBits = 12;

    //机器ID向左移12位
    const workerIdShift = self::sequenceBits;

    //时间截向左移22位(10+12)
    const timestampLeftShift = self::workerIdBits  + self::sequenceBits;

    //序列号值的最大值,这里为4095 (0b111111111111=0xfff=4095)
    const sequenceMask = (-1 ^ (-1 << self::sequenceBits));

    //工作机器ID(0~1023):默认0
    private $workerId = 0;

    //毫秒内序列(0~4095):标识符,常驻内存
    static $sequence = 0 ;

    //上次生成ID的时间截
    static $lastTimestamp = -1;

    /***
     * 构造函数:设置当前机器id
     * SnowflakeIdWorker constructor.
     * @param $workerId
     */
    public function __construct($workerId)
    {
        //转换类型
        $workerId = (int) $workerId;

        //判断参数合法性
        if($workerId < 0 || $workerId > self::maxWorkerId){
            die('error...');
        }

        //设置当前机器id
        $this->workerId = $workerId;
    }

    public function nextId(){
        //获取当前毫秒时间戳
        $timestamp = $this->timeGen();
        //获取上一次生成id时的毫秒时间戳
        $lastTimestamp = self::$lastTimestamp;

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if($timestamp < $lastTimestamp){
            die('error...');
        }

        //如果是同一毫秒内生成的,则进行毫秒序列化
        if($timestamp == $lastTimestamp){
            //获取当前序列号值
            self::$sequence = (self::$sequence + 1) & self::sequenceMask;
            //毫秒序列化值溢出(就是超过了4095)
            if(self::$sequence == 0){
                //阻塞到下一秒,获得新的时间戳
                $timestamp = $this->tilNextMillis($lastTimestamp);
            }
        }
        //如果不是同一毫秒,那么重置毫秒序列化值
        else{
            self::$sequence = 0;
        }

        //重置上一次生成的时间戳
        self::$lastTimestamp = $timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return
            //时间戳左移 22 位
            (($timestamp - self::twepoch) << self::timestampLeftShift) |
            //机器id左移 12 位
            ($this->workerId << self::workerIdShift) |
            //或运算序列号值
            self::$sequence;
    }

    /****
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param $lastTimestamp 上次生成ID的时间截
     * @return float 当前毫秒时间戳
     */
    private function tilNextMillis($lastTimestamp){
        //重新获取当前时间戳
        $timestamp = $this->timeGen();
        //如果等于上一次获取的时间戳,仍然重新获取
        while($timestamp <= $lastTimestamp){
            $timestamp = $this->timeGen();
        }
        //返回新的时间戳
        return $timestamp;
    }

    /***
     * 返回当前毫秒时间戳
     * @return float
     */
    private function timeGen(){
        return  (float)sprintf("%.0f", microtime(true) * 1000);
    }

}

//调用
header("Content-Type: text/html; charset=utf-8");
//
$work1 = new SnowflakeIdWorker(1);
for($i=0; $i<10;$i++) {
    echo $i."--".$work1->nextId()."<br/>";
}

参考资料:Twitter的分布式自增ID算法snowflake

雪花算法生成全局唯一ID的更多相关文章

  1. 如何在高并发分布式系统中生成全局唯一Id

    月整理出来,有兴趣的园友可以关注下我的博客. 分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案.我和我的小伙伴们也讨论了这个主题,我受益匪浅啊…… 博文示例: 1.     ...

  2. 如何在高并发分布式系统中生成全局唯一Id(转)

    http://www.cnblogs.com/heyuquan/p/global-guid-identity-maxId.html 又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文, ...

  3. (转)如何在高并发分布式系统中生成全局唯一Id

    又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...

  4. 常见的生成全局唯一id有哪些?他们各有什么优缺点?

    分布式系统中全局唯一id是我们经常用到的,生成全局id方法由很多,我们选择的时候也比较纠结.每种方式都有各自的使用场景,如果我们熟悉各种方式及优缺点,使用的时候才会更方便.下面我们就一起来看一下常见的 ...

  5. 面试官:如何在分布式场景下生成全局唯一 ID?

    在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或者用作事务版本号.分布式链路追踪等等,好的全局唯 ...

  6. 生成全局唯一ID

    在实际业务处理中,有时需要生成全局唯一ID来区别同类型的不同事物,介绍一下几种方式及其C++实现 //获取全局唯一ID //server_id为服务的id,因当同一个服务部署在多个服务器上时,需要区别 ...

  7. 高并发分布式系统中生成全局唯一Id汇总

    数据在分片时,典型的是分库分表,就有一个全局ID生成的问题.单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求:   1 不能有单点故障.   2 以时间为序,或者ID里包含时间 ...

  8. 游戏服务器生成全局唯一ID的几种方法

    在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动id了,而是需要在插入数据库之前生成一个全局的唯一id,使 ...

  9. SnowFlake 生成全局唯一id

    public class SnowFlakeUtil { private long workerId; private long datacenterId; private long sequence ...

随机推荐

  1. pyenv-virtualenv环境搭建

    搞了个新服务器,搭个python环境 安装pyenv 直接上懒人脚本,不怕麻烦想手动装的就麻烦您自己查吧~ curl -L https://raw.githubusercontent.com/yyuu ...

  2. 【ACM】poj_1579_Function Run Fun_201308121654

    Function Run FunTime Limit: 1000MS  Memory Limit: 10000K Total Submissions: 14940  Accepted: 7736 De ...

  3. 查看TEMP 表空间usage

    SELECT S.sid || ','|| S.serial# sid_serial, S.username, S.osuser, P.spid, S.module, P.program, SUM ( ...

  4. LeetCode258——Add Digits

    Given a non-negative integer num, repeatedly add all its digits until the result has only one digit. ...

  5. HDU 2348

    DFS+博弈. 假设存在两数(x,y),且x<y.对于(x+ky,y)k>=2,只能转移向两种状态(x+y,y),或者(x,y).而对于(x+y,y)只能向(x,y)转移,那么,可知,无论 ...

  6. 粗结MySql数据库基础知识点之一

    首先弄什么是数据库? 数据库就是用来存储和管理数据的仓库. 数据库存储数据的优点: 1.可存储大量的数据  2.方便检索  3.保持数据的一致性,完整性  4.安全 可共享  5.通过组合分析,可以产 ...

  7. linux下获取按键响应事件【转】

    本文转载自:https://my.oschina.net/u/157503/blog/91548 1.问题 通过一个死循环将读取键盘对应的设备文件将触发键盘事件在屏幕上打印出来,按esc退出程序 代码 ...

  8. LuoguP4365 [九省联考2018]秘密袭击

    https://zybuluo.com/ysner/note/1141136 题面 求一颗大小为\(n\)的树取联通块的所有方案中,第\(k\)个数之和. \(n\leq1,667,k\leq n\) ...

  9. bzoj 1034 [ ZJOI 2008 ] 泡泡堂BNB —— 贪心

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1034 一开始想了个很麻烦的贪心做法,对于每个 a[i],找第一个大于它的 b 匹配…… 然后 ...

  10. autofac的小知识点

    autofac 注入中i遇到的泛型传参问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ...