系统中某些场景少不了全局唯一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. MyBatis 3在XML文件中处理大于号小于号(<>)的方法(转)

    说明:以下方式支持XML和注解的方式. 一. 用了转义字符把>和<替换掉. AND start_date <= CURRENT_DATE AND end_date >= CUR ...

  2. N天学习一个linux命令之kill

    用途 用于终止进程 用法 kill [-s signal|-p] [--] pid... kill -l [signal] 说明 1.默认发送信号15(请求终止进程,程序可以捕获,操作系统会杀死没有对 ...

  3. Clojure:日期操作方法

    ;; 日期格式转换 (def df (java.text.SimpleDateFormat. "yyyy-MM-dd hh:mm:ss")) ;; 字符串转换到日期 (defn s ...

  4. 使用Django框架实现游戏站点搭建

    完整project链接点击打开链接 上一篇中我们使用了Javascript和Html5实现了弹球游戏.而在本文中我们希望以其为基础实现游戏站点,可以实现用户的注冊登录.游戏成绩记录,排名显示.微博分享 ...

  5. 蒟蒻的数位DP专题总结

    BZOJ  1026: [SCOI2009]windy数: 题目链接: http://www.lydsy.com/JudgeOnline/problem.php?id=1026           d ...

  6. oc26--Property,省略setget的声明

    // // Person.h #import <Foundation/Foundation.h> @interface Person : NSObject { int _age; } /* ...

  7. 【bzoj2038】[2009国家集训队]小Z的袜子(hose)(细致总结)

    [bzoj2038][2009国家集训队]小Z的袜子(hose)(细致总结) Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z ...

  8. B2568 比特集合 树状数组

    啊啊啊,跳题坑死人.抽了一道国集的题,自己瞎编了一个算法,好像过不了而半途而废.转去看题解,发现用二维树状数组维护一下,偏移量我倒是想对了,但是维护的东西和我的完全不一样.还是有很大差距啊... 题解 ...

  9. json用法

    什么是JSON? JavaScript 对象表示法(JavaScript Object Notation). JSON是一种轻量级的数据交换格式,某个JSON格式的文件内部譬如可以长成这样: 1 2 ...

  10. [转]"RDLC"报表-参数传递及主从报表

    本文转自:http://www.cnblogs.com/yjmyzz/archive/2011/09/19/2180940.html 今天继续学习RDLC报表的“参数传递”及“主从报表” 一.先创建D ...