- 后端
- Web框架
- 微服务
- RPC框架
- 异步编程
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
导读
本文围绕 Hyperf 框架内置的hyperf/snowflake组件展开,完整讲解 Twitter Snowflake 算法的 64 位二进制结构、组件的安装与配置、IdGeneratorInterface与MetaGeneratorInterface两个核心接口的用法、自定义 Meta 生成器的扩展方式,以及如何在数据库 Model 中把 Snowflake ID 直接作为主键使用。读完本文,你将掌握在 Hyperf 项目中快速集成全局唯一、随时间递增的分布式 ID 生成能力,并理解其底层位运算与 Redis 分配机制的实现原理。
一、Snowflake 算法原理
Snowflake是 Twitter 提出的一种分布式全局唯一 ID 生成算法,算法产出的结果是64bit的整数。在标准算法中,其二进制结构从高位到低位分为以下四段:
| 位段 | 长度 | 用途 |
|---|---|---|
| 符号位 | 1 bit | 不使用,恒为 0 |
| 时间戳 | 41 bits | 记录毫秒级时间戳 |
| 机器 ID | 10 bits | 记录机器 ID,由5 bits DatacenterId+5 bits WorkerId组成 |
| 序列号 | 12 bits | 同一毫秒内产生的不同 ID 序号 |
1. 符号位(1 bit)
二进制中的最高位是符号位。我们生成的ID通常是正整数,因此该位固定设置为0。
2. 时间戳(41 bits)
41 bits最多可以表示2^41 - 1个数值,即可以记录2^41 - 1个毫秒值。换算成年份,约为(2^41 - 1) / (1000 * 60 * 60 * 24 * 365),大约是69年。结合 Hyperf 组件中默认的起始时间begin_second = 1560960000(即 2019-06-20,参见 MetaGeneratorInterface.php),时间戳段的理论可用跨度可覆盖相当长的时间范围。
3. 机器 ID(10 bits)
10 bits最多可部署2^10 = 1024个节点,其中包含5 bits的DatacenterId和5 bits的WorkerId。
4. 序列号(12 bits)
12 bits可以表示的最大正整数是2^12 - 1 = 4095,即同一台机器在同一个毫秒内最多可以产生4095个序列号,超出后需要等待下一个毫秒。
Snowflake 算法的核心保证
- 所有生成的
ID都随时间的推移呈递增趋势; - 整个分布式系统中不会产生重复
ID,因为DatacenterId(5 bits)与WorkerId(5 bits)被用于节点区分。
Hyperf 的hyperf/snowflake组件在设计上具有良好的可扩展性,允许你通过简单的扩展来实现基于 Snowflake 的各种变体算法(例如下文介绍的基于UserId而非WorkerId的生成方式)。
二、安装组件
在 Hyperf 项目中通过 Composer 安装:
composer require hyperf/snowflake组件依赖说明(见 composer.json):
- 运行时依赖
hyperf/context、hyperf/contract、hyperf/coroutine,PHP 版本要求>= 8.2; - 若使用基于 Redis 的
RedisMilliSecondMetaGenerator/RedisSecondMetaGenerator,还需要安装hyperf/redis; - 读取配置需要
hyperf/config,使用MetaGeneratorFactory需要psr/container。
三、配置文件与默认实现
框架默认使用的MetaGeneratorInterface实现是基于Redis的毫秒级生成器RedisMilliSecondMetaGenerator。配置文件位于config/autoload/snowflake.php,如果文件不存在,可以通过以下命令发布默认配置:
php bin/hyperf.php vendor:publish hyperf/snowflake发布后的配置文件内容如下(该文件在仓库中的模板见 publish/snowflake.php):
<?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' => RedisSecondMetaGenerator::DEFAULT_REDIS_KEY ], ];各配置项的含义与默认值如下:
| 配置项 | 说明 | 默认值 |
|---|---|---|
begin_second | ID 时间戳段的起始秒数,ID 的可用时间跨度以此为基准 | MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,即1560960000 |
pool | 用于计算WorkerId的 Redis 连接池名称 | default |
key | 用于在 Redis 中递增计数、进而分配WorkerId的 Key | RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY,即hyperf:snowflake:workerId |
从源码看,begin_second在毫秒级生成器中会被乘以1000后作为起始时间戳(见 RedisMilliSecondMetaGenerator.php),而pool与key的读取逻辑位于 RedisMetaGenerator.php。
四、基本使用:生成与反解 ID
在框架中使用Snowflake非常简单,只需从DI容器中取出IdGeneratorInterface对象:
<?php use Hyperf\Snowflake\IdGeneratorInterface; use Hyperf\Context\ApplicationContext; $container = ApplicationContext::getContainer(); $generator = $container->get(IdGeneratorInterface::class); $id = $generator->generate();当需要把ID反解回对应的Meta(即还原出时间戳、机器 ID、序列号等信息)时,调用degenerate即可:
<?php use Hyperf\Snowflake\IdGeneratorInterface; use Hyperf\Context\ApplicationContext; $container = ApplicationContext::getContainer(); $generator = $container->get(IdGeneratorInterface::class); $meta = $generator->degenerate($id);接口契约定义见 IdGeneratorInterface.php:generate(?Meta $meta = null): int在传入Meta时按指定元信息生成,否则使用默认Meta;degenerate(int $id): Meta负责反解。
底层位运算原理
IdGenerator抽象类(见 IdGenerator.php)通过位移与或运算完成 ID 的组装与拆分:
- 生成:
时间差 << 时间戳左移位数 | DataCenterId << 数据中心位移 | WorkerId << 机器位移 | 序列号,其中位移量由Configuration根据位段长度计算(timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits,见 Configuration.php); - 反解:分别对 ID 做右移运算,再结合位掩码(
^异或)从各段中还原出interval、dataCenterId、workerId与sequence,最终构造出Meta。
Meta类(见 Meta.php)承载四段元信息,取值范围分别为:dataCenterId在[0, 31],workerId在[0, 31],sequence在[0, 4095]。其中时间戳与起始时间戳的单位既可以是秒也可以是毫秒,由具体生成器决定。
Redis 自动分配机器 ID 的机制
默认的毫秒级/秒级生成器都继承自RedisMetaGenerator(见 RedisMetaGenerator.php)。其分配逻辑如下:
- 首次使用时通过
Hyperf\Coroutine\Locker加锁,保证协程环境下只初始化一次; - 从配置中读取
pool与key,通过RedisProxy执行$redis->incr($key)获取一个全局自增序号; - 按下式计算
workerId与dataCenterId:workerId = 序号 % maxWorkerId()dataCenterId = intval(序号 / maxWorkerId()) % maxDataCenterId()
其中maxWorkerId()、maxDataCenterId()由 Configuration.php 通过-1 ^ (-1 << 位数)计算得出(5 bits 对应最大值 31)。这样每个进程首次运行时都会从 Redis 获得一个互不重复的机器标识,从而保证分布式环境下的全局唯一性。
五、重写 Meta Generator:基于 UserId 的变体实现
实现分布式全局唯一 ID 的方式很多,基于 Snowflake 算法的变体也层出不穷。虽然都是 Snowflake 算法,但它们并不完全相同,例如有些人会基于UserId而非WorkerId来生成Meta。
下面实现一个简单的自定义MetaGenerator:由于UserId通常会超过10 bits,默认的DataCenterId与WorkerId显然无法容纳,因此需要对UserId取模后再放入WorkerId位段。
<?php declare(strict_types=1); use Hyperf\Snowflake\IdGenerator\SnowflakeIdGenerator; class UserDefinedIdGenerator { protected SnowflakeIdGenerator $idGenerator; public function __construct(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\Context\ApplicationContext; $container = ApplicationContext::getContainer(); $generator = $container->get(UserDefinedIdGenerator::class); $userId = 20190620; $id = $generator->generate($userId);关键点说明:
SnowflakeIdGenerator(见 SnowflakeIdGenerator.php)是IdGenerator的直接子类,本身不增加额外逻辑;- 通过
$this->idGenerator->getMetaGenerator()->generate()先生成一份默认Meta,再用setWorkerId($userId % 31)将UserId取模后写入WorkerId段(5 bits 最大值 31),最后调用generate($meta)按定制元信息产出 ID; - 该示例对应的测试桩可见 UserDefinedIdGenerator.php。
六、在数据库 Model 中应用
配置好 Snowflake 后,可以创建一个以 Snowflake ID 作为主键的数据库模型:
<?php use Hyperf\Database\Model\Model; use Hyperf\Snowflake\Concern\Snowflake; class User extends Model { use Snowflake; }上面的User模型在创建(creating)时,会默认使用 Snowflake 算法生成主键。
从 Concern/Snowflake.php 的源码可以看到该 Trait 的具体行为:
creating()钩子中,若当前模型尚无主键(! $this->getKey()),则从ApplicationContext容器取出IdGeneratorInterface并调用generate()写入主键字段;getIncrementing()返回false,关闭自增主键;getKeyType()返回int,声明主键类型为整型。
处理 creating 冲突
由于 Snowflake Trait 会覆写creating方法,而用户可能也需要自定义自己的creating方法,此时会出现ID无法生成的问题。解决方式是在类中显式地对 Trait 方法做别名重命名,再在自己的creating中手动调用:
<?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(); // 做其他事情 ... } }这样既保留了 Snowflake 自动生成主键的能力,又不影响用户在创建模型时追加自定义逻辑。
七、小结
hyperf/snowflake组件在标准 Snowflake 算法之上提供了清晰的两层抽象:MetaGeneratorInterface负责生成携带时间戳与机器标识的Meta,IdGeneratorInterface负责把Meta通过位运算组装成最终 ID 并支持反向解析。默认的 Redis 实现通过自增 Key 为分布式节点自动分配WorkerId与DataCenterId,保证了全局唯一;同时组件的可扩展设计允许你基于UserId、业务分片等维度定制属于自己的 Snowflake 变体,并可直接与 Hyperf 的 ORM 模型无缝集成作为主键方案使用。
- 后端
- Web框架
- 微服务
- RPC框架
- 异步编程
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Hyperf Snowflake 组件实战:分布式全局唯一 ID 生成算法解析与扩展指南
Hyperf Snowflake 组件实战:分布式全局唯一 ID 生成算法解析与扩展指南 导读 在微服务与分布式系统架构中,生成全局唯一、趋势递增的 ID 是订
后端Web框架微服务RPC框架异步编程Hyperf Snowflake 分布式 ID 生成器:从算法原理到源码级实践指南
Hyperf Snowflake 分布式 ID 生成器:从算法原理到源码级实践指南 导读 Snowflake(雪花算法)是 Twitter 提出的分布式全局唯一
后端微服务Thunderbolt 重试预算机制详解:408/429/5xx 自动重试如何保护你的 AI 对话
Thunderbolt 重试预算机制详解:408/429/5xx 自动重试如何保护你的 AI 对话 当你在使用 Thunderbolt 这类 AI 桌面应用时,
人工智能大模型AI 应用本地部署前端后端桌面应用AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考