diff --git a/src/Snowflake.php b/src/Snowflake.php index 7d8b9ad..c2d2119 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -1,8 +1,8 @@ -lastTimestamp = $this->epoch; } + public function makeSequenceId(int $currentTime, int $max = self::MAX_SEQUENCE): int + { + if ($this->lastTimestamp === $currentTime) { + $this->sequence = $this->sequence + 1; + return $this->sequence; + } + + $this->sequence = mt_rand(0, $max); + $this->lastTimestamp = $currentTime; + return $this->sequence; + } + /** * Generate the 64bit unique id. * - * @return integer - * - * @throws Exception + * @return int */ - public function next() + public function id(): int { - $timestamp = $this->timestamp(); - - // もし、今回生成したタイムスタンプが、前回生成したタイムスタンプより過去のものなら、待機する - // そもそもこのような状況が起こるのかは不明なので、不要かもしれない。 - if ($timestamp < $this->lastTimestamp) { - $waitTime = $this->lastTimestamp - $timestamp; - - // 待機時間がtimeout以上なら、例外を投げる - if ($waitTime > self::TIMEOUT) { - $errorLog = "[Timeout({self::TIMEOUT})] Couldn't generation snowflake id, os time is backwards. [last timestamp:" . $this->lastTimestamp ."]"; - throw new Exception($errorLog); - } - - // 待機時間がtimeout以下なら、待機して再生成 - usleep($this->lastTimestamp - $timestamp); - return $this->next(); + $currentTime = $this->timestamp(); + while (($sequenceId = $this->makeSequenceId($currentTime)) > self::MAX_SEQUENCE) { + usleep(1); + $currentTime = $this->timestamp(); } - // タイムスタンプが同じなら、シーケンスをインクリメント - if ($timestamp === $this->lastTimestamp) { - $this->sequence = $this->sequence + 1; - // シーケンス番号が最大値を超えたら、待機して再生成 - if ($this->sequence > 4095) { - usleep(1); - $this->sequence = 0; - return $this->next(); - } - } else { - $this->sequence = mt_rand(0, 4095); + $this->lastTimestamp = $currentTime; + return $this->toSnowflakeId($currentTime - $this->epoch, $sequenceId); + } + + /** + * Generate the 64bit unique id. + * + * @return int + */ + public function next(): int + { + return $this->id(); + } + + /** + * Create 53bit Id. + * timestamp_bits(41) + sequence_bits(12) + * + * @return int + */ + public function short(): int + { + $currentTime = $this->timestamp(); + while (($sequenceId = $this->makeSequenceId($currentTime)) > self::MAX_SEQUENCE) { + usleep(1); + $currentTime = $this->timestamp(); } - $this->lastTimestamp = $timestamp; - return $this->toSnowflakeId($timestamp - $this->epoch, $this->sequence); + $this->lastTimestamp = $currentTime; + return $this->toShortflakeId($currentTime - $this->epoch, $sequenceId); } - public function toSnowflakeId(int $currentTime, int $sequence) + public function toShortflakeId(int $currentTime, int $sequenceId) + { + return ($currentTime << self::SEQUENCE_BITS) | ($sequenceId); + } + + public function toSnowflakeId(int $currentTime, int $sequenceId) { $workerIdLeftShift = self::SEQUENCE_BITS; $datacenterIdLeftShift = self::WORKER_ID_BITS + self::SEQUENCE_BITS; @@ -123,7 +143,17 @@ public function toSnowflakeId(int $currentTime, int $sequence) return ($currentTime << $timestampLeftShift) | ($this->datacenterId << $datacenterIdLeftShift) | ($this->workerId << $workerIdLeftShift) - | ($sequence); + | ($sequenceId); + } + + /** + * Return the now unixtime. + * + * @return int + */ + public function timestamp(): int + { + return (int) floor(microtime(true) * 1000); } public function parse(int $id): array @@ -155,26 +185,4 @@ public function parse(int $id): array 'datetime' => $datetime, ]; } - - /** - * Create 53bit Id. - * timestamp_bits(41) + sequence_bits(12) - * - * @return int - */ - public function short(): int - { - $parsed = $this->parse($this->next()); - return $parsed['timestamp'] << 12 | $parsed['sequence']; - } - - /** - * Return the now unixtime. - * - * @return int - */ - public function timestamp(): int - { - return floor(microtime(true) * 1000) | 0; - } } diff --git a/tests/SnowflakeTest.php b/tests/SnowflakeTest.php index f7ada08..a3555bf 100644 --- a/tests/SnowflakeTest.php +++ b/tests/SnowflakeTest.php @@ -18,6 +18,17 @@ public function testNextId() $this->assertTrue($timestamp - $now < 3); } + public function testMultipleNextId() + { + $instance = app(Snowflake::class); + + $ids = []; + foreach (range(0, 1000) as $_) { + $ids[] = $instance->next(); + } + $this->assertTrue(array_unique($ids) === $ids); + } + public function testShortId() { $now = strtotime(date('Y-m-d H:i:s')); @@ -29,4 +40,14 @@ public function testShortId() $this->assertTrue($timestamp - $now < 3); } + + public function testMultipleShortId() + { + $instance = app(Snowflake::class); + $ids = []; + foreach (range(0, 1000) as $_) { + $ids[] = $instance->short(); + } + $this->assertTrue(array_unique($ids) === $ids); + } }