KCloud-Platform-IoT KCloud-Platform-IoT
首页
  • 活动

    • KCloud-Platform-IoT 开源三周年快乐&父亲节快乐
  • 更新日志

    • 4.0.1版本更新日志【总览】
  • 指南

    • 后端项目启动【单体】
    • 前端项目启动【单体】
    • 后端项目启动【微服务】
    • 前端项目启动【微服务】
    • Docker安装
    • Linux常用命令
    • Docker常用命令
    • JVM性能优化
    • 项目配置
    • COLA代码规范
    • OAuth2.1授权服务器配置
    • OAuth2.1资源服务器配置
    • OAuth2.1认证API配置
    • Nacos配置
    • 网关路由配置
    • 项目常用注解
    • Elasticsearch注解
    • Spring Cloud Alibaba配置
    • Spring Cloud Gateway配置
    • gRPC配置
    • ID生成器
赞助
GitHub (opens new window)
首页
  • 活动

    • KCloud-Platform-IoT 开源三周年快乐&父亲节快乐
  • 更新日志

    • 4.0.1版本更新日志【总览】
  • 指南

    • 后端项目启动【单体】
    • 前端项目启动【单体】
    • 后端项目启动【微服务】
    • 前端项目启动【微服务】
    • Docker安装
    • Linux常用命令
    • Docker常用命令
    • JVM性能优化
    • 项目配置
    • COLA代码规范
    • OAuth2.1授权服务器配置
    • OAuth2.1资源服务器配置
    • OAuth2.1认证API配置
    • Nacos配置
    • 网关路由配置
    • 项目常用注解
    • Elasticsearch注解
    • Spring Cloud Alibaba配置
    • Spring Cloud Gateway配置
    • gRPC配置
    • ID生成器
赞助
GitHub (opens new window)
  • 活动

    • KCloud-Platform-IoT 开源三周年快乐&父亲节快乐
  • 更新日志

    • 4.0.1版本更新日志【总览】
  • 指南

    • 后端项目启动【单体】
    • 前端项目启动【单体】
    • 后端项目启动【微服务】
    • 前端项目启动【微服务】
    • Docker安装
    • Linux常用命令
    • Docker常用命令
    • JVM性能优化
    • 项目配置
    • COLA代码规范
    • OAuth2.1授权服务器配置
    • OAuth2.1资源服务器配置
    • OAuth2.1认证API配置
    • Nacos配置
    • 网关路由配置
    • 项目常用注解
    • Elasticsearch注解
    • Spring Cloud Alibaba配置
    • Spring Cloud Gateway配置
    • gRPC配置
    • ID生成器
      • 目录
      • 1. 统一接口说明
      • 2. Redis 分段 ID 生成器
        • 2.1 原理说明
        • 2.2 Lua 脚本(segment_alloc.lua)
        • 2.3 配置说明(application.yml)
        • 2.4 Java 使用示例
        • 生成单个 ID
        • 手动初始化(非 Spring 容器)
        • 2.5 测试用例
        • 2.6 异常行为说明
      • 3. 雪花算法 ID 生成器
        • 3.1 原理说明
        • ID 位布局(共 64 位)
        • 3.2 Nacos 节点自动分配流程
        • 3.3 时钟回拨处理
        • 3.4 配置说明(application.yml)
        • 3.5 Java 使用示例
        • 生成 ID
        • 手动初始化(非 Spring 容器)
        • 3.6 测试用例
      • 4. 两种策略对比
      • 5. 选型建议
  • 二开指南
  • 指南
KCloud-Platform-IoT
2026-03-05
目录

ID生成器

你好呀,我的老朋友!我是老寇,欢迎来到老寇IoT云平台!

模块路径:laokou-common/laokou-common-id-generator

支持两种策略,均实现 IdGenerator 接口,可在 gRPC 请求参数中动态选择。


# 目录

  1. 统一接口说明
  2. Redis 分段 ID 生成器
  3. 雪花算法 ID 生成器
  4. 两种策略对比
  5. 选型建议

# 1. 统一接口说明

所有生成器均实现 IdGenerator 接口:

public interface IdGenerator {
    void init() throws Exception;                    // 初始化生成器
    void close() throws Exception;                   // 关闭生成器(释放资源)
    long nextId(BizType bizType);                    // 生成单个 ID
    List<Long> nextIds(BizType bizType, int num);    // 批量生成 ID
    Instant getInstant(long snowflakeId);            // 从雪花ID反推生成时间
    long getDatacenterId(long snowflakeId);          // 从雪花ID提取数据中心ID
    long getWorkerId(long snowflakeId);              // 从雪花ID提取机器ID
    long getSequence(long snowflakeId);              // 从雪花ID提取序列号
}

BizType 为业务类型枚举,用于区分不同业务线的 ID 空间(如 BizType.AUTH)。


# 2. Redis 分段 ID 生成器

# 2.1 原理说明

基于美团 Leaf-Segment 思路,采用双 Buffer 异步预加载方案:

┌─────────────────────────────────────────────────────────────────┐
│                       Redis INCRBY (Lua 原子脚本)                │
│   每次批量申请号段(step=10000),返回本次号段的 maxId             │
└──────────────────────┬──────────────────────────────────────────┘
                       │ 号段 [minId, maxId]
              ┌────────▼────────┐
              │  SegmentBuffer  │  双 Buffer(A / B 交替使用)
              │  ┌───────────┐  │
              │  │ Segment A │◄─┼── 当前正在使用(AtomicLong 本地自增)
              │  └───────────┘  │
              │  ┌───────────┐  │
              │  │ Segment B │◄─┼── 异步预加载(消耗到 loadFactor 时触发)
              │  └───────────┘  │
              └─────────────────┘

核心流程:

  1. 启动时通过 Lua 脚本 INCRBY key step 原子申请首个号段
  2. 本地 AtomicLong.getAndIncrement() 高速分配 ID,无需每次访问 Redis
  3. 当前号段消耗到 loadFactor(默认 80%)时,虚拟线程异步加载下一号段到备用 Buffer
  4. 当前号段耗尽,原子切换到备用 Buffer 继续分配,无中断
  5. 关闭时回写 cursor 到 Redis,重启后从上次位置继续,避免 ID 大幅跳跃

# 2.2 Lua 脚本(segment_alloc.lua)

-- KEYS[1] = 号段 Key
-- ARGV[1] = 步长(step)
local key = KEYS[1]
local step = tonumber(ARGV[1])
return redis.call('INCRBY', key, step)
-- 返回值 = 本次号段的 maxId
-- minId = maxId - step + 1

# 2.3 配置说明(application.yml)

spring:
  id-generator:
    segment:
      node-id: 1           # 节点标识(可选)
      configs:
        AUTH:                     # BizType 枚举的 code 值,可配置多个
          step: 10000             # 每次从 Redis 申请的号段步长(默认 10000)
          key: "id-generator:segment:auth"              # Redis 号段计数 Key
          cursor-key: "id-generator:segment:cursor:auth:1" # 断点续传 Key
          load-factor: 0.8        # 消耗到 80% 时异步预加载下一号段
        ORDER:
          step: 5000
          key: "id-generator:segment:order"
          cursor-key: "id-generator:segment:cursor:order:1"
          load-factor: 0.7
配置项 默认值 说明
step 10000 每次从 Redis 批量申请的 ID 数量,越大访问 Redis 越少,但重启浪费 ID 越多
key id-generator:segment Redis 号段计数 Key
cursor-key id-generator:segment:cursor 重启断点续传 Key(关闭时写入当前 cursor)
load-factor 0.8 触发异步预加载的号段消耗比例,范围 (0, 1)

# 2.4 Java 使用示例

# 生成单个 ID

@Service
public class OrderService {

    private final IdGenerator idGenerator;

    public OrderService(RedisSegmentIdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    public long createOrder() {
        // ID 为正整数,同一号段内严格递增
        return idGenerator.nextId(BizType.ORDER);
    }

    public List<Long> createBatchOrders(int count) {
        // 批量生成,适合批量插入场景
        return idGenerator.nextIds(BizType.ORDER, count);
    }
}

# 手动初始化(非 Spring 容器)

SpringSegmentProperties props = new SpringSegmentProperties();
SpringSegmentProperties.SegmentConfig config = new SpringSegmentProperties.SegmentConfig();
config.setStep(100);
config.setKey("id-generator:segment:test:auth");
config.setCursorKey("id-generator:segment:cursor:test:auth");
config.setLoadFactor(0.8);
props.setConfigs(Map.of(BizType.AUTH.getCode(), config));

RedisSegmentIdGenerator generator = new RedisSegmentIdGenerator(redisUtils, props);
// 必须先初始化
generator.init();

long id = generator.nextId(BizType.AUTH);
List<Long> ids = generator.nextIds(BizType.AUTH, 50);

// 优雅关闭,回写 cursor 到 Redis
generator.close();

# 2.5 测试用例

// 验证 ID 唯一性(500 个 ID 无重复)
@Test
void test_nextId_uniqueness() {
    Set<Long> ids = new HashSet<>();
    for (int i = 0; i < 500; i++) {
        long id = generator.nextId(BizType.AUTH);
        Assertions.assertThat(ids.add(id)).isTrue();
    }
    Assertions.assertThat(ids).hasSize(500);
}

// 验证 ID 单调递增
@Test
void test_nextId_increasing() {
    long id1 = generator.nextId(BizType.AUTH);
    long id2 = generator.nextId(BizType.AUTH);
    Assertions.assertThat(id2).isGreaterThan(id1);
}

// 验证跨号段切换(step=100,生成 250 个会触发 2 次切换)
@Test
void test_nextId_crossSegmentSwitch() {
    Set<Long> ids = new HashSet<>();
    for (int i = 0; i < 250; i++) {
        long id = generator.nextId(BizType.AUTH);
        Assertions.assertThat(ids.add(id)).isTrue();
    }
    Assertions.assertThat(ids).hasSize(250);
}

// 验证批量生成
@Test
void test_nextIds_batch() {
    List<Long> ids = generator.nextIds(BizType.AUTH, 50);
    Assertions.assertThat(ids).hasSize(50);
    Assertions.assertThat(ids).allMatch(id -> id > 0);
    Assertions.assertThat(new HashSet<>(ids)).hasSize(50); // 无重复
}

# 2.6 异常行为说明

场景 异常类型 消息
未调用 init() IllegalStateException "not initialized"
close() 后调用 IllegalStateException "not initialized"
等待下一号段超时(约 10s) RuntimeException "Timeout waiting for next segment"
Redis 连接失败(重试 3 次后) RuntimeException "Failed to load segment from Redis after 3 attempts"
调用 getInstant/getDatacenterId 等 UnsupportedOperationException 分段模式不支持雪花ID反解析

# 3. 雪花算法 ID 生成器

# 3.1 原理说明

基于 Twitter Snowflake 算法,结合 Nacos 服务注册中心自动分配节点标识,解决集群环境中节点 ID 冲突问题。

# ID 位布局(共 64 位)

┌────┬────────────────────────┬─────────────┬──────────┬─────────────┐
│ 0  │      时间戳(41位)     │ DC(5位)   │ M(5位) │  SEQ(13位)│
└────┴────────────────────────┴─────────────┴──────────┴─────────────┘
 符号  相对 startTimestamp 的     数据中心ID    机器ID      序列号
 位    毫秒偏移
字段 位数 最大值 说明
符号位 1 — 固定为 0(确保正整数)
时间戳 41 ~69 年 相对于 startTimestamp 的毫秒偏移
datacenterId 5 31 数据中心 ID(Nacos 自动分配)
machineId 5 31 机器 ID(Nacos 自动分配)
序列号 13 8191 同一毫秒内最多 8192 个 ID
  • 最大节点数:32 × 32 = 1024 个
  • 单节点每毫秒 TPS:8192

# 3.2 Nacos 节点自动分配流程

启动
 └─► 获取 Nacos 所有注册实例的 Metadata(datacenter_id + machine_id)
 └─► 找到最小未占用的 (dc, machine) 组合槽位
 └─► 将 {datacenter_id, machine_id, grpc_port} 写入本实例 Metadata 并注册
 └─► 再次拉取实例列表,检查是否有冲突(并发注册场景)
      ├── 无冲突 → 初始化成功,订阅实例变更事件
      └── 有冲突 → 注销本实例,重新分配(最多重试 10 次)

# 3.3 时钟回拨处理

回拨幅度 处理方式
≤ 5ms 等待 2 倍偏移时间后重新检查,仍回拨则抛异常
> 5ms 立即抛出 RuntimeException,拒绝生成 ID

# 3.4 配置说明(application.yml)

spring:
  id-generator:
    snowflake:
      # ⚠️ 起始时间戳,默认 2020-06-15 00:00:00 UTC
      # 一旦投产设定,永远不可修改!否则可能产生重复 ID
      start-timestamp: 1592150400000
  application:
    name: laokou-distributed-id-snowflake  # Nacos 注册服务名
  cloud:
    nacos:
      discovery:
        cluster-name: iot-cluster           # Nacos 集群名
  grpc:
    server:
      port: 10111  # gRPC 端口,写入 Nacos 实例 Metadata

# 3.5 Java 使用示例

# 生成 ID

@Service
public class UserService {

    private final IdGenerator idGenerator;

    public UserService(NacosSnowflakeIdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    // 生成单个 ID
    public long createUser() {
        return idGenerator.nextId(BizType.AUTH);
    }

    // 批量生成
    public List<Long> createBatchUsers(int count) {
        return idGenerator.nextIds(BizType.AUTH, count);
    }

    // 从 ID 反推生成时间(雪花模式独有)
    public Instant getCreateTime(long userId) {
        return idGenerator.getInstant(userId);
    }

    // 从 ID 中拆解各字段(排查问题用)
    public void inspectId(long userId) {
        long datacenterId = idGenerator.getDatacenterId(userId);
        long machineId    = idGenerator.getWorkerId(userId);
        long sequence     = idGenerator.getSequence(userId);
        Instant createAt  = idGenerator.getInstant(userId);
        System.out.printf("ID=%d, dc=%d, machine=%d, seq=%d, time=%s%n",
            userId, datacenterId, machineId, sequence, createAt);
    }
}

# 手动初始化(非 Spring 容器)

NamingService namingService = NamingUtils.createNamingService("127.0.0.1:8848");

SpringSnowflakeProperties props = new SpringSnowflakeProperties();
// 2021-01-01 00:00:00 UTC
props.setStartTimestamp(1609459200000L);

NacosSnowflakeIdGenerator generator = new NacosSnowflakeIdGenerator(
    configManager, serviceManager, props, environment
);

// 向 Nacos 注册,分配 datacenterId + machineId
generator.init();

long id = generator.nextId(BizType.AUTH);

// 取消 Nacos 订阅,清理资源
generator.close();

# 3.6 测试用例

// 验证 1000 个 ID 完全唯一
@Test
void test_nextId_uniqueness() {
    Set<Long> ids = new HashSet<>();
    for (int i = 0; i < 1000; i++) {
        Assertions.assertThat(ids.add(generator.nextId(BizType.AUTH))).isTrue();
    }
    Assertions.assertThat(ids).hasSize(1000);
}

// 验证 datacenterId 在有效范围 [0, 31]
@Test
void test_getDatacenterId() {
    long id = generator.nextId(BizType.AUTH);
    Assertions.assertThat(generator.getDatacenterId(id)).isBetween(0L, 31L);
}

// 验证 machineId 在有效范围 [0, 31]
@Test
void test_getWorkerId() {
    long id = generator.nextId(BizType.AUTH);
    Assertions.assertThat(generator.getWorkerId(id)).isBetween(0L, 31L);
}

// 验证序列号在有效范围 [0, 8191]
@Test
void test_getSequence() {
    long id = generator.nextId(BizType.AUTH);
    Assertions.assertThat(generator.getSequence(id)).isBetween(0L, 8191L);
}

// 验证从 ID 反推时间(误差在 ±1秒内)
@Test
void test_getInstant() {
    Instant before = Instant.now();
    long id = generator.nextId(BizType.AUTH);
    Instant after = Instant.now();
    Instant idInstant = generator.getInstant(id);
    Assertions.assertThat(idInstant).isBetween(before.minusSeconds(1), after.plusSeconds(1));
}

# 4. 两种策略对比

对比维度 Redis 分段 雪花算法
ID 特性 趋势递增(号段内严格递增) 趋势递增(毫秒内序列随机起步)
依赖组件 Redis Nacos
时间信息 ❌ 不含 ✅ 可从 ID 反推生成时间
单节点峰值 TPS 极高(内存自增,无网络开销) 高(8192 / ms)
最大集群节点数 无限制(按 BizType 水平扩展) 1024 个
时钟回拨影响 ❌ 完全不受影响 ⚠️ >5ms 拒绝生成
重启 ID 连续性 ✅ cursor 持久化,断点续传 ✅ 自然递增
ID 反解析 ❌ 不支持 ✅ 可提取时间 / 节点 / 序列
典型适用场景 订单号、业务流水号、数据库主键 日志 ID、链路追踪 ID

# 5. 选型建议

需要从 ID 中获取时间 / 节点信息?
    ├── 是 → 雪花算法(NacosSnowflakeIdGenerator)
    └── 否 → 优先考虑 Redis 分段(RedisSegmentIdGenerator)
              ID 更短、更连续,对 B+ 树索引更友好

经验法则:

  • 数据库主键 / 业务流水号 → 分段模式(ID 紧凑,写入性能好)
  • 分布式日志 / 链路追踪 → 雪花模式(天然携带时间戳,便于排查)

⚠️ 重要:startTimestamp 一旦投产设定,永远不可修改。若修改,在相同毫秒内同一节点可能生成出历史重复 ID。

我是老寇,我们下次再见啦!

上次更新: 3/5/2026, 10:22:08 PM
gRPC配置

← gRPC配置

Theme by Vdoing | Copyright © 2022-2026 laokou | Apache 2.0 License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式