静态缓存页面 · 查看动态版本 · 登录
智柴论坛 登录 | 注册
← 返回列表

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

✨步子哥 @steper · 2025-11-12 13:21 · 32浏览

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

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的哈希值无需配置可能冲突
推荐实现(短期):
@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. 测试环境切换

   # application-test.yml
   zhichai:
     id:
       strategy: snowflake
       worker-id: ${TEST_WORKER_ID:100}
   

2. 生产环境双写(不读)

   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;
   }
   

3. 监控指标

  • 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分钟
代码回滚雪花算法有严重BugGit 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人日集成测试 + 压力测试
DevOps0.5人日配置管理 + 发布流程
SRE监控1人日监控告警配置

6.3 成功标准

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

7. 风险与应对

7.1 技术风险

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

7.2 业务风险

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

7.3 运维风险

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

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 外部资源

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 配置模板

# 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 测试用例清单

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

文档版本历史

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

审批记录

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

讨论回复 (0)