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`: ```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` 工具类(已在项目中): ```java 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 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**: ```yaml zhichai: id: # 机器ID分配策略: # - 开发环境: 0-99 # - 测试环境: 100-199 # - 预发环境: 200-299 # - 生产环境: 300-1023 worker-id: ${WORKER_ID:1} # 从环境变量读取 datacenter-id: ${DATACENTER_ID:1} # 数据中心ID ``` **Docker/K8s 部署**: ```yaml # 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. 实现适配器模式,保持接口一致: ```java @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} ``` 2. **生产环境双写(不读)**: ```java 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. **配置切换**(灰度发布): ```yaml # 第一批实例(10%流量) zhichai: id: strategy: snowflake worker-id: 300 # 生产环境起始ID ``` 3. **观察1小时**,无异常后逐步扩大范围(30% → 50% → 100%) 4. **全量切换后观察24小时** **回滚预案**(如有异常): ```yaml # 立即回滚配置 zhichai: id: strategy: redis # 切回Redis ``` **验证点**: - ✅ 业务指标无异常(注册、消息发送、服务器创建等) - ✅ 错误率无上升 - ✅ 响应时间无退化 --- #### 阶段 4:清理阶段(1周后) **目标**:移除旧代码、优化配置 **步骤**: 1. 删除 `BusinessIdGeneratorService`(保留代码历史) 2. 删除 Redis 中的计数器键: ```bash 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:配置回滚** ```bash # 修改配置中心或环境变量 export ID_STRATEGY=redis # 重启服务(滚动重启,保证可用性) kubectl rollout restart deployment backend-service ``` **步骤2:验证** ```bash # 查看日志确认使用Redis生成ID kubectl logs -f backend-service-xxx | grep "ID generated" ``` **步骤3:清理雪花ID数据(如需要)** ```cypher // 仅在测试环境执行!生产环境需谨慎评估 // 删除雪花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%) ```cypher // 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 | 低 | • 检测时钟回拨并抛异常
• 等待时钟追上
• 使用本地序列号补偿 | | **WorkerId冲突** | 多实例ID冲突 | 中 | • 静态配置严格管理
• 启动时校验唯一性
• 监控告警 | | **序列号溢出** | 单毫秒超4096次生成 | 极低 | • 自动等待下一毫秒
• 监控序列号使用率 | | **性能退化** | 切换后性能下降 | 极低 | • 雪花算法性能远超Redis
• 压测验证 | ### 7.2 业务风险 | 风险 | 影响 | 应对措施 | |-----|------|---------| | **切换期间故障** | 新用户无法注册 | • 选择低峰期
• 灰度发布
• 快速回滚预案 | | **数据一致性** | 新老ID混合导致混乱 | • ID范围明确区分
• 充分测试 | ### 7.3 运维风险 | 风险 | 影响 | 应对措施 | |-----|------|---------| | **配置错误** | WorkerId重复 | • 配置管理流程
• 自动化检测脚本 | | **监控盲区** | 问题发现延迟 | • 完善监控大盘
• 告警规则配置 | --- ## 8. 监控与告警 ### 8.1 关键指标 ```yaml # 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 告警规则 ```yaml # 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 测试用例清单 - [x] 单线程生成100万ID唯一性测试 - [x] 多线程并发生成唯一性测试 - [x] 趋势递增性验证 - [x] 时间戳提取准确性测试 - [ ] 时钟回拨模拟测试 - [ ] WorkerId冲突检测测试 - [ ] 性能基准测试(QPS、延迟分布) --- **文档版本历史**: | 版本 | 日期 | 作者 | 变更说明 | |-----|------|------|---------| | v0.1 | 2025-11-12 | Steper | 初始草案 | --- **审批记录**: | 角色 | 姓名 | 审批意见 | 日期 | |-----|------|---------|------| | 技术负责人 | - | 待审批 | - | | 架构师 | - | 待审批 | - | | CTO | - | 待审批 | - |

讨论回复

0 条回复

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