如何实现高可用且多实例无重复输出的增量式token id生成器
高可用增量全局唯一Token ID生成器技术方案
核心需求确认
你需要的生成器核心特性为:多实例部署下绝对不生成重复ID、ID整体呈增量趋势、服务高可用,以下是生产级可落地的两类实现方案:
可选实现方案
1. 号段预分配模式
这是工业界最常用的方案,核心逻辑是给每个实例预先分配独占的连续ID区间,本地消耗完再重新申请:
- 实现逻辑
- 维护一张全局号段分配表,存储每个业务线的当前最大已分配ID、号段步长等信息
- 每个实例启动或当前号段即将耗尽时,向号段管理服务发起申请,通过事务性操作抢占一段独占ID区间,例如步长为1000时,实例申请到的区间为
[1001,2000],其他实例不会再拿到这个区间的ID - 实例在本地内存中维护当前号段的剩余ID计数,按顺序生成即可
- 优势
- 绝对唯一:号段分配操作是原子性的,不存在重复分配的可能
- 性能极高:绝大多数请求在本地生成ID,无需跨网络调用,单实例QPS可达10万以上
- 增量保证:ID整体是趋势递增的,单个实例内部生成的ID严格连续递增
- 高可用:实例拿到号段后,即使号段管理服务短时间宕机,也可以继续生成ID,只要在号段消耗完成前恢复服务即可
- 灵活度高:步长可根据业务QPS动态调整,支持多业务线隔离
- 优化点
- 可以提前预申请下一个号段,避免当前号段耗尽时等待申请的阻塞问题
2. 雪花算法(Snowflake)变种方案
完全本地化生成的方案,不需要依赖任何外部服务,核心是对64位Long型ID的位数做拆分:
- 实现逻辑
- 标准64位ID拆分规则:1位固定符号位(恒为0,保证ID为正) + 41位毫秒级时间戳(可支持使用69年) + 10位全局唯一工作机器ID + 12位单毫秒内序列号(单毫秒最多生成4096个ID)
- 唯一保证:只要所有实例的工作机器ID全局唯一,就不会生成重复ID
- 工作机器ID可通过配置中心统一分配、实例IP+端口哈希、容器环境Pod唯一标识生成等方式保证唯一性
- 优势
- 无外部依赖:完全本地生成,性能最高,单实例QPS可达百万级别
- 增量保证:ID按时间戳递增,整体呈严格的时间顺序递增
- 高可用:只要实例本身可用就能生成ID,不存在外部单点故障
- 注意事项
- 必须解决时钟回拨问题:可通过时钟回拨等待、预留序列号位兜底、绑定实例时钟校验规则等方式处理
- 可根据业务需求调整位数分配:例如需要更多实例时,可将工作机器ID调整为12位,最多支持4096个实例同时运行
选型建议
如果业务要求ID严格全局连续递增,优先选择号段预分配模式;如果追求极致性能、希望减少外部依赖,选择解决了时钟回拨问题的雪花算法变种方案。
高可用保障措施
- 号段模式下,号段分配的数据库需要做主从集群+自动故障切换,保证号段申请链路的可用性
- 不管选择哪种方案,都需要部署旁路校验监控:抽样采集生成的ID落库,定期校验是否存在重复ID,出现异常立即告警
- 生成器实例跨可用区部署,避免单可用区故障导致服务完全不可用
核心代码示例
号段表结构
CREATE TABLE `id_segment` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键', `biz_type` varchar(64) NOT NULL COMMENT '业务类型标识', `current_max_id` bigint NOT NULL DEFAULT '0' COMMENT '当前已分配的最大ID', `step` int NOT NULL COMMENT '号段步长', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_biz_type` (`biz_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ID号段分配表';
号段申请逻辑
// 事务性申请号段 @Transactional(rollbackFor = Exception.class) public Segment applyNewSegment(String bizType) { // 原子更新当前最大ID jdbcTemplate.update("UPDATE id_segment SET current_max_id = current_max_id + step WHERE biz_type = ?", bizType); // 查询更新后的号段信息 Map<String, Object> segInfo = jdbcTemplate.queryForMap("SELECT current_max_id, step FROM id_segment WHERE biz_type = ?", bizType); Long maxId = (Long) segInfo.get("current_max_id"); Integer step = (Integer) segInfo.get("step"); // 返回当前实例独占的号段区间 return new Segment(maxId - step + 1, maxId); }
内容的提问来源于stack exchange,提问作者DevEngine
相关产品推荐
相关产品推荐

