Created
April 4, 2018 03:29
-
-
Save MilesPong/080009075f82f9606fda976d24b0d8d5 to your computer and use it in GitHub Desktop.
Snowflake algo sample (PHP)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Exception; | |
/** | |
* Class Snowflake | |
* | |
* @see https://github.com/twitter/snowflake | |
*/ | |
class Snowflake | |
{ | |
/** | |
* | |
*/ | |
const SIGN_BIT = 1; | |
/** | |
* | |
*/ | |
const TIMESTAMP_BIT = 41; | |
/** | |
* | |
*/ | |
const DATACENTER_BIT = 5; | |
/** | |
* | |
*/ | |
const MACHINE_BIT = 5; | |
/** | |
* | |
*/ | |
const SEQUENCE_BIT = 12; | |
/** | |
* | |
*/ | |
const MAX_TIMESTAMP_DEC = (1 << self::TIMESTAMP_BIT) - 1; | |
/** | |
* | |
*/ | |
const MAX_DATACENTER_DEC = (1 << self::DATACENTER_BIT) - 1; | |
/** | |
* | |
*/ | |
const MAX_MACHINE_DEC = (1 << self::MACHINE_BIT) - 1; | |
/** | |
* | |
*/ | |
const MAX_SEQUENCE_DEC = (1 << self::SEQUENCE_BIT) - 1; | |
/** | |
* | |
*/ | |
const TIMESTAMP_OFFSET = self::DATACENTER_BIT + self::MACHINE_BIT + self::SEQUENCE_BIT; | |
/** | |
* | |
*/ | |
const DATACENTER_OFFSET = self::MACHINE_BIT + self::SEQUENCE_BIT; | |
/** | |
* | |
*/ | |
const MACHINE_OFFSET = self::SEQUENCE_BIT; | |
/** | |
* | |
*/ | |
const EPOCH_TIME = 1514260211335; // 2017-12-26 11:50 | |
/** | |
* @var int | |
*/ | |
protected $datacenterId; | |
/** | |
* @var int | |
*/ | |
protected $machineId; | |
/** | |
* @var | |
*/ | |
private $_lastTimestamp; | |
/** | |
* @var int | |
*/ | |
private $_sequence = 0; | |
/** | |
* Snowflake constructor. | |
* @param int $datacenterId | |
* @param int $machineId | |
* @throws \Exception | |
*/ | |
public function __construct($datacenterId = 0, $machineId = 0) | |
{ | |
$this->datacenterId = $datacenterId; | |
$this->machineId = $machineId; | |
$this->validate(); | |
} | |
/** | |
* Simple validation. | |
* | |
* @throws \Exception | |
*/ | |
protected function validate() | |
{ | |
if ($this->datacenterId > self::MAX_DATACENTER_DEC || $this->datacenterId < 0) { | |
throw new Exception('Illegal datacenter id.'); | |
} | |
if ($this->machineId > self::MAX_MACHINE_DEC || $this->machineId < 0) { | |
throw new Exception('Illegal machine id.'); | |
} | |
} | |
/** | |
* Create next ID. | |
* | |
* @return int | |
* @throws \Exception | |
*/ | |
public function next() | |
{ | |
$timestamp = $this->getMilliseconds(); | |
if ($timestamp < $this->_lastTimestamp) { | |
throw new Exception('Clock error.'); | |
} | |
if ($timestamp == $this->_lastTimestamp) { | |
$this->_sequence = ++$this->_sequence & self::MAX_SEQUENCE_DEC; | |
if ($this->_sequence == 0) { | |
$timestamp = $this->waitTillNextMS($this->_lastTimestamp); | |
} | |
} else { | |
$this->_sequence = 0; | |
} | |
$this->_lastTimestamp = $timestamp; | |
return ($timestamp - self::EPOCH_TIME) << self::TIMESTAMP_OFFSET | |
| $this->datacenterId << self::DATACENTER_OFFSET | |
| $this->machineId << self::MACHINE_OFFSET | |
| $this->_sequence; | |
} | |
/** | |
* @return float | |
*/ | |
protected function getMilliseconds() | |
{ | |
return floor(microtime(true) * 1000); | |
} | |
/** | |
* @param $lastTimestamp | |
* @return float | |
*/ | |
protected function waitTillNextMS($lastTimestamp) | |
{ | |
do { | |
$timestamp = $this->getMilliseconds(); | |
} while ($timestamp <= $lastTimestamp); | |
return $timestamp; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment