ID生成器-SnowFlake

Posted by

ID生成器针对于日常业务来说,遇到的太频繁了,这里记录一下雪花算法。

雪花算法一共由64位组成:

第1位:未使用

后41位:时间戳

后10位:机器ID

后12位:随机数

在实际的使用过程中,第一位未使用,用0表示。

时间戳范围:假设我们直接使用当前时间戳毫秒级来记录,比如现在是2021-04-04 12:00:00.674,即1617508800674。41位的长度可以涵盖的时间长度(以某一时间为起点,记录时间差)的即(2^41 – 1) / (86400 * 365 * 1000) = 69 年,如果直接将第一位挪出来使用,则可以扩展到139年。

机器ID范围:10位表示,则可以涵盖1023台机器。

后12位:这里的取数是针对一毫秒的并发数据量,我看到有的直接使用随机数来表示,那从严格意义上来讲,这里还是有碰撞的可能。后面我在其他的地方里看到另外一种处理方案:基于信号量和共享内存处理。

这里贴一下核心代码。

    // 简版数据组成
    // 里面有两种方案,一种是基于数的乘法来得到一个数之后,然后利用字符串拼接的方法
    // 另一种直接利用了二进制,通过位移来得到乘法的效果,最后按位或,得到最终的ID
    public function createID(): string
    {
        // 假设一个机器id
        $machineId = 1;
        // 当前时间
        [$uSec, $sec] = explode(' ', microtime());

        // 41bit timestamp(毫秒)
        $time = floor(($sec + $uSec) * 1000);

        # 0bit 未使用
        # $suffix = 0 << 63;

        // datacenterId  添加数据的时间
        # 乘法方案:
        # $base = decbin(pow(2, 40) - 1 + $time);
        // 位移方案:22位,机器ID的位数10+随机数的位数12
        $base = $time << 22;

        // workerId  机器ID
        # 乘法方案:
        # $machineid = decbin(pow(2, 9) - 1 + $machineId);
        // 利用位移,机器ID,左移12位
        $machineId = $machineId << 12;

        // 随机数
        $random = $this->getMuxUid($sec);

        # $random = decbin(pow(2, 11) - 1 + $random);
        $random = decbin($random);
        // 拼装所有数据
        # 乘法方案,利用拼接
        # $base64 = $suffix . $base . $machineid . $random;
        // 按位或,高位的每一个区间与低位的每一个区间按位或,相当于加法
        $base64 = $base | $machineId | $random;
        # 拼接模式下,将二进制转换int
        # $base64 = bindec($base64);

        return $base64;
    }

    // 基于共享内存与信号量产生ID
    public function getMuxUid(int $timestamp): int
    {
        $key = ftok(__FILE__, 'p');
        // connect一个共享内存,同时设置大小100kb
        $shmKey = shm_attach($key, 102400);
        // 保存当前累计计数,key为1,调用次数为值
        shm_put_var($shmKey, 1, (@shm_get_var($shmKey, 1) ?: 0) + 1);

        // 获取一个信号量
        $semKey = sem_get($key);
        // 请求信号量,这里如果存在堵塞,会有一个等待
        if (sem_acquire($semKey)) {
            // 判断当前时间戳是否有存储过
            if (shm_has_var($shmKey, $timestamp)) {
                // 存在则获取出来
                $id = shm_get_var($shmKey, $timestamp);
                // 同时自增
                $id++;
            } else {
                // 否则初始化ID
                $id = 1;
            }
            // 将当前ID再次放入内存
            shm_put_var($shmKey, $timestamp, $id);

            // 判断调用次数是否超过1000
            if (shm_get_var($shmKey, 1) > 1000) {
                // 超过则remove
                shm_remove($shmKey);
            } else {
                // 否则就disconnect
                shm_detach($shmKey);
            }
            return $id;
        }
        return 0;
    }

参考文章

 

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注