Skip to content

Instantly share code, notes, and snippets.

@lifei6671
Created August 15, 2017 01:49
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lifei6671/095e9a072a468092a777cbef9157d78b to your computer and use it in GitHub Desktop.
Save lifei6671/095e9a072a468092a777cbef9157d78b to your computer and use it in GitHub Desktop.
PHP实现的snowflake算法
/**
* 使用 snowflake 算法生成递增的分布式唯一ID.
* 该算法支持 15 条业务线,4 个数据中心,每个数据中心最多 128 台机器,每台机器每毫秒可生成 4096 个不重复ID.
*/
class Snowflake
{
const SEQUENCE_BITS = 12;
const MILLISECOND_BITS = 39;
const BUSINESS_ID_BITS = 4;
const DATA_CENTER_ID_BITS = 2;
const MACHINE_ID_BITS = 7;
const TWEPOC = 1399943202863;
/**
* @var int 7bit 毫秒内序列号
*/
protected $sequence;
/**
* @var int 39bit 毫秒数,最后一次生成的时间戳
*/
protected $last_timestamp;
/**
* @var int 4bit 业务线标识
*/
protected $business_id;
/**
* @var int 2bit 机房标识,仅支持4个数据中心
*/
protected $data_center_id;
/**
* @var int 7bit 机器标识,
*/
protected $machine_id;
/**
* Snowflake constructor.
* @param int $business_id 业务线标识,必须大于0小于等于15
* @param int $data_center_id 数据中心标识,必须大于0小于等于4
* @param int $machine_id 机器标识,必须大于0小于等于128
* @param int $sequence 顺序号,必须大于0小于等于4096
* @throws \Exception
*/
public function __construct($business_id,$data_center_id,$machine_id,$sequence = 0)
{
if($business_id <= 0 || $business_id > $this->maxBusinessId()){
throw new \Exception('Business Id can\'t be greater than 15 or less than 0');
}
if($data_center_id <=0 || $data_center_id > $this->maxDataCenterId()){
throw new \Exception('DataCenter Id can\'t be greater than 4 or less than 0');
}
if($machine_id <= 0 || $machine_id > $this->maxMachineId()){
throw new \Exception('Machine Id can\'t be greater than 128 or less than 0');
}
if($sequence <= 0 || $sequence > $this->maxSequence()){
throw new \Exception('Sequence can\'t be greater than 4096 or less than 0');
}
$this->business_id = $business_id;
$this->data_center_id = $data_center_id;
$this->business_id = $business_id;
$this->sequence = $sequence;
}
/**
* @return float 获取当前毫秒数
*/
public function getTimestamp()
{
return floor(microtime(true) * 1000);
}
protected function nextMillisecond($lastTimestamp)
{
$timestamp = $this->getTimestamp();
while ($timestamp <= $lastTimestamp) {
$timestamp = $this->getTimestamp();
}
return $timestamp;
}
private function maxMachineId()
{
return -1 ^ (-1 << self::MACHINE_ID_BITS);
}
private function maxDataCenterId()
{
return -1 ^ (-1 << self::DATA_CENTER_ID_BITS);
}
private function maxBusinessId()
{
return -1 ^ (-1 << self::BUSINESS_ID_BITS);
}
private function maxSequence()
{
return -1 ^ (-1 << self::SEQUENCE_BITS);
}
private function mintId64($timestamp,$business_id, $datacenterId, $machine_id, $sequence)
{
return (string)$timestamp | $business_id | $datacenterId | $machine_id | $sequence;
}
private function timestampLeftShift()
{
return self::SEQUENCE_BITS + self::MACHINE_ID_BITS + self::DATA_CENTER_ID_BITS + self::BUSINESS_ID_BITS;
}
private function businessIdLeftShift()
{
return self::SEQUENCE_BITS + self::MACHINE_ID_BITS + self::DATA_CENTER_ID_BITS ;
}
private function dataCenterIdShift()
{
return self::SEQUENCE_BITS + self::MACHINE_ID_BITS;
}
private function machineIdShift()
{
return self::SEQUENCE_BITS;
}
protected function nextSequence()
{
return $this->sequence++;
}
public function getLastTimestamp()
{
return $this->last_timestamp;
}
public function getDataCenterId()
{
return $this->data_center_id;
}
public function getMachineId()
{
return $this->machine_id;
}
public function getBusinessId()
{
return $this->business_id;
}
public function getNextId()
{
$timestamp = $this->getTimestamp();
if ($timestamp < $this->last_timestamp) {
throw new InvalidSystemClockException(sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", ($this->lastTimestamp - $timestamp)));
}
if ($timestamp == $this->last_timestamp) {
$sequence = $this->nextSequence() & $this->maxSequence();
// sequence rollover, wait til next millisecond
if ($sequence == 0) {
$timestamp = $this->nextMillisecond($this->last_timestamp);
}
} else {
$this->sequence = 0;
$sequence = $this->nextSequence();
}
$this->last_timestamp = $timestamp;
$t = floor($timestamp - self::TWEPOC) << $this->timestampLeftShift();
$b = $this->getBusinessId() << $this->machineIdShift();
$dc = $this->getDataCenterId() << $this->dataCenterIdShift();
$worker = $this->getMachineId() << $this->machineIdShift();
return $this->mintId64($t, $b, $dc, $worker, $sequence);
}
}
@PeterRock
Copy link

逻辑没问题,只是PHP是页面级语言,多线程的,这个类会有非常多个在同一台机器上同时运行。单独使用此类,并不能确保生成唯一ID

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment