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

    For each prefix of a given string S with N characters (each character has an ASCII code between 97 a ...

  2. [转]十五天精通WCF——第十二天 说说wcf中的那几种序列化

    我们都知道wcf是由信道栈组成的,在我们传输的参数走到传输信道层之前,先需要经过序列化的过程,也就是将参数序列化为message,这篇 我们就来说说这里的序列化,蛮有意思的,可能初学者也明白,在wcf ...

  3. 【从0開始Tornado建站】发表文章和评论

            先上个效果图: 这是每一个用户的主页.由于是基本功能.所以用户头像.爱好等信息都还没有,在下一阶段加上.右側"发表新文章"按钮点击后进入发表文章的页面: 之前尝试过 ...

  4. 高速搞定Eclipse的语法高亮

    编辑器背景颜色 打开Preferences 选择TextEditors 语法高亮配色 这里以Javascript为例. 选择Javascript 点击右边圈出的绿色框里的选项,适当改动颜色, 高亮色參 ...

  5. java调用百度地图API依据地理位置中文获取经纬度

    百度地图api提供了非常多地图相关的免费接口,有利于地理位置相关的开发,百度地图api首页:http://developer.baidu.com/map/. 博主使用过依据地理依据地理位置中文获取经纬 ...

  6. log_archive_dest_1设置报错

    DG搭建完之后,又报错: Tue Dec 22 16:24:33 2015 Errors in file /u01/app/oracle/admin/orcl/bdump/orcl_arc1_2994 ...

  7. 深入分析JavaWeb Item47 -- Struts2拦截器与文件上传下载

    一.struts2中的拦截器(框架功能核心) 1.过滤器VS拦截器 过滤器VS拦截器功能是一回事. 过滤器是Servlet规范中的技术,能够对请求和响应进行过滤. 拦截器是Struts2框架中的技术. ...

  8. 我在SharePoint行业的从业经历(二)

     本文是我的SharePoint从业经历的第二篇,第一篇请參考 我在SharePoint行业的从业经历(一) 做完那个项目之后.对SharePoint 2003有了一些认识. 可是后来几年我就没在 ...

  9. Android+Jquery Mobile学习系列(6)-个人信息设置

    本节开始,进行代码的实战练习.我的这个App是管理保险客户信息的,数据采用Sqlite存储在本地手机上,第一次使用需要先登记自己的个人信息,这个功能非常简单,也无关紧要,我是拿这个练手,方便做后面复杂 ...

  10. nyoj--496--巡回赛(拓扑排序)

    巡回赛 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 世界拳击协会(WBA)是历史最悠久的世界性拳击组织,孕育了众多的世界冠军,尤其是重量级,几乎造就了大家耳熟能详的所 ...