分布式必备发号器-SnowFlake算法原理详解

发号器

生成唯一id的需求很多,我们经常会用到,不管是单库单表中的唯一,还是分布式的唯一。

SnowFlake 算法

说一下SnowFlake算法,这个算法是一个生成唯一id的算法。

使用的是一个64位的二进制串,把这个串分成了几个部分。

  • 符号位 占一个位置 0 为正
  • 时间戳位 占41个位置,使用毫秒级时间戳
  • 机器位 占10个位置, 可以支持2的10次方-1个机器使用
  • 序号位 占12个位置, 同一毫秒内可以生成2的12次方-1个id

但是我们的业务很少用到这个级别的发号器,所以可以把时间改为秒级,下面是我改版后的SnowFlake算法组成:

  • 符号位 占一个位置 0 为正
  • 时间戳位 占38个位置,使用秒级时间戳
  • 机器位 占5个位置, 可以支持2的5次方-1个机器使用
  • 业务位 占8个位置, 可以支持2的8次方-1个业务
  • 序号位 占12个位置, 同一毫秒内可以生成2的12次方-1个id

我这里面增加了业务位,因为这样可以把每个业务都分开,保证每个业务每个机器每秒内可以生成1024个id。

看一下我们php的实现代码

<?php

class SnowFlake {

    const FIRST = 0; //首位 符号位 表示正负0为正 1为负

    const TIME_LENGTH = 38; //时间戳位数 二进制的位数

    const MACHINE_LENGTH = 5; //机器码位数 二进制的位数

    const BUSINESS_LENGTH = 8; //业务位数 二进制的位数

    const SEQUENCE_LENGTH = 12; //序列号位数 二进制的位数

    private $machineId = 1;

    //上一次发号时间
    private $oldTime;

    private $sequence;

    //业务对应的业务id
    private $businessArr = [
        'order' => 1,
    ];

    function __construct($machineId = 1)
    {
        if (strlen(decbin($machineId)) > self::MACHINE_LENGTH) {
            return '机器id超长!';
        }
        $this->machineId = $machineId;
        //初始化时间戳
        $this->oldTime = $this->getTime();
        //初始化序号
        $this->sequence = 1;
    }

    /**
     * 生成唯一id
     * @param string $businessType 业务类型
     */
    function generate($businessType) {
        $time = $this->getTime();
        //比较时间戳
        if ($time == $this->oldTime) {
            //在同一毫秒内创建,序号递增
            if (strlen(decbin($this->sequence)) >= self::SEQUENCE_LENGTH) {
                return '到达最大发号个数';
            }
            ++$this->sequence; 
        } else {
            //到达下一个时间,重置序号
            $this->sequence = 1;
        }
        
        $businessId = $this->getBusinessId($businessType);

        //字符位偏移量
        $firstShift = self::TIME_LENGTH + self::MACHINE_LENGTH + self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
        //时间戳偏移量
        $timeShift = self::MACHINE_LENGTH + self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
        //机器位偏移量
        $machineShift = self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
        //业务偏移量
        $businessShift = self::SEQUENCE_LENGTH;

        $res = self::FIRST << $firstShift | $time << $timeShift | $this->machineId << $machineShift | $businessId << $businessShift | $this->sequence;

        //写入时间
        $this->oldTime = $time;
        return $res;
    }

    /**
     * 获取毫秒级时间戳
     */
    function getTime() {
        return time();
    }

    /**
     * 获取业务id
     */
    function getBusinessId($businessType) {
        return $this->businessArr[$businessType];
    }
} 

最后一步是把每个部分归位,左移相应的偏移量,到达相应的位置,比如时间戳:

1011110100000010111110100010100 << 25 = 10111101000000101111101000101000000000000000000000000000

机器位

1 << 20 = 100000000000000000000

业务位

1 << 12 = 1000000000000

序号位不用左移

最后将这些进行或操作,二进制的或操作就是将每一位进行对比,如果都是0则返回0,有一个1就返回1,两个都是1也返回1。

时间戳或上机器位就是这个结果,这也相当于两个加在一起了
10111101000000101111101000101000000000000000000000000000 | 100000000000000000000 = 10111101000000101111101000101000000100000000000000000000

再或上业务位和符号位

10111101000000101111101000101000000100000000000000000000 | 1000000000000 | 1 = 10111101000000101111101000101000000100000010000000000001

最后把每部分或操作之后就是我们要的最终结果,不管是左移操作还是或操作,实际上都是对变量里面的二进制进行的操作,操作之后会转换成我们看到的十进制消息,也就是53202044035534850

在这里面还用到了decbin()这个函数,这个函数的作用就是将十进制转换成二进制。

我们使用这个发号器生成的唯一id隐含了我们的业务性,也保证了唯一性,如果你的业务很庞大,可以使用毫秒级时间戳或者扩大序号位,都可以根据实际使用情况调整。

下面是这个算法的github地址

https://github.com/Thepatterraining/design-pattern/tree/master/app/Http/Models/SnowFlake

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值