Loading...
正在加载...
请稍候

RFC-001: 生产级业务ID生成策略

✨步子哥 (steper) 2025年11月12日 13:21

状态: Draft
作者: Steper
创建日期: 2025-11-12
最后更新: 2025-11-12
相关组件: backend-service, BusinessIdGeneratorService


1. 背景与问题

1.1 当前实现

智柴网(zhichai.graph)目前使用 Redisson RAtomicLong 生成业务ID,核心实现在 BusinessIdGeneratorService.java

private long next(String key, long start) {
    RAtomicLong seq = redissonClient.getAtomicLong(key);
    if (seq.get() == 0L && start > 0) {
        seq.set(start);
    }
    return seq.incrementAndGet();
}

支持的ID类型

  • User ID (biz:id:user)
  • Server ID (biz:id:server)
  • Channel ID (biz:id:channel)
  • Message ID (biz:id:message)
  • Role ID (biz:id:role)
  • Attachment ID (biz:id:attachment)
  • Reaction ID (biz:id:reaction)
  • Thread ID (biz:id:thread)
  • Audit Log ID (biz:id:auditlog)
  • Ban Record ID (biz:id:banrecord)

数据库约束

  • Neo4j 中对所有业务ID字段建立了 UNIQUE 约束(见 create-unique-constraints.cypher

1.2 已发现的问题

测试环境问题(已临时缓解)

  1. ID 冲突:并发测试类分配相同ID,触发 Neo4j 唯一约束违规

    • 现象:ConstraintValidationFailed: Node(xxx) already exists with label Role and property role_id = 1001
    • 临时方案:测试环境下从 1_000_000_000 + (currentTimeMillis % 1_000_000_000) 开始
  2. 测试隔离不足:Redis 计数器跨测试类共享,导致序列持续累积

生产环境潜在风险

风险类别 具体问题 影响等级
单点故障 Redis 不可用时无法生成ID,服务完全不可用 🔴 严重
性能瓶颈 每次生成ID需要网络往返(~1-5ms),高并发下成为瓶颈 🟡 中等
数据一致性 Redis 数据丢失后序列重置,可能导致ID重复 🔴 严重
跨区域延迟 多数据中心部署时,需共享同一Redis,增加延迟 🟡 中等
扩展性限制 单个Redis实例处理所有ID生成请求,无法水平扩展 🟡 中等
时序保证缺失 多实例场景下,ID仅保证单调递增,不保证严格时序 🟢 轻微

1.3 业务需求

  • 全局唯一性:跨所有实例、所有时间段唯一
  • 趋势递增:便于范围查询、分页、调试
  • 高可用:不依赖单点服务,故障时可降级
  • 高性能:支持 10,000+ QPS(单实例)
  • 可读性:长度适中(64位Long),便于日志查看
  • 🔶 时序性:可选,ID包含时间信息(便于追溯、归档)
  • 🔶 分布式友好:支持多实例、多数据中心部署

2. 方案对比

2.1 候选方案

方案 原理 优点 缺点 适用场景
A. Redisson 计数器<br>(当前方案) Redis原子递增 • 实现简单<br>• 全局唯一<br>• 严格递增<br>• ID较小 • 单点依赖Redis<br>• 网络开销大<br>• 数据丢失风险<br>• 跨区域延迟高 单体应用<br>低并发场景
B. 雪花算法<br>(推荐) 时间戳+机器ID+序列号 • 本地生成,无网络开销<br>• 包含时间信息<br>• 分布式友好<br>• 高性能(百万QPS) • ID较大(18-19位)<br>• 需要workerId分配<br>• 时钟回拨问题 分布式系统<br>高并发场景
C. UUID 随机/时间戳混合 • 完全无依赖<br>• 实现极简<br>• 跨系统唯一 • 128位(太长)<br>• 无序(索引性能差)<br>• 不可读 需要跨系统唯一<br>对性能要求不高
D. 数据库序列 PostgreSQL SERIAL<br>Neo4j 自增 • 原生支持<br>• 事务一致性 • 单点依赖DB<br>• 性能瓶颈<br>• Neo4j不支持原生序列 单体+关系型DB
E. 外部ID服务 独立微服务 • 中心化管理<br>• 灵活策略 • 增加运维成本<br>• 网络依赖<br>• 单点故障 大型企业<br>多系统共享

2.2 推荐方案:雪花算法(Snowflake)

选择理由

  1. ✅ 符合所有核心需求(唯一性、性能、高可用)
  2. ✅ 适配当前架构(Spring Boot 微服务)
  3. ✅ 成熟方案(Twitter、美团、百度等大厂验证)
  4. ✅ 实现成本低(已有成熟库:Hutool、Leaf)

3. 推荐方案详细设计

3.1 雪花算法结构

64位 Long 组成

┌─1bit─┬─────41bit─────┬──10bit──┬──12bit──┐
│ 符号 │   时间戳(ms)   │ 机器ID  │ 序列号  │
└──────┴───────────────┴─────────┴─────────┘
   0      xxxxxxxxxx      xxxxx     xxxxxxxx
  • 符号位(1bit):固定为0(Long为正数)
  • 时间戳(41bit):毫秒级时间戳(相对于起始时间)
    • 可用约 69 年(2^41 / (1000 * 60 * 60 * 24 * 365) ≈ 69.7)
    • 起始时间建议:2025-01-01 08:00:00(1735660800000L)
  • 机器ID(10bit):支持 1024 个实例(0-1023)
    • 可拆分为:5bit 数据中心ID + 5bit 机器ID
  • 序列号(12bit):单毫秒内的递增序列(0-4095)
    • 单实例理论QPS:4096 * 1000 = 409万/秒

示例ID

时间戳: 2025-11-13 04:58:25.123
机器ID: 5
序列号: 100

生成ID: 1952148847000100 (约16位数字)

3.2 核心实现

使用 Hutool 提供的 Snowflake 工具类(已在项目中):

package com.zhichai.backend.service;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;

@Service
public class SnowflakeIdGeneratorService {
    
    private static final long EPOCH = 1735660800000L; // 2025-01-01 08:00:00
    
    private final ConcurrentHashMap<String, Snowflake> generators = new ConcurrentHashMap<>();
    
    @Value("\({zhichai.id.worker-id:1}")
    private long workerId;
    
    @Value("\){zhichai.id.datacenter-id:1}")
    private long datacenterId;
    
    private Snowflake getGenerator(String type) {
        return generators.computeIfAbsent(type, k -> {
            // 为每种类型分配不同的 workerId 偏移(可选优化)
            long typeOffset = Math.abs(type.hashCode() % 32);
            long finalWorkerId = (workerId + typeOffset) % 1024;
            return IdUtil.getSnowflake(finalWorkerId, datacenterId);
        });
    }
    
    public long nextUserId() {
        return getGenerator("user").nextId();
    }
    
    public long nextServerId() {
        return getGenerator("server").nextId();
    }
    
    public long nextChannelId() {
        return getGenerator("channel").nextId();
    }
    
    public long nextMessageId() {
        return getGenerator("message").nextId();
    }
    
    public long nextRoleId() {
        return getGenerator("role").nextId();
    }
    
    // ... 其他类型 ...
    
    /**
     * 从ID中提取时间戳(用于调试/监控)
     */
    public long extractTimestamp(long id) {
        return ((id >> 22) + EPOCH);
    }
}

3.3 配置管理

application.yml

zhichai:
  id:
    # 机器ID分配策略:
    # - 开发环境: 0-99
    # - 测试环境: 100-199
    # - 预发环境: 200-299
    # - 生产环境: 300-1023
    worker-id: \({WORKER_ID:1}           # 从环境变量读取
    datacenter-id:\){DATACENTER_ID:1}   # 数据中心ID

Docker/K8s 部署

# docker-compose.yml
environment:
  WORKER_ID: \({POD_INDEX:-1}  # 使用Pod索引作为workerId
  DATACENTER_ID: 1
```

### 3.4 WorkerId 分配策略

| 方案 | 实现 | 优点 | 缺点 |
|-----|------|-----|------|
| **静态配置**
(推荐-短期) | 配置文件/环境变量 | 简单、可控 | 需要手动管理 | | **Redis自动分配** | 启动时从Redis获取可用ID | 自动化 | 依赖Redis | | **ZooKeeper** | 使用ZK节点编号 | 自动化、持久 | 增加依赖 | | **MAC地址哈希** | 网卡MAC的哈希值 | 无需配置 | 可能冲突 | **推荐实现**(短期): ```java @Value("\)
{zhichai.id.worker-id:#{T(java.net.InetAddress).getLocalHost().hashCode()
% 1024}}") private long workerId;

长期方案:使用 Redis 分布式锁自动分配并缓存到本地。


4. 迁移策略

4.1 迁移原则

  • 零停机:不影响线上业务
  • 可回滚:任何阶段可快速回退
  • 数据兼容:新老ID共存,逐步切换
  • 充分验证:每个阶段都有验证点

4.2 分阶段迁移

阶段 1:准备阶段(1-2天)

目标:代码准备、兼容层实现

步骤

  1. 新增 SnowflakeIdGeneratorService(与 BusinessIdGeneratorService 并存)

  2. 实现适配器模式,保持接口一致:

    @Service
    @Primary  // 优先使用雪花算法
    public class HybridIdGeneratorService implements IdGenerator {
    
        @Autowired
        private SnowflakeIdGeneratorService snowflakeGenerator;
    
        @Autowired
        private BusinessIdGeneratorService redisGenerator;
    
        @Value("\({zhichai.id.strategy:snowflake}") // snowflake | redis
           private String strategy;
           
           public long nextUserId() {
               return "snowflake".equals(strategy) 
                   ? snowflakeGenerator.nextUserId() 
                   : redisGenerator.nextUserId();
           }
       }
       ```
    
    3. 添加单元测试验证雪花算法:
       - 唯一性测试(并发100万次生成)
       - 趋势递增测试
       - 时钟回拨模拟测试
    
    **验证点**:
    - ✅ 单元测试全部通过
    - ✅ 本地集成测试可正常生成ID
    
    ---
    
    #### 阶段 2:灰度测试(3-5天)
    
    **目标**:测试环境全量切换、生产环境观察
    
    **步骤**:
    1. **测试环境切换**:
       ```yaml
       # application-test.yml
       zhichai:
         id:
           strategy: snowflake
           worker-id:\){TEST_WORKER_ID:100}
    
  3. 生产环境双写(不读)

    public long nextUserId() {
        long snowflakeId = snowflakeGenerator.nextUserId();
        long redisId = redisGenerator.nextUserId(); // 仅用于监控对比
    
        // 记录两种ID到日志/监控
        log.debug("ID generated: snowflake={}, redis={}", snowflakeId, redisId);
    
        return "snowflake".equals(strategy) ? snowflakeId : redisId;
    }
    
  4. 监控指标

    • ID生成延迟(P50, P99, P999)
    • ID唯一性校验(定时扫描Neo4j)
    • 时钟回拨事件计数
    • 序列号溢出告警

验证点

  • ✅ 测试环境运行7天无异常
  • ✅ 性能指标:雪花算法延迟 < 1ms(Redis方案 3-5ms)
  • ✅ 无唯一约束冲突

阶段 3:生产切换(1天)

目标:生产环境切换到雪花算法

步骤

  1. 选择低峰期(如凌晨2:00)执行切换

  2. 配置切换(灰度发布):

    # 第一批实例(10%流量)
    zhichai:
      id:
        strategy: snowflake
        worker-id: 300  # 生产环境起始ID
    
  3. 观察1小时,无异常后逐步扩大范围(30% → 50% → 100%)

  4. 全量切换后观察24小时

回滚预案(如有异常):

# 立即回滚配置
zhichai:
  id:
    strategy: redis  # 切回Redis

验证点

  • ✅ 业务指标无异常(注册、消息发送、服务器创建等)
  • ✅ 错误率无上升
  • ✅ 响应时间无退化

阶段 4:清理阶段(1周后)

目标:移除旧代码、优化配置

步骤

  1. 删除 BusinessIdGeneratorService(保留代码历史)

  2. 删除 Redis 中的计数器键:

    redis-cli DEL biz:id:user biz:id:server biz:id:channel ...
    
  3. 简化配置,移除 strategy 开关

  4. 文档更新(API文档、运维手册)


4.3 数据兼容性

无需数据迁移

  • ✅ 新老ID都是 Long 类型,Neo4j 字段类型无需修改
  • ✅ 唯一约束继续有效(雪花ID保证唯一)
  • ✅ 查询逻辑无需改动(业务代码不感知)

ID混合存在示例

旧ID(Redis):1001, 1002, 1003, ...
新ID(雪花) :1952148847000100, 1952148847000101, ...

两种ID不会冲突(雪花ID远大于Redis ID)。


5. 回滚方案

5.1 回滚场景

场景 触发条件 回滚操作 恢复时间
配置回滚 切换后发现性能/稳定性问题 修改 strategy: redis < 5分钟
代码回滚 雪花算法有严重Bug Git revert + 重新部署 < 30分钟
数据修复 ID冲突(极端情况) 见下方脚本 视数据量

5.2 紧急回滚步骤

步骤1:配置回滚

# 修改配置中心或环境变量
export ID_STRATEGY=redis

# 重启服务(滚动重启,保证可用性)
kubectl rollout restart deployment backend-service

步骤2:验证

# 查看日志确认使用Redis生成ID
kubectl logs -f backend-service-xxx | grep "ID generated"

步骤3:清理雪花ID数据(如需要)

// 仅在测试环境执行!生产环境需谨慎评估
// 删除雪花ID生成的节点(ID > 10亿)
MATCH (u:User) WHERE u.user_id > 1000000000 DELETE u;
MATCH (s:Server) WHERE s.server_id > 1000000000 DELETE s;
// ... 其他节点类型

5.3 数据修复脚本

场景:极端情况下出现ID冲突(概率 < 0.0001%)

// 1. 检测重复ID
MATCH (r:Role)
WITH r.role_id AS roleId, count(r) AS cnt
WHERE cnt > 1
RETURN roleId, cnt;

// 2. 修复重复节点(保留创建时间最早的)
MATCH (r:Role)
WITH r.role_id AS roleId, collect(r) AS nodes
WHERE size(nodes) > 1
WITH roleId, nodes, nodes[0] AS keepNode, tail(nodes) AS deleteNodes
UNWIND deleteNodes AS dn
DETACH DELETE dn;

// 3. 验证
MATCH (r:Role)
WITH r.role_id AS roleId, count(r) AS cnt
WHERE cnt > 1
RETURN roleId, cnt;  // 应该返回空结果

6. 实施计划

6.1 时间表

阶段 时间 负责人 里程碑
代码开发 第1-2天 后端团队 SnowflakeIdGeneratorService 实现完成
单元测试 第2天 QA团队 100万次并发唯一性测试通过
测试环境部署 第3天 DevOps 测试环境切换完成
灰度观察 第4-7天 全员 监控指标正常
生产切换 第8天 后端+DevOps 生产环境切换完成
稳定性观察 第9-14天 SRE团队 7天无故障
清理工作 第15天 后端团队 旧代码/配置清理完成

6.2 资源需求

资源 数量 用途
后端开发 1人日 代码实现 + 单元测试
QA测试 0.5人日 集成测试 + 压力测试
DevOps 0.5人日 配置管理 + 发布流程
SRE监控 1人日 监控告警配置

6.3 成功标准

  • 功能:所有ID生成接口正常工作
  • 性能:ID生成延迟 < 1ms(P99)
  • 可靠性:7天内无ID冲突事件
  • 可用性:服务可用性 ≥ 99.9%
  • 可观测性:监控大盘完整,告警及时

7. 风险与应对

7.1 技术风险

风险 影响 概率 应对措施
时钟回拨 生成重复ID • 检测时钟回拨并抛异常<br>• 等待时钟追上<br>• 使用本地序列号补偿
WorkerId冲突 多实例ID冲突 • 静态配置严格管理<br>• 启动时校验唯一性<br>• 监控告警
序列号溢出 单毫秒超4096次生成 极低 • 自动等待下一毫秒<br>• 监控序列号使用率
性能退化 切换后性能下降 极低 • 雪花算法性能远超Redis<br>• 压测验证

7.2 业务风险

风险 影响 应对措施
切换期间故障 新用户无法注册 • 选择低峰期<br>• 灰度发布<br>• 快速回滚预案
数据一致性 新老ID混合导致混乱 • ID范围明确区分<br>• 充分测试

7.3 运维风险

风险 影响 应对措施
配置错误 WorkerId重复 • 配置管理流程<br>• 自动化检测脚本
监控盲区 问题发现延迟 • 完善监控大盘<br>• 告警规则配置

8. 监控与告警

8.1 关键指标

# Prometheus 监控指标
metrics:
  - name: id_generation_duration_seconds
    type: histogram
    labels: [type, strategy]
    
  - name: id_generation_total
    type: counter
    labels: [type, strategy, status]
    
  - name: id_clock_callback_total
    type: counter
    help: "时钟回拨事件计数"
    
  - name: id_sequence_overflow_total
    type: counter
    help: "序列号溢出计数"

8.2 告警规则

# AlertManager 告警
alerts:
  - name: IdGenerationHighLatency
    condition: id_generation_duration_seconds{quantile="0.99"} > 0.01
    severity: warning
    message: "ID生成延迟过高: {{ \(value }}s"
    
  - name: IdClockCallback
    condition: rate(id_clock_callback_total[5m]) > 0
    severity: critical
    message: "检测到时钟回拨事件"
    
  - name: IdUniqueConstraintViolation
    condition: rate(neo4j_constraint_violations_total[5m]) > 0
    severity: critical
    message: "Neo4j唯一约束冲突"
```

---

## 9. 参考资料

### 9.1 外部资源

- [Twitter Snowflake](https://github.com/twitter-archive/snowflake)
- [Leaf - 美团分布式ID服务](https://tech.meituan.com/2017/04/21/mt-leaf.html)
- [Hutool Snowflake 文档](https://www.hutool.cn/docs/#/core/工具类/唯一ID工具-IdUtil?id=snowflake)
- [百度UidGenerator](https://github.com/baidu/uid-generator)

### 9.2 内部文档

- 《智柴网架构设计文档》
- 《Redis运维手册》
- 《Neo4j索引与约束规范》

---

## 10. 决策记录

### 10.1 关键决策

| 决策点 | 选项 | 最终选择 | 理由 |
|-------|------|---------|------|
| ID生成策略 | Redis / 雪花 / UUID | **雪花算法** | 性能、可用性、分布式友好 |
| WorkerId分配 | 静态 / Redis / ZK | **静态配置** | 简单可控,后续可升级 |
| 实现库 | Hutool / Leaf / 自研 | **Hutool** | 已引入依赖,稳定成熟 |
| 迁移方式 | 停机 / 双写 / 灰度 | **灰度切换** | 风险最小 |

### 10.2 待决策事项

- [ ] 生产环境 WorkerId 分配规则(静态 vs 自动)
- [ ] 监控大盘设计与实现责任人
- [ ] 是否需要ID解析API(从ID提取时间戳)

---

## 11. 附录

### 11.1 完整代码示例

见独立文件:
- `SnowflakeIdGeneratorService.java`(待实现)
- `HybridIdGeneratorService.java`(兼容层)
- `IdGeneratorTest.java`(单元测试)

### 11.2 配置模板

```yaml
# application-prod.yml
zhichai:
  id:
    strategy: snowflake
    worker-id:\){WORKER_ID}       # 从环境变量注入
    datacenter-id: 1              # 生产数据中心
    
# application-test.yml
zhichai:
  id:
    strategy: snowflake
    worker-id: ${TEST_POD_INDEX:100}
    datacenter-id: 2              # 测试数据中心

11.3 测试用例清单

  • 单线程生成100万ID唯一性测试
  • 多线程并发生成唯一性测试
  • 趋势递增性验证
  • 时间戳提取准确性测试
  • 时钟回拨模拟测试
  • WorkerId冲突检测测试
  • 性能基准测试(QPS、延迟分布)

文档版本历史

版本 日期 作者 变更说明
v0.1 2025-11-12 Steper 初始草案

审批记录

角色 姓名 审批意见 日期
技术负责人 - 待审批 -
架构师 - 待审批 -
CTO - 待审批 -

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录