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个序列号
如图所示:
- 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等等
下面我直接贴代码:
<?php
/**
* Snowflake 生成唯一ID算法,固定返回19位
* User: zc
* Date: 2020/7/22
* Time: 15:29
*/
define('EPOCH', 1579533469598); // 当前时间(毫秒)
define('NUMWORKERBITS', 10);
define('NUMSEQUENCEBITS', 12);
define('MAXWORKERID', (-1 ^ (-1 << NUMWORKERBITS))); // 集群ID + 机器ID, 10位,最多支持1024台机器
define('MAXSEQUENCE', (-1 ^ (-1 << NUMSEQUENCEBITS))); // 序列,12位,每台机器每毫秒内最多产生4096个序列号
class Snowflake
{
private $_lastTimestamp;
private $_sequence = 0;
private $_workerId = 1;
public function __construct($workerId)
{
if (($workerId < 0) || ($workerId > 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公里