Skip to content

Instantly share code, notes, and snippets.

@NHZEX
Last active May 31, 2019 11:05
Show Gist options
  • Save NHZEX/f3503760887573857c00932e5922057f to your computer and use it in GitHub Desktop.
Save NHZEX/f3503760887573857c00932e5922057f to your computer and use it in GitHub Desktop.
SnowFlake 初版
<?php
/**
* Created by PhpStorm.
* User: Auoor
* Date: 2019/3/14
* Time: 20:41
*/
declare(strict_types=1);
namespace SnowFlake;
class SnowFlake
{
/** @var int 2019-01-01 00:00:00.000 - 1546272000000 */
protected const TWEPOCH = 1546272000000;
// 是否已经初始化完成
protected static $inited = false;
// 每一部分占用的位数
protected static $bitTwepoch = 41;
protected static $bitDataCenter = 5;
protected static $bitMachine = 5;
protected static $bitSequence = 12;
// 归零偶数处理
protected static $bitZeroRandSequence = 4;
protected static $maxZeroEvenSequence;
// 每一部分的最大值
protected static $maxDataCenter;
protected static $maxMachine;
protected static $maxSequence;
// 每一部分向左的位移值
protected static $machineLeft;
protected static $dataCenterLeft;
protected static $timestampLeft;
/** @var int */
protected $dataCenterId = 0;
/** @var int */
protected $machineId = 0;
/** @var int */
protected $lastTimestamp = 0;
/** @var int */
protected $sequence = 0;
/**
* SnowFlake constructor.
* @param int $dataCenterId
* @param int $machineId
* @throws \Exception
*/
public function __construct(int $dataCenterId = 0, int $machineId = 0)
{
if (!self::$inited) {
self::$maxDataCenter = -1 ^ (-1 << self::$bitDataCenter);
self::$maxMachine = -1 ^ (-1 << self::$bitMachine);
self::$maxSequence = -1 ^ (-1 << self::$bitSequence);
self::$machineLeft = self::$bitSequence;
self::$dataCenterLeft = self::$bitSequence + self::$bitMachine;
self::$timestampLeft = self::$bitSequence + self::$bitMachine + self::$bitDataCenter;
self::$maxZeroEvenSequence = self::$maxSequence >> self::$bitZeroRandSequence;
self::$inited = true;
}
if ($dataCenterId > self::$maxDataCenter) {
throw new \Exception('数据中心编号取值范围为:0-' . self::$maxDataCenter);
}
if ($machineId > self::$maxMachine) {
throw new \Exception('机器编号编号取值范围为:0-' . self::$maxMachine);
}
$this->dataCenterId = $dataCenterId;
$this->machineId = $machineId;
$this->nextId();
}
/**
* 获取一个十六进制id
* @return string
*/
public function nextHex(): string
{
return dechex($this->nextId());
}
/**
* 获取一个id
* @return int
*/
public function nextId(): int
{
$currStmp = $this->getCurrUnixMicroTimestamp();
// TODO 允许时间回退容差
if ($currStmp < $this->lastTimestamp) {
throw new \RuntimeException("Clock moved backwards. Refusing to generate id");
}
if ($currStmp == $this->lastTimestamp) {
//相同毫秒内,序列号自增
$this->sequence = ($this->sequence + 1) & self::$maxSequence;
//同一毫秒的序列数已经达到最大
if ($this->sequence == 0) {
$currStmp = $this->getNextUnixMicroTimestamp();
}
} else {
//不同毫秒内,序列号置为0
$this->sequence = mt_rand(0, self::$maxZeroEvenSequence);
}
$this->lastTimestamp = $currStmp;
return $currStmp << self::$timestampLeft //时间戳部分
| $this->dataCenterId << self::$dataCenterLeft //数据中心部分
| $this->machineId << self::$machineLeft //机器标识部分
| $this->sequence; //序列号部分
}
/**
* 获取下一个毫秒时间戳 (阻塞到下一个毫秒,直到获得新的时间戳)
* @return int
*/
protected function getNextUnixMicroTimestamp(): int
{
$numt = $this->getCurrUnixMicroTimestamp();
while ($numt <= $this->lastTimestamp) {
$numt = $this->getCurrUnixMicroTimestamp();
}
return $numt;
}
/**
* 获取当前毫秒时间戳
* @return int
*/
protected function getCurrUnixMicroTimestamp(): int
{
return (int)(microtime(true) * 1000) - self::TWEPOCH;
}
}
<?php
declare(strict_types=1);
namespace HZEX\SimpleRpc;
use Co;
use LengthException;
use RuntimeException;
final class SnowFlake
{
/** @var int 2019-01-01 00:00:00.000 - 1546272000000 */
protected const TWEPOCH = 1546272000000;
// 是否已经初始化完成
protected static $inited = false;
// 累加器归零处理
protected static $maxSequenceZeroRand = 256;
// 每一部分占用的位数
protected static $bitTwepoch = 41; // 毫秒级时间
protected static $bitWorkerId = 8; // 工人ID
protected static $bitExtension = 3; // 扩展位
protected static $bitSequence = 11; // 毫秒累加器
// 每一部分的最大值
protected static $maxExtension;
protected static $maxWorkerId;
protected static $maxSequence;
// 每一部分向左的位移值
protected static $extensionLeft;
protected static $workerIdLeft;
protected static $timestampLeft;
/** @var int */
protected $workerId = 0;
/** @var int */
protected $extension = 0;
/** @var int */
protected $lastTimestamp = 0;
/** @var int */
protected $sequence = 0;
/**
* SnowFlake constructor.
* @param int $workerId
* @throws LengthException
*/
public function __construct(int $workerId = 0)
{
if (!self::$inited) {
self::$maxWorkerId = -1 ^ (-1 << self::$bitWorkerId);
self::$maxExtension = -1 ^ (-1 << self::$bitExtension);
self::$maxSequence = -1 ^ (-1 << self::$bitSequence);
self::$timestampLeft = self::$bitWorkerId + self::$bitExtension + self::$bitSequence;
self::$workerIdLeft = self::$bitExtension + self::$bitSequence;
self::$extensionLeft = self::$bitSequence;
self::$inited = true;
}
if ($workerId > self::$maxWorkerId) {
throw new LengthException('机器编号编号取值范围为:0-' . self::$maxWorkerId);
}
$this->workerId = $workerId;
$this->nextId();
}
/**
* 获取一个十六进制id
* @return string
*/
public function nextHex(): string
{
return dechex($this->nextId());
}
/**
* 获取一个id
* @return int
*/
public function nextId(): int
{
$currStmp = $this->getCurrUnixMicroTimestamp();
// 时间回拨,抛出异常 TODO 允许时间回退容差
if ($currStmp < $this->lastTimestamp) {
if (null === ($currStmp = $this->waitTimeGoBack($currStmp))) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
}
if ($currStmp == $this->lastTimestamp) {
//相同毫秒内,序列号自增
$this->sequence = ($this->sequence + 1) & self::$maxSequence;
//同一毫秒的序列数已经达到最大
if ($this->sequence == 0) {
$currStmp = $this->getNextUnixMicroTimestamp();
}
} else {
//不同毫秒内,序列号重置
$this->sequence = mt_rand(0, self::$maxSequenceZeroRand);
}
$this->lastTimestamp = $currStmp;
return $currStmp << self::$timestampLeft //时间戳部分
| $this->extension << self::$extensionLeft //扩展部分
| $this->workerId << self::$workerIdLeft //工人标识部分
| $this->sequence; //序列号部分
}
/**
* 等待时间回拨
* 支持3毫秒波动纠正
* @param int $microTimestamp
* @param int $retry
* @return int|null
*/
private function waitTimeGoBack(int $microTimestamp, int $retry = 3): ?int
{
if ($retry < 0) {
return null;
}
$timeDiff = $this->lastTimestamp - $microTimestamp;
if ($timeDiff <= 3000) {
if (-1 === Co::getPcid()) {
usleep($timeDiff);
} else {
Co::sleep(min(round($timeDiff / 1000, 2), 0.01));
}
}
$microTimestamp = $this->getCurrUnixMicroTimestamp();
if ($microTimestamp >= $this->lastTimestamp) {
return $microTimestamp;
}
$this->waitTimeGoBack($microTimestamp, --$retry);
return null;
}
/**
* 获取下一个毫秒时间戳 (阻塞到下一个毫秒,直到获得新的时间戳)
* @return int
*/
protected function getNextUnixMicroTimestamp(): int
{
$numt = $this->getCurrUnixMicroTimestamp();
while ($numt <= $this->lastTimestamp) {
$numt = $this->getCurrUnixMicroTimestamp();
}
return $numt;
}
/**
* 获取当前毫秒时间戳
* @return int
*/
protected function getCurrUnixMicroTimestamp(): int
{
return (int)(microtime(true) * 1000) - self::TWEPOCH;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment