hyperf/docs/zh-hk/snowflake.md
沈唁 bb3ed4815c
Add translate docs action (#3276)
* Add translate docs action

* Remove push

Co-authored-by: sy-records <sy-records@users.noreply.github.com>
2021-02-19 12:53:50 +08:00

5.5 KiB
Raw Blame History

Snowflake

算法介紹

Snowflake 是由 Twitter 提出的一個分佈式全局唯一 ID 生成算法,算法生成 ID 的結果是一個 64bit 大小的長整,標準算法下它的結構如下圖:

snowflake

  • 1 位,不用。

    • 二進制中最高位為符號位,我們生成的 ID 一般都是正整數,所以這個最高位固定是 0。
  • 41 位,用來記錄時間戳(毫秒)。

    • 41 位 可以表示 2^41 - 1 個數字。
    • 也就是説 41 位 可以表示 2^41 - 1 個毫秒的值,轉化成單位年則是 (2^41 - 1) / (1000 * 60 * 60 * 24 * 365) 約為 69 年。
  • 10 位,用來記錄工作機器 ID

    • 可以部署在 2^101024 個節點,包括 5DatacenterId5WorkerId
  • 12 位,序列號,用來記錄同毫秒內產生的不同 id

    • 12 位 可以表示的最大正整數是 2^12 - 14095 個數字,來表示同一機器同一時間截(毫秒)內產生的 4095ID 序號。

Snowflake 可以保證:

  • 所有生成的 ID 按時間趨勢遞增。
  • 整個分佈式系統內不會產生重複 ID(因為有 DatacenterId (5 bits)WorkerId (5 bits) 來做區分)。

Hyperf 的 hyperf/snowflake 組件在設計上提供了很好的可擴展性,允許您通過簡單的擴展就能實現其它基於 Snowflake 的變體算法。

安裝

composer require hyperf/snowflake

使用

框架提供了 MetaGeneratorInterfaceIdGeneratorInterfaceMetaGeneratorInterface 會生成 IDMeta 文件,IdGeneratorInterface 則會根據對應的 Meta 文件生成 分佈式 ID

框架默認使用的 MetaGeneratorInterface 是基於 Redis 實現的 毫秒級別生成器
配置文件位於 config/autoload/snowflake.php,如配置文件不存在可通過執行 php bin/hyperf.php vendor:publish hyperf/snowflake 命令創建默認配置,配置文件內容如下:

<?php

declare(strict_types=1);

use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
use Hyperf\Snowflake\MetaGenerator\RedisSecondMetaGenerator;
use Hyperf\Snowflake\MetaGeneratorInterface;

return [
    'begin_second' => 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
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\ApplicationContext;

$container = ApplicationContext::getContainer();
$generator = $container->get(IdGeneratorInterface::class);

$id = $generator->generate();

當知道 ID 需要反推對應的 Meta 時,只需要調用 degenerate 即可。

<?php
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\ApplicationContext;

$container = ApplicationContext::getContainer();
$generator = $container->get(IdGeneratorInterface::class);

$meta = $generator->degenerate($id);

重寫 Meta 生成器

分佈式全局唯一 ID 的實現方式多種多樣,也有很多基於 Snowflake 算法的變體算法,雖然都是 Snowflake 算法,但也不盡相同。比如有人可能會根據 UserId 生成 Meta,而非 WorkerId。接下來,讓我們實現一個簡單的 MetaGenerator。 簡單的來講,UserId 絕對會超過 10 bit,所以默認的 DataCenterIdWorkerId 肯定是裝不過來的,所以就需要對 UserId 取模。

<?php

declare(strict_types=1);

use Hyperf\Snowflake\IdGenerator;

class UserDefinedIdGenerator
{
    /**
     * @var IdGenerator\SnowflakeIdGenerator
     */
    protected $idGenerator;

    public function __construct(IdGenerator\SnowflakeIdGenerator $idGenerator)
    {
        $this->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
use Hyperf\Database\Model\Model;
use Hyperf\Snowflake\Concern\Snowflake;

class User extends Model {
    use Snowflake;
}

上述 User 模型在創建時便會默認使用 Snowflake 算法生成主鍵。

因為 Snowflake 中會複寫 creating 方法,而用户有需要自己設置 creating 方法時,就會出現無法生成 ID 的問題。這裏需要用户按照以下方式自行處理即可

<?php
use Hyperf\Database\Model\Model;
use Hyperf\Snowflake\Concern\Snowflake;

class User extends Model {
    use Snowflake {
        creating as create;
    }

    public function creating()
    {
        $this->create();
        // Do something ...
    }
}