diff --git a/README.md b/README.md index 32bfb9c..a4dd034 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Snowflake & Sonyflake algorithm PHP implementation [中文文档](https://github ![file](https://images.godruoyi.com/logos/201908/13/_1565672621_LPW65Pi8cG.png) -Snowflake is a network service that generates unique ID numbers at high scale with simple guarantees. +Snowflake is a network service that generates unique ID numbers at a high scale with simple guarantees. 1. The first bit is an unused sign bit. 2. The second part consists of a 41-bit timestamp (in milliseconds) representing the offset of the current time relative to a certain reference time. @@ -84,7 +84,7 @@ $snowflake = new \Godruoyi\Snowflake\Snowflake($datacenterId, $workerId); $snowflake->id(); ``` -3. Specify start time. +3. Specify the start time. ```php $snowflake = new \Godruoyi\Snowflake\Snowflake; @@ -95,7 +95,16 @@ $snowflake->id(); > The maximum value of a 41-bit timestamp (in milliseconds) can represent up to 69 years, so the Snowflake algorithm can run safely for 69 years. In order to make the most of it, we recommend setting a start time. -4. Use Sonyflake +4. Using different sequence number resolvers (optional). + +```php +$snowflake = new \Godruoyi\Snowflake\Snowflake; +$snowflake->setSequenceResolver(new \Godruoyi\Snowflake\RandomSequenceResolver); + +$snowflake->id(); +``` + +5. Use Sonyflake ```php $sonyflake = new \Godruoyi\Snowflake\Sonyflake; diff --git a/src/RandomSequenceResolver.php b/src/RandomSequenceResolver.php index a1e88f3..ce32581 100644 --- a/src/RandomSequenceResolver.php +++ b/src/RandomSequenceResolver.php @@ -43,7 +43,7 @@ public function sequence(int $currentTime): int return $this->sequence; } - $this->sequence = random_int(0, $this->maxSequence); + $this->sequence = crc32(uniqid((string) random_int(0, PHP_INT_MAX), true)) % $this->maxSequence; $this->lastTimeStamp = $currentTime; return $this->sequence; diff --git a/src/Snowflake.php b/src/Snowflake.php index 9b9fcd7..7513321 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -60,8 +60,8 @@ public function __construct(int $datacenter = 0, int $workerId = 0) $maxWorkId = -1 ^ (-1 << self::MAX_WORKID_LENGTH); // If not set datacenter or workid, we will set a default value to use. - $this->datacenter = $datacenter > $maxDataCenter || $datacenter < 0 ? random_int(0, 31) : $datacenter; - $this->workerId = $workerId > $maxWorkId || $workerId < 0 ? random_int(0, 31) : $workerId; + $this->datacenter = $datacenter > $maxDataCenter || $datacenter <= 0 ? random_int(0, 31) : $datacenter; + $this->workerId = $workerId > $maxWorkId || $workerId <= 0 ? random_int(0, 31) : $workerId; } /** diff --git a/tests/FileLockResolverTest.php b/tests/FileLockResolverTest.php index 5813b3f..57f269c 100644 --- a/tests/FileLockResolverTest.php +++ b/tests/FileLockResolverTest.php @@ -18,14 +18,15 @@ class FileLockResolverTest extends TestCase { private static string $mainLockFileDirPath; + private static string $unWriteableFileDirPath; private FileLockResolver $fileLocker; public static function setUpBeforeClass(): void { - self::$mainLockFileDirPath = dirname(__DIR__) . '/.locks'; - self::$unWriteableFileDirPath = __DIR__ . '/.locks'; + self::$mainLockFileDirPath = dirname(__DIR__).'/.locks'; + self::$unWriteableFileDirPath = __DIR__.'/.locks'; } protected function setUp(): void @@ -375,7 +376,7 @@ private function touch(string $content = ''): string private function cleanUpLockFileDirs(): void { - $glob = self::$mainLockFileDirPath . '/*'; + $glob = self::$mainLockFileDirPath.'/*'; $files = glob($glob); foreach ($files as $file) { if (is_file($file)) { diff --git a/tests/PredisSequenceResolverTest.php b/tests/PredisSequenceResolverTest.php index e0ef02f..f944f9f 100644 --- a/tests/PredisSequenceResolverTest.php +++ b/tests/PredisSequenceResolverTest.php @@ -19,7 +19,7 @@ class PredisSequenceResolverTest extends TestCase { - public function setUp(): void + protected function setUp(): void { if (! class_exists('Predis\\Client')) { $this->markTestSkipped('Predis extension is not installed'); diff --git a/tests/RandomSequenceResolverTest.php b/tests/RandomSequenceResolverTest.php index a8a991d..d7a1b8c 100644 --- a/tests/RandomSequenceResolverTest.php +++ b/tests/RandomSequenceResolverTest.php @@ -29,7 +29,7 @@ public function test_basic(): void $this->assertCount(Snowflake::MAX_SEQUENCE_SIZE, $seqs); } - public function test_can_generate_unique_id_by_snowflake(): void + public function test_can_generate_unique_id_using_same_instance(): void { $snowflake = new Snowflake(1, 1); $seqs = []; @@ -40,4 +40,19 @@ public function test_can_generate_unique_id_by_snowflake(): void $this->assertCount(Snowflake::MAX_SEQUENCE_SIZE, $seqs); } + + public function test_can_generate_unique_id_by_different_instance(): void + { + for ($i = 0; $i < 1000; $i++) { + $ids = []; + + for ($i = 0; $i < 100_000; $i++) { + $snowflake = new Snowflake(); + $ids[$snowflake->id()] = true; + } + + // We expect to have at least 100_000 - 10 unique ids if using random sequence resolver + $this->assertGreaterThanOrEqual(100_000 - 10, count($ids)); + } + } } diff --git a/tests/RedisSequenceResolverTest.php b/tests/RedisSequenceResolverTest.php index 25c5908..d5e2ab5 100644 --- a/tests/RedisSequenceResolverTest.php +++ b/tests/RedisSequenceResolverTest.php @@ -17,7 +17,7 @@ class RedisSequenceResolverTest extends TestCase { - public function setUp(): void + protected function setUp(): void { if (! extension_loaded('redis')) { $this->markTestSkipped('Redis extension is not installed'); diff --git a/tests/SwooleSequenceResolverTest.php b/tests/SwooleSequenceResolverTest.php index 020eb1f..2bec754 100644 --- a/tests/SwooleSequenceResolverTest.php +++ b/tests/SwooleSequenceResolverTest.php @@ -16,7 +16,7 @@ class SwooleSequenceResolverTest extends TestCase { - public function setUp(): void + protected function setUp(): void { if (version_compare(PHP_VERSION, '8.4') >= 0) { $this->markTestSkipped('Swoole does not yet support PHP 8.4');