2019-08-21 16:19:09 +08:00
|
|
|
|
# Snowflake
|
|
|
|
|
|
2019-09-05 14:43:42 +08:00
|
|
|
|
## 算法介绍
|
2019-09-05 13:52:36 +08:00
|
|
|
|
|
2019-09-05 15:00:59 +08:00
|
|
|
|
`Snowflake` 是由 Twitter 提出的一个分布式全局唯一 ID 生成算法,算法生成 `ID` 的结果是一个 `64bit` 大小的长整,标准算法下它的结构如下图:
|
2019-09-05 14:43:42 +08:00
|
|
|
|
|
2020-06-09 20:29:48 +08:00
|
|
|
|
![snowflake](imgs/snowflake.jpeg)
|
2019-09-05 14:43:42 +08:00
|
|
|
|
|
2019-11-12 19:46:54 +08:00
|
|
|
|
- `1 位`,不用。
|
2019-09-05 14:43:42 +08:00
|
|
|
|
- 二进制中最高位为符号位,我们生成的 `ID` 一般都是正整数,所以这个最高位固定是 0。
|
|
|
|
|
|
2019-11-12 19:46:54 +08:00
|
|
|
|
- `41 位`,用来记录时间戳(毫秒)。
|
|
|
|
|
- `41 位` 可以表示 `2^41 - 1` 个数字。
|
|
|
|
|
- 也就是说 `41 位` 可以表示 `2^41 - 1` 个毫秒的值,转化成单位年则是 `(2^41 - 1) / (1000 * 60 * 60 * 24 * 365)` 约为 `69` 年。
|
2019-09-05 14:43:42 +08:00
|
|
|
|
|
2019-11-12 19:46:54 +08:00
|
|
|
|
- `10 位`,用来记录工作机器 `ID`。
|
2019-09-05 14:43:42 +08:00
|
|
|
|
- 可以部署在 `2^10` 共 `1024` 个节点,包括 `5` 位 `DatacenterId` 和 `5` 位 `WorkerId`。
|
|
|
|
|
|
2019-11-12 19:46:54 +08:00
|
|
|
|
- `12 位`,序列号,用来记录同毫秒内产生的不同 `id`。
|
|
|
|
|
- `12 位` 可以表示的最大正整数是 `2^12 - 1` 共 `4095` 个数字,来表示同一机器同一时间截(毫秒)内产生的 `4095` 个 `ID` 序号。
|
2019-09-05 14:43:42 +08:00
|
|
|
|
|
|
|
|
|
`Snowflake` 可以保证:
|
|
|
|
|
|
|
|
|
|
- 所有生成的 `ID` 按时间趋势递增。
|
2019-09-05 15:00:59 +08:00
|
|
|
|
- 整个分布式系统内不会产生重复 `ID`(因为有 `DatacenterId (5 bits)` 和 `WorkerId (5 bits)` 来做区分)。
|
|
|
|
|
|
2019-10-22 09:35:48 +08:00
|
|
|
|
Hyperf 的 [hyperf/snowflake](https://github.com/hyperf/snowflake) 组件在设计上提供了很好的可扩展性,允许您通过简单的扩展就能实现其它基于 Snowflake 的变体算法。
|
2019-09-05 13:52:36 +08:00
|
|
|
|
|
2019-08-21 16:19:09 +08:00
|
|
|
|
## 安装
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
composer require hyperf/snowflake
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 使用
|
|
|
|
|
|
2019-11-12 19:46:54 +08:00
|
|
|
|
框架提供了 `MetaGeneratorInterface` 和 `IdGeneratorInterface`,`MetaGeneratorInterface` 会生成 `ID` 的 `Meta` 文件,`IdGeneratorInterface` 则会根据对应的 `Meta` 文件生成 `分布式 ID`。
|
2019-08-21 16:19:09 +08:00
|
|
|
|
|
2019-09-18 01:23:20 +08:00
|
|
|
|
框架默认使用的 `MetaGeneratorInterface` 是基于 `Redis` 实现的 `毫秒级别生成器`。
|
2019-10-11 09:14:55 +08:00
|
|
|
|
配置文件位于 `config/autoload/snowflake.php`,如配置文件不存在可通过执行 `php bin/hyperf.php vendor:publish hyperf/snowflake` 命令创建默认配置,配置文件内容如下:
|
2019-08-21 16:19:09 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
|
2020-01-10 14:10:17 +08:00
|
|
|
|
use Hyperf\Snowflake\MetaGenerator\RedisSecondMetaGenerator;
|
2019-08-21 16:19:09 +08:00
|
|
|
|
use Hyperf\Snowflake\MetaGeneratorInterface;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'begin_second' => MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
|
|
|
|
|
RedisMilliSecondMetaGenerator::class => [
|
2019-12-06 18:47:42 +08:00
|
|
|
|
// Redis Pool
|
2019-08-21 16:19:09 +08:00
|
|
|
|
'pool' => 'default',
|
2019-12-06 18:47:42 +08:00
|
|
|
|
// 用于计算 WorkerId 的 Key 键
|
|
|
|
|
'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY
|
2019-08-21 16:19:09 +08:00
|
|
|
|
],
|
2020-01-10 14:10:17 +08:00
|
|
|
|
RedisSecondMetaGenerator::class => [
|
|
|
|
|
// Redis Pool
|
|
|
|
|
'pool' => 'default',
|
|
|
|
|
// 用于计算 WorkerId 的 Key 键
|
|
|
|
|
'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY
|
|
|
|
|
],
|
2019-08-21 16:19:09 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2020-05-08 11:56:46 +08:00
|
|
|
|
框架中使用 `Snowflake` 十分简单,只需要从 `DI` 中取出 `IdGeneratorInterface` 对象即可。
|
2019-08-21 16:19:09 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
use Hyperf\Snowflake\IdGeneratorInterface;
|
|
|
|
|
use Hyperf\Utils\ApplicationContext;
|
|
|
|
|
|
|
|
|
|
$container = ApplicationContext::getContainer();
|
|
|
|
|
$generator = $container->get(IdGeneratorInterface::class);
|
|
|
|
|
|
|
|
|
|
$id = $generator->generate();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
当知道 `ID` 需要反推对应的 `Meta` 时,只需要调用 `degenerate` 即可。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
use Hyperf\Snowflake\IdGeneratorInterface;
|
|
|
|
|
use Hyperf\Utils\ApplicationContext;
|
|
|
|
|
|
|
|
|
|
$container = ApplicationContext::getContainer();
|
|
|
|
|
$generator = $container->get(IdGeneratorInterface::class);
|
|
|
|
|
|
|
|
|
|
$meta = $generator->degenerate($id);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 重写 `Meta` 生成器
|
|
|
|
|
|
2019-09-19 11:15:17 +08:00
|
|
|
|
`分布式全局唯一 ID` 的实现方式多种多样,也有很多基于 `Snowflake` 算法的变体算法,虽然都是 `Snowflake` 算法,但也不尽相同。比如有人可能会根据 `UserId` 生成 `Meta`,而非 `WorkerId`。接下来,让我们实现一个简单的 `MetaGenerator`。
|
2019-08-21 16:19:09 +08:00
|
|
|
|
简单的来讲,`UserId` 绝对会超过 `10 bit`,所以默认的 `DataCenterId` 和 `WorkerId` 肯定是装不过来的,所以就需要对 `UserId` 取模。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?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);
|
|
|
|
|
|
2019-09-05 15:00:59 +08:00
|
|
|
|
```
|
2020-07-14 17:56:00 +08:00
|
|
|
|
|
|
|
|
|
## 在数据库模型中应用
|
|
|
|
|
|
|
|
|
|
配置好 Snowflake 以后,我们可以让数据库模型直接使用雪花 id 作为主键。
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
2020-11-20 14:13:56 +08:00
|
|
|
|
use Hyperf\Database\Model\Model;
|
|
|
|
|
use Hyperf\Snowflake\Concern\Snowflake;
|
2020-07-14 17:56:00 +08:00
|
|
|
|
|
2020-11-20 14:13:56 +08:00
|
|
|
|
class User extends Model {
|
|
|
|
|
use Snowflake;
|
2020-07-14 17:56:00 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上述 User 模型在创建时便会默认使用 Snowflake 算法生成主键。
|
2020-11-20 14:13:56 +08:00
|
|
|
|
|
|
|
|
|
因为 Snowflake 中会复写 `creating` 方法,而用户有需要自己设置 `creating` 方法时,就会出现无法生成 `ID` 的问题。这里需要用户按照以下方式自行处理即可
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?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 ...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|