开发的大多数项目并未接触到百万级并发,但是一直以来有个id生成的纠结。我一般就是年月日+随机数生成唯一id,一般来说生成一个备用随机数据,不重复每次一次取值隔天重新从第一个开始取。虽然是可以解决,总感觉这个有点繁琐,仔细查查资料研究了一下,在这里有提到uuid、Vesta、Twitter-Snowflake等。从文章中uuid在php中已经有专门的扩展或者写法生成唯一字符串在大并发数据中处理效率还是不够好。其他几中方法有用到数据库……
这对雪花算法我查了查资料发现还是值得操作一番。
雪花算法的基本描述
1.最高位是符号位,始终为0,不可用。 2.41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。3.10位的机器标识,10位的长度最多支持部署1024个节点。4.12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
根据以上描述及参考雪花算法及运用PHP、基于php的雪花算法等内容我的本地测试
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class snowflake { const EPOCH_OFFSET = 0; //偏移时间戳 const SIGN = 1; //最高位树,始终为0,不可用 const TIMESTAMP = 41; //时间戳位数 默认41位,可以使用69年 const DATA_CENTER = 5; //IDC编号位数 最多32个节点 const MACHINE_ID = 5; //机器编号位数 最多32个节点 const SEQUENCE = 12; //计数序列号位数,即一系列自增id 每个节点每毫秒产生4096个ID序列
protected $data_center_id; //数据中心编号
protected $unix_id; //机器编号
protected $last_time = null; //最后一次生成id使用的时间戳
protected $serial = 1; protected $sign_left_shift = self::TIMESTAMP + self::DATA_CENTER + self::MACHINE_ID + self::SEQUENCE; //符号左位移 protected $time_left_shift = self::DATA_CENTER + self::MACHINE_ID + self::SEQUENCE; //时间戳左位移 protected $data_center_left_shift = self::MACHINE_ID + self::SEQUENCE; //idc左位移 protected $unix_left_shift = self::SEQUENCE; //机器编号左位移位数 protected $max_serial = -1 ^ (-1 << self::SEQUENCE); //最大序列号 protected $max_unix = -1 ^ (-1 << self::MACHINE_ID); //最大机器编号 protected $max_data_center = -1 ^ (-1 << self::DATA_CENTER); //最大数据中心编号
public function __construct($data_center_id, $unix_id) { if ($data_center_id > $this->max_data_center) { throw new Exception('数据中心编号取值错误,取值范围为:0-' . $this->max_data_center); } if ($unix_id > $this->max_unix) { throw new Exception('机器编号取值错误,取值范围为:0-' . $this->max_unix); } $this->data_center_id = $data_center_id; $this->unix_id = $unix_id; }
public function generateId() { $sign = 0; $unix_time = $this->getUnixTime(); //判断时间戳 if ($unix_time < $this->last_time) { throw new Exception('当前时间不能小于最后一次时间!'); } if ($unix_time == $this->last_time) { $serial = ++$this->serial; if ($serial == $this->max_serial) { $unix_time = $this->getUnixTime(); while ($unix_time <= $this->last_time) { $unix_time = $this->getUnixTime(); } $this->serial = 0; $serial = ++$this->serial; } } else { $this->serial = 0; $serial = ++$this->serial; } $this->last_time = $unix_time; $time = (int)($unix_time - self::EPOCH_OFFSET); $id = ($sign << $this->sign_left_shift) | ($time << $this->time_left_shift) | ($this->data_center_id << $this->data_center_left_shift) | ($this->unix_id << $this->unix_left_shift) | $serial; return $id; }
public function getUnixTime() { return floor(microtime(true) * 1000); } }
$data=new snowflake(1,1); $get_id=$data->generateId();
echo $get_id.PHP_EOL;
|