# Snowflake ## 算法介紹 `Snowflake` 是由 Twitter 提出的一個分佈式全局唯一 ID 生成算法,算法生成 `ID` 的結果是一個 `64bit` 大小的長整,標準算法下它的結構如下圖: ![snowflake](imgs/snowflake.jpeg) - `1 位`,不用。 - 二進制中最高位為符號位,我們生成的 `ID` 一般都是正整數,所以這個最高位固定是 0。 - `41 位`,用來記錄時間戳(毫秒)。 - `41 位` 可以表示 `2^41 - 1` 個數字。 - 也就是説 `41 位` 可以表示 `2^41 - 1` 個毫秒的值,轉化成單位年則是 `(2^41 - 1) / (1000 * 60 * 60 * 24 * 365)` 約為 `69` 年。 - `10 位`,用來記錄工作機器 `ID`。 - 可以部署在 `2^10` 共 `1024` 個節點,包括 `5` 位 `DatacenterId` 和 `5` 位 `WorkerId`。 - `12 位`,序列號,用來記錄同毫秒內產生的不同 `id`。 - `12 位` 可以表示的最大正整數是 `2^12 - 1` 共 `4095` 個數字,來表示同一機器同一時間截(毫秒)內產生的 `4095` 個 `ID` 序號。 `Snowflake` 可以保證: - 所有生成的 `ID` 按時間趨勢遞增。 - 整個分佈式系統內不會產生重複 `ID`(因為有 `DatacenterId (5 bits)` 和 `WorkerId (5 bits)` 來做區分)。 Hyperf 的 [hyperf/snowflake](https://github.com/hyperf/snowflake) 組件在設計上提供了很好的可擴展性,允許您通過簡單的擴展就能實現其它基於 Snowflake 的變體算法。 ## 安裝 ``` composer require hyperf/snowflake ``` ## 使用 框架提供了 `MetaGeneratorInterface` 和 `IdGeneratorInterface`,`MetaGeneratorInterface` 會生成 `ID` 的 `Meta` 文件,`IdGeneratorInterface` 則會根據對應的 `Meta` 文件生成 `分佈式 ID`。 框架默認使用的 `MetaGeneratorInterface` 是基於 `Redis` 實現的 `毫秒級別生成器`。 配置文件位於 `config/autoload/snowflake.php`,如配置文件不存在可通過執行 `php bin/hyperf.php vendor:publish hyperf/snowflake` 命令創建默認配置,配置文件內容如下: ```php MetaGeneratorInterface::DEFAULT_BEGIN_SECOND, RedisMilliSecondMetaGenerator::class => [ // Redis Pool 'pool' => 'default', // 用於計算 WorkerId 的 Key 鍵 'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY ], RedisSecondMetaGenerator::class => [ // Redis Pool 'pool' => 'default', // 用於計算 WorkerId 的 Key 鍵 'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY ], ]; ``` 框架中使用 `Snowflake` 十分簡單,只需要從 `DI` 中取出 `IdGeneratorInterface` 對象即可。 ```php get(IdGeneratorInterface::class); $id = $generator->generate(); ``` 當知道 `ID` 需要反推對應的 `Meta` 時,只需要調用 `degenerate` 即可。 ```php get(IdGeneratorInterface::class); $meta = $generator->degenerate($id); ``` ## 重寫 `Meta` 生成器 `分佈式全局唯一 ID` 的實現方式多種多樣,也有很多基於 `Snowflake` 算法的變體算法,雖然都是 `Snowflake` 算法,但也不盡相同。比如有人可能會根據 `UserId` 生成 `Meta`,而非 `WorkerId`。接下來,讓我們實現一個簡單的 `MetaGenerator`。 簡單的來講,`UserId` 絕對會超過 `10 bit`,所以默認的 `DataCenterId` 和 `WorkerId` 肯定是裝不過來的,所以就需要對 `UserId` 取模。 ```php idGenerator = $idGenerator; } public function generate(int $userId) { $meta = $this->idGenerator->getMetaGenerator()->generate(); return $this->idGenerator->generate($meta->setWorkerId($userId % 31)); } public function degenerate(int $id) { return $this->idGenerator->degenerate($id); } } use Hyperf\Utils\ApplicationContext; $container = ApplicationContext::getContainer(); $generator = $container->get(UserDefinedIdGenerator::class); $userId = 20190620; $id = $generator->generate($userId); ``` ## 在數據庫模型中應用 配置好 Snowflake 以後,我們可以讓數據庫模型直接使用雪花 id 作為主鍵。 ```php create(); // Do something ... } } ```