一佳技术

easy-idgen ID 生成器组件设计

本文档描述 easy-idgen 组件的设计与实现。该组件提供三种 ID 生成策略:Leaf 号段(leaf-core 外部库)、Snowflake 雪花算法(本地实现)、Simple 简单计数器(本地实现)。三种策略统一到 IdGenerator 接口,由 IdGeneratorProperties 配置驱动 Spring Boot 自动装配,生产代码零外部 MQ/Redis 依赖(Leaf 模式需可选 JDBC 依赖)。


一、背景与问题

1.1 分布式 ID 生成的常见方案对比

方案优点缺点
数据库自增简单单点、性能差、分表后冲突
UUID全局唯一无序、字符串、长
雪花算法 Snowflake有序、数字、高性能时钟回拨需要处理
Leaf 号段有序、高性能、号段预热需数据库、双 buffer 预热
号段(自研)同 Leaf重复造轮子

Matrix Framework 选择三种并存:本地单机场景用 Simple,分布式无 DB 场景用 Snowflake,有 DB 场景用 Leaf 号段(依赖 leaf-core 外部库)。


二、组件定位与边界

定位:策略可切换的分布式 ID 门面。业务方注入 IdGenerator,nextId() / nextId(batchSize) 取 ID;底层用配置驱动三种实现之一。

边界:

做不做
三种策略实现 + IdGenerator 统一接口不做 Redis / Zookeeper 号段实现
Snowflake 本地实现(workerId 从 NetworkInterface 取)不做 workerId 中心化注册
Leaf 号段(委托 leaf-core)不做 Leaf 号段表 DDL(README 有参考 SQL)
Spring Boot @ConfigurationProperties + @AutoConfiguration不做 Spring Cloud Config 动态刷新
Simple 本地计数器(AtomicLong + 持久化可选)不做持久化文件锁

约束:

  • 雪花算法的 allowClockBackward / maxClockBackwardMs 配置字段未生效——时钟回拨阈值硬编码为 5ms(见 关键设计决策)
  • Leaf 模式运行时强依赖数据库(leaf-core SegmentService),jdbc 依赖为 compile scope
  • Snowflake / Simple 模式无外部依赖,本地即可运行

三、总体架构

flowchart TD
    subgraph "业务层"
        A1["注入 IdGenerator<br/>(接口)"]
    end

    subgraph "统一接口"
        B1["IdGenerator 接口<br/>nextId() / nextId(batchSize)"]
    end

    subgraph "Spring Boot 自动装配"
        C1["EasyIdGenAutoConfiguration<br/>@ConfigurationProperties(IdGeneratorProperties)<br/>@ConditionalOnProperty"]
    end

    subgraph "三种策略实现"
        D1["SnowflakeIdGenerator<br/>雪花算法,本地实现"]
        D2["SimpleIdGenerator<br/>AtomicLong + setStartValue + 可选持久化"]
        D3["LeafIdGenerator<br/>委托 leaf-core IDGen (外部库)"]
    end

    subgraph "外部依赖"
        E1["leaf-core:1.0.2 (optional)"]
        E2["NetworkInterface.getNetworkInterfaces()"]
        E3["jdbc:runtime (Leaf 模式)"]
    end

    A1 --> B1
    C1 -- "enabled + type=snowflake" --> D1
    C1 -- "enabled + type=simple" --> D2
    C1 -- "enabled + type=leaf" --> D3
    D3 --> E1
    D1 --> E2
    D3 --> E3

策略选择流程:

flowchart TD
    A["matrix.id-generator.enabled=true"]
    B{"type=?"}
    C["simple → SimpleIdGenerator<br/>setStartValue(IdGeneratorProperties.simple.startValue)<br/>init()"]
    D["snowflake → SnowflakeIdGenerator<br/>注入 SnowflakeProperties<br/>init() 位数校验<br/>generateWorkerId()"]
    E["leaf → LeafIdGenerator<br/>segmentService.get(bizTag)<br/>Status.SUCCESS ? getId : throw"]
    A --> B
    B -- "simple" --> C
    B -- "snowflake" --> D
    B -- "leaf" --> E

四、三种策略实现对比

维度SimpleSnowflakeLeaf (leaf-core)
有序✅ 严格递增✅ 时间戳有序(毫秒)✅ 号段内递增
依赖零零leaf-core + JDBC
可分布式❌ 单机(AtomicLong)✅ workerId 区分✅ 号段预热
批量生成AtomicLong.addAndGet(batchSize)逐毫秒分配,溢出等下一秒从 Segment 取一段
核心字段AtomicLong counterEPOCH=1672531200000L
workerIdBits=10 / sequenceBits=12
IDGen.get(bizTag)
初始值startValue(默认 1)EPOCH + workerId 自动数据库 step(默认 1000)
异常处理无时钟回拨 ≥ 5ms → 翻倍等待;batchSize≤0 抛Status≠SUCCESS 抛 RuntimeException

五、包结构与核心类

easy-idgen/
└── io/matrix/genid/
    ├── IdGenerator.java                    # 统一接口
    ├── autoconfig/EasyIdGenAutoConfiguration.java
    ├── config/
    │   ├── IdGeneratorProperties.java       # prefix=id-generator
    │   ├── SnowflakeProperties.java         # workerIdBits=10 / sequenceBits=12
    │   └── SimpleGenProperties.java         # startValue=1
    ├── local/
    │   ├── SimpleIdGenerator.java           # AtomicLong + @PostConstruct init() + reset() + getCurrentValue()
    │   └── SnowflakeIdGenerator.java         # 雪花算法 + parseId 还原 + 时钟回拨硬编码 5ms
    └── leaf/
        └── LeafIdGenerator.java              # 委托 leaf-core IDGen,@Autowired 注入 segmentService
类职责文件
IdGenerator统一接口:Long nextId() / List<Long> nextId(int batchSize)IdGenerator.java
SimpleIdGeneratorAtomicLong 自增;@PostConstruct init() 从 SnowflakeProperties 读 startValue;batchSize≤0 抛 IllegalArgumentExceptionSimpleIdGenerator.java
SnowflakeIdGeneratorgenerateWorkerId() 从 NetworkInterface 取 MAC 低 bits;init() 做位数校验(workerIdBits + sequenceBits ≤ 22,各 1-20);时钟回拨阈值硬编码 5ms;parseId 把 id 还原成 timestamp/workerId/sequenceSnowflakeIdGenerator.java
LeafIdGenerator@Autowired private IDGen segmentService(标准 Spring 注入,IDGen Bean 由 leaf-core 自动配置);nextId() 调 segmentService.get(bizTag) 读 Result.getStatus() + getIdLeafIdGenerator.java

六、雪花算法位分配

默认位分配(可配):

0 ┄ 1 ┄ 41 ┄ 51 ┄ 63
  ├ timestamp (41 bits)     ← 毫秒级时间戳(相对 EPOCH=2023-01-01)
  ├ workerId (10 bits)      ← NetworkInterface MAC 低 bits 取模
  └ sequence (12 bits)      ← 同毫秒内自增

参数校验(SnowflakeIdGenerator.init()):

约束异常
workerIdBits < 1 或 > 20IllegalArgumentException
sequenceBits < 1 或 > 20IllegalArgumentException
workerIdBits + sequenceBits > 22IllegalArgumentException

Compact 模式(compactMode=true):强制 workerIdBits=6 / sequenceBits=10,把更多位留给 timestamp。

parseId 还原:

public IdInfo parseId(long id) {
    // timestamp = id >> (workerIdBits + sequenceBits) + EPOCH
    // workerId = (id >> sequenceBits) & workerIdMask
    // sequence = id & sequenceMask
}

七、Spring Boot 自动装配

flowchart TD
    A["application.yml"] --> B["matrix.id-generator.enabled=true<br/>matrix.id-generator.snowflake.enabled=true"]
    B --> C["EasyIdGenAutoConfiguration<br/>@ConfigurationProperties(prefix='matrix.id-generator')"]
    C -- "snowflake.enabled=true" --> D["@Bean SnowflakeIdGenerator<br/>@ConditionalOnProperty(snowflake.enabled=true)"]
    C -- "simple.enabled=true" --> E["@Bean SimpleIdGenerator<br/>@ConditionalOnProperty(simple.enabled=true)"]
    C -- "segment.enabled=true" --> F["@Bean LeafIdGenerator<br/>@Autowired IDGen segmentService"]

三个 @ConditionalOnProperty:

BeanCondition
SimpleIdGeneratormatrix.id-generator.simple.enabled=true
SnowflakeIdGeneratormatrix.id-generator.snowflake.enabled=true
LeafIdGeneratormatrix.id-generator.segment.enabled=true

八、使用示例

// application.yml
easy:
  id-generator:
    snowflake:
      enabled: true
      worker-id-bits: 10
      sequence-bits: 12
      epoch: 1672531200000  # 2023-01-01

// 注入
@Service
public class OrderService {
    @Autowired
    private IdGenerator idGenerator;

    public Order createOrder(...) {
        Long id = idGenerator.nextId();
        // 批量生成
        List<Long> batch = idGenerator.nextId(10);
        return new Order(id, ...);
    }
}

// 解析雪花 ID
SnowflakeIdGenerator sf = (SnowflakeIdGenerator) idGenerator;
SnowflakeIdGenerator.IdInfo info = sf.parseId(123456789L);
// info.getTimestamp() → 毫秒时间戳(相对 EPOCH)
// info.getWorkerId() → 节点 ID
// info.getSequence() → 同毫秒内序号

九、关键设计决策

决策内容理由
IdGenerator 接口返回 Long(非原始 long)方便 batchSize 边界值处理、null 可扩展但调用方需注意 NPE(当前三种实现永不为 null)
雪花 EPOCH 硬编码 1672531200000L(2023-01-01)减少位空间浪费2023 年前生成的业务 ID 不兼容;README 可注明
时钟回拨阈值硬编码 5ms(未用 maxClockBackwardMs 字段)简化 v1 实现SnowflakeProperties.allowClockBackward / maxClockBackwardMs 在生产代码中从未使用;可作为 v2 修复项
SimpleIdGenerator 的 @PostConstruct init()从 SnowflakeProperties 读 startValue让 Simple 模式启动时从配置初始化,无需手动 reset()
LeafIdGenerator @Autowired 注入 IDGen segmentServiceleaf-core 的 IDGen 作为 Spring Bean 自动注册到容器如果 leaf-core 自动配置未生效,需要手动定义 IDGen Bean
雪花 workerId 从 NetworkInterface 取本地自发现机制MAC 地址低 bits 取模后作为 workerId;生产环境多节点需确保 MAC 不同
batchSize ≤ 0 抛 IllegalArgumentExceptionfail-fast调用方批量生成边界条件

十、测试覆盖

当前共 33 例(全绿):

测试类用例覆盖点
SimpleIdGeneratorTest8递增 / batchSize / reset / getCurrentValue / setStartValue + init / batchSize=0 / batchSize=-1
SnowflakeIdGeneratorTest13位数校验(<1/>20/>20/>22 和)/ compactMode / nextId 连续递增 / 唯一性 / batchSize / parseId 还原 timestamp+workerId+sequence / IdInfo.toString / conditionMet / conditionNotMet
LeafIdGeneratorTest6反射注入 IDGen / default bizTag / custom bizTag / Status≠SUCCESS 抛 RuntimeException / batchSize=0 / batchSize=-1
IdGeneratorPropertiesTest3enabled 默认 true / database 默认驱动 / segment 配置
SimpleGenPropertiesTest2默认值 / setter
SnowflakePropertiesTest1默认值(workerIdBits=10 / sequenceBits=12 / epoch=null / allowClockBackward=true / maxClockBackwardMs=5)

可测程度 4/5:SimpleIdGenerator / SnowflakeIdGenerator 纯 JVM;LeafIdGenerator 依赖 leaf-core Mock 但易 mock(Result/Status 都是 POJO)。Snowflake 时钟回拨阈值硬编码 5ms——不测试 maxClockBackwardMs 字段(已知 bug)。

踩坑:

  1. StateMachineException — COLA StateMachineFactory 不允许重复 id
  2. Result 构造器参数顺序 — leaf-core Result(long id, Status status) 而非 (Status, long)
  3. Mock withContext(Runnable) — mock default 方法不会自动调用 Runnable,需 doAnswer(inv -> inv.getArgument(0, Runnable.class).run())
  4. 雪花 clock backward 硬编码 5ms — maxClockBackwardMs 字段不生效,测试不覆盖