前言:
很多朋友面试的时候都会被问道 百万计或千万级数据应该如何存储,其ID又该如何做到唯一的问题。
自增ID其实有非常多的方法,下面四种是比较常见的
- 数据库自增ID、比如mongo、mysql;如果使用Mysql的话还可以使用集群模式,不同集群步长不同
- Redis的incr命令,但是持久化很难保证
- 雪花算法:时间戳+机器ID+自增ID;可以使用cas
- 号段模式
我这里就简单介绍下雪花算法的实现方式,具体实现原理并不难,这里便不赘述:
实现:
一般我们封装算法函数都建议放在Servers(服务层)中,这样后续代码量或逻辑复杂的时候就不会像两千年的代码那样难看了。
1. 在服务层中 创建SnowFlakeServe.php文件
<?php
namespace App\Http\Servers;
/**
* 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 SnowFlakeServe
{
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) < 13) $return_pack = str_pad($return_pack, 13, '0');
// return $return_pack;
return substr($return_pack, 0, 14);
}
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);
}
}
2. 在逻辑层中调用方法
$key = 1;
$snowflake = new SnowFlakeServe($key);
$drawal_sn = 'F' . $snowflake->next();
这个示例生成的是订单编号