php利用 snowflake算法 生成唯一订单号(ID)


1:早期做唯一订单生成的时候结合了时间和随机数和后缀,以及redis写了一个方法,下面先贴代码 会给注释


    /**
     * 订单规则生成加排重
     * @param  string||integer $key 后缀,用来区分来源
     * @return string||function
     */
    protected function makeOrder($key)
    {

        // 判断redis有没有连接,因为我制作的框架,只有要用到redis或者mysql才会连接
        if (is_null(self::$redis_instance)) 
        {
            self::redis();
        }
        //构建订单号
        $order = @date("YmdHis") . mt_rand(1000, 9999) . $key;   

        // 订单号去重,如果同一秒生成的随机数一样,继续生成
        if( self::$redis_instance->get($order) )
        {
            return $this->makeOrder($key);
        }
        else
        {
            self::$redis_instance->setex($order, 60, 1);
            return $order;
        }
    }

利用上面的代码,一秒内生成5000个不重复的订单号,按道理是没有问题,但是并发高存在重复请求方法,造成不必要的内存消耗和增加接口的返回时间。

2:期间想过用 uniqid() 方法去实现,这样做不用redis ,但生成出来的有英文字母,我们想要纯数字就行不通

md5(uniqid(mt_rand(), true))

3:接下来隆重介绍 snowFlake算法,它可以生成不大于19位纯数字,分布式的唯一ID

snowflake算法来源于Twitter,使用scala语言实现,利用Thrift框架实现RPC接口调用,最初的项目起因是数据库从mysql迁移到Cassandra,Cassandra没有现成可用 的ID生成机制,就催生了这个项目,现有的github源码有兴趣可以去看看。

snowflake算法的特性是有序、唯一,并且要求高性能,低延迟(每台机器每秒至少生成10k条数据,并且响应时间在2ms以内),要在分布式环境(多集群,跨机房)下使用,因此snowflake算法得到的ID是分段组成的:

  • 与指定日期的时间差(毫秒级),41位,够用69年
  • 集群ID + 机器ID, 10位,最多支持1024台机器
  • 序列,12位,每台机器每毫秒内最多产生4096个序列号

如图所示:

php利用 snowflake算法 生成唯一订单号(ID)_第1张图片

  • 1bit:符号位,固定是0,表示全部ID都是正整数
  • 41bit:毫秒数时间差,从指定的日期算起,够用69年,我们知道用Long类型表示的时间戳是从1970-01-01 00:00:00开始算起的,咱们这里的时间戳可以指定日期,如2019-10-23 00:00:00
  • 10bit:机器ID,有异地部署,多集群的也可以配置,需要线下规划好各地机房,各集群,各实例ID的编号
  • 12bit:序列ID,前面都相同的话,最多可以支持到4096个

以上的位数分配只是官方建议的,我们可以根据实际需要自行分配,比如说我们的应用机器数量最多也就几十台,但并发数很大,我们就可以将10bit减少到8bit,序列部分从12bit增加到14bit等等

 

下面我直接贴代码:

 MAXWORKERID)) {
            return null;
        }
        $this->_workerId = $workerId;
    }

    public function next()
    {
        $ts = $this->timestamp();
        if ($ts == $this->_lastTimestamp) {
            $this->_sequence = ($this->_sequence + 1) & MAXSEQUENCE;
            if ($this->_sequence == 0) {
                $ts = $this->waitNextMilli($ts);
            }
        } else {
            $this->_sequence = 0;
        }

        if ($ts < $this->_lastTimestamp) {
            return 0;
        }

        $this->_lastTimestamp = $ts;

        $return_pack = $this->pack();
        if(strlen($return_pack) < 19) $return_pack = str_pad($return_pack, 19, '0');
        return $return_pack;
    }

    private function pack()
    {
        return ($this->_lastTimestamp << (NUMWORKERBITS + NUMSEQUENCEBITS)) | ($this->_workerId << NUMSEQUENCEBITS) | $this->_sequence;
    }

    private function waitNextMilli($ts)
    {
        if ($ts = $this->_lastTimestamp) {
            sleep(0.1);
            $ts = $this->timestamp();
        }

        return $ts;
    }

    private function timestamp()
    {
        return $this->millitime() - EPOCH;
    }

    private function millitime()
    {
        $microtime = microtime();
        $comps = explode(' ', $microtime);
        // Note: Using a string here to prevent loss of precision
        // in case of "overflow" (PHP converts it to a double)
        return sprintf('%d%03d', $comps[1], $comps[0] * 1000);
    }
}

下面是具体用法:

// 如果你有做负载均衡,不同的节点(不同的机器)传不同的key 即可
$key = 1;
$snowflake = new Snowflake($key);
$haha = $snowflake->next();
echo $haha; // 唯一ID

至此,可以生成 6656891297569587200 这样的唯一ID,真棒,奖励晚上跑步4公里

你可能感兴趣的:(PHP)