系统中某些场景少不了全局唯一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. java 的collection

    参考:http://skyuck.iteye.com/blog/526358 https://www.tutorialspoint.com/java/java_collections.htm Prio ...

  2. H3C交换机经常使用命令汇总

    H3C交换机经常使用命令 1.查看Linux下查看port状态 root@root:~# netstat -an|grep -E "6002|6003" 2.H3C交换机显示当前配 ...

  3. android之GMS认证

    来到了新的公司,才知道做手机是须要做GMS认证的.于是从一个从没有做过GMS认证的小白到一个月做了8个项目的GMS认证.最后.自己都是吐了.每天晚上都是一个人傻傻在加班.更是知道了高通的支持力度让人发 ...

  4. 操作系统: 二级文件夹文件系统的实现(c/c++语言)

    操作系统的一个课程设计,实现一个二级文件夹文件系统. 用disk.txt模拟磁盘,使用Help查看支持的命令及其操作方式,root为超级用户(写在disk.txt中) 文件的逻辑结构:流式文件. 物理 ...

  5. alsa 用户空间编程【转】

    本文转载自:http://blog.csdn.net/sjin_1314/article/details/12872581 /**alsa play test *ALSA用户空间编译,ALSA驱动的声 ...

  6. Android定时任务

    前言 我们在平常的开发中可能会遇到一些需求,比如说,每日定时提醒,定时更新数据等等,反正就是周期性任务,碰到这类需求,我们就可以叫做定时任务.以前我们可以通过使用线程Handler来实现,现在既然是在 ...

  7. codeforces 782B The Meeting Place Cannot Be Changed+hdu 4355+hdu 2438 (三分)

                                                                   B. The Meeting Place Cannot Be Change ...

  8. poj3926

    dp+优化 很明显可以用单调队列优化. 记录下自己犯的sb错误:  数组开小,sum没搞清... #include<cstdio> #include<cstring> usin ...

  9. GROUPPING和ROLLUP的基本知识

    1.GROUPPING 是一个聚合函数,它产生一个附加的列,当用 CUBE 或 ROLLUP 运算符添加行时,附加的列输出值为1,当所添加的行不是由 CUBE 或 ROLLUP 产生时,附加列值为0. ...

  10. Java 最基础的三种排序排序

    主要讲五个排序,冒泡排序.选择排序.插入排序 1)冒泡排序 /** * 冒泡排序 (默认升序排) * 相邻两个相比较,较大的向后放,从数组或者集合中的第一个与第二个比较到倒数第二个与最后一个比较为一轮 ...