
系列导读
上一篇的UUID方案适合无依赖场景,但如果你的系统已经部署了Redis(缓存)或ZooKeeper(服务注册中心),没必要再引入雪花算法、号段模式等新方案------直接复用现有中间件就能实现分布式ID,减少系统依赖和维护成本。
本文详解Redis和ZooKeeper的分布式ID实现方案,附实战代码,帮你快速复用现有组件落地。
一、适用场景
- 系统已部署Redis/ZooKeeper(避免重复引入中间件);
- 中低并发场景(Redis:QPS万级+;ZooKeeper:QPS千级);
- 需ID有序(支持排序、分页);
- 代表业务:
- Redis:电商优惠券ID、活动ID、中小型系统订单ID;
- ZooKeeper:配置中心标识、分布式锁关联ID、低并发业务ID。
二、方案1:Redis分布式ID(基于INCR原子操作)
1. 核心原理
Redis的INCR/INCRBY命令是原子操作,支持跨客户端递增,天然适合生成自增ID:
- 原理:用Redis键(如
order_id_generator)存储当前最大ID,每次生成ID时执行INCR key,返回递增后的ID; - 唯一性:Redis是单线程模型,
INCR命令原子执行,不会出现并发冲突; - 有序性:ID严格递增,支持排序和分页。
2. 进阶优化:避免ID重置+增加业务前缀
- 问题:Redis重启后,键值会丢失,ID会从1重新开始,导致重复;
- 解决方案:
- Redis开启持久化(RDB+AOF),避免重启后数据丢失;
- ID中加入时间戳前缀(如
20240520_1001),即使Redis重置,也不会与历史ID重复。
3. 实战代码(Spring Boot+Redis)
3.1 核心依赖(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 Redis配置(application.yml)
yaml
spring:
redis:
host: 127.0.0.1
port: 6379
password: # 若Redis有密码则填写
database: 0
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接数
3.3 核心代码实现
java
@Service
public class RedisIdGeneratorService {
@Autowired
private StringRedisTemplate redisTemplate;
// 业务前缀(避免不同业务ID冲突)
private static final String PREFIX_ORDER = "ORDER_";
private static final String PREFIX_COUPON = "COUPON_";
// Redis键(按业务区分)
private static final String REDIS_KEY_ORDER = "id_generator:order";
private static final String REDIS_KEY_COUPON = "id_generator:coupon";
// 生成订单ID(时间戳前缀+Redis自增ID)
public String generateOrderId() {
return generateId(PREFIX_ORDER, REDIS_KEY_ORDER);
}
// 生成优惠券ID
public String generateCouponId() {
return generateId(PREFIX_COUPON, REDIS_KEY_COUPON);
}
// 通用生成逻辑
private String generateId(String prefix, String redisKey) {
// 1. 获取当前日期前缀(yyyyMMdd,如20240520)
String datePrefix = new SimpleDateFormat("yyyyMMdd").format(new Date());
// 2. Redis原子递增(步长=1)
Long incrId = redisTemplate.opsForValue().increment(redisKey, 1);
// 3. 拼接ID(前缀+日期+6位自增ID,不足6位补0)
String suffix = String.format("%06d", incrId);
return prefix + datePrefix + "_" + suffix;
}
// 测试方法
public static void main(String[] args) {
// 模拟生成订单ID
RedisIdGeneratorService generator = new RedisIdGeneratorService();
String orderId = generator.generateOrderId();
System.out.println("订单ID:" + orderId); // 输出示例:ORDER_20240520_000101
}
}
3.4 调用示例
java
@Controller
@RequestMapping("/coupon")
public class CouponController {
@Autowired
private RedisIdGeneratorService idGeneratorService;
@PostMapping("/create")
public ResponseEntity<?> createCoupon() {
// 生成优惠券ID(Redis方案)
String couponId = idGeneratorService.generateCouponId();
// 后续优惠券创建逻辑
return ResponseEntity.ok("优惠券创建成功,ID:" + couponId);
}
}
4. 优点&缺点
- 优点:
- 复用现有Redis组件,无需额外部署;
- 原子操作,ID唯一有序;
- 实现简单,支持业务前缀和日期前缀,可读性好;
- 性能高(Redis单机QPS万级+,支持主从复制)。
- 缺点:
- 依赖Redis高可用(Redis宕机后无法生成ID,需主从+哨兵);
- Redis持久化配置不当,重启后ID可能重复;
- 高并发场景(10万+QPS)下,Redis可能成为瓶颈。
三、方案2:ZooKeeper分布式ID(基于持久顺序节点)
1. 核心原理
ZooKeeper支持创建"持久顺序节点",每次创建节点时,ZooKeeper会自动在节点名称后追加一个有序编号(如id_order_00000001),截取该编号作为分布式ID:
- 原理:
- 创建父节点(如
/id_generator/order); - 每次生成ID时,在父节点下创建临时顺序节点(如
/id_generator/order/temp_); - 获取节点名称,截取末尾的有序编号(如
00000001)作为ID; - 删除临时节点(避免节点过多占用资源)。
- 创建父节点(如
- 唯一性:ZooKeeper保证顺序节点的编号全局唯一;
- 有序性:编号递增,支持排序。
2. 实战代码(Java+ZooKeeper)
2.1 核心依赖(pom.xml)
xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2 核心代码实现
java
public class ZkIdGenerator {
// ZooKeeper连接地址
private static final String ZK_CONNECT_STR = "127.0.0.1:2181";
// 会话超时时间(3秒)
private static final int SESSION_TIMEOUT = 3000;
// 父节点路径(按业务区分)
private static final String PARENT_NODE_ORDER = "/id_generator/order";
private ZooKeeper zk;
// 初始化ZooKeeper连接
public ZkIdGenerator() throws IOException, InterruptedException {
zk = new ZooKeeper(ZK_CONNECT_STR, SESSION_TIMEOUT, event -> {
// 监听连接状态
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
synchronized (this) {
this.notify();
}
}
});
synchronized (this) {
this.wait(); // 等待连接建立
}
// 创建父节点(不存在则创建)
createParentNode(PARENT_NODE_ORDER);
}
// 创建父节点
private void createParentNode(String parentNode) {
try {
if (zk.exists(parentNode, false) == null) {
// 持久节点(PERSISTENT)
zk.create(parentNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (Exception e) {
throw new RuntimeException("创建ZooKeeper父节点失败", e);
}
}
// 生成分布式ID
public Long generateId() throws Exception {
// 1. 创建临时顺序节点(EPHEMERAL_SEQUENTIAL)
String tempNode = PARENT_NODE_ORDER + "/temp_";
String nodePath = zk.create(tempNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 2. 截取有序编号(如"/id_generator/order/temp_0000000123" → "0000000123")
String sequence = nodePath.substring(nodePath.lastIndexOf("_") + 1);
// 3. 转换为Long型ID
Long id = Long.parseLong(sequence);
// 4. 删除临时节点(可选,避免节点过多)
zk.delete(nodePath, -1);
return id;
}
// 测试方法
public static void main(String[] args) throws Exception {
ZkIdGenerator generator = new ZkIdGenerator();
Long orderId = generator.generateId();
System.out.println("ZooKeeper生成订单ID:" + orderId); // 输出示例:123
}
}
3. 优点&缺点
- 优点:
- 复用现有ZooKeeper组件,无需额外部署;
- 天然支持分布式协同,ID唯一有序;
- 临时顺序节点自动删除,无资源残留。
- 缺点:
- 性能一般(ZooKeeper QPS千级,不适合高并发);
- 部署复杂(ZooKeeper需集群部署,保证高可用);
- 网络开销大(每次生成ID需创建节点、删除节点,涉及网络通信)。
四、避坑指南:2个方案的关键问题解决
1. Redis方案避坑
- 坑1:Redis主从切换导致ID重复;
解决方案:用Redis哨兵或集群模式,确保主从切换时数据同步完成,避免从库未同步最新自增ID就提供服务。 - 坑2:Redis持久化配置不当导致ID重置;
解决方案:开启AOF持久化(appendonly yes),并设置appendfsync everysec(每秒同步),确保自增ID数据不丢失。
2. ZooKeeper方案避坑
- 坑1:ZooKeeper集群脑裂导致ID重复;
解决方案:部署ZooKeeper集群(至少3台节点),配置合理的选举参数(如tickTime=2000),避免脑裂。 - 坑2:节点过多导致ZooKeeper性能下降;
解决方案:用临时顺序节点(自动删除),或定时清理历史节点。
五、Redis vs ZooKeeper方案对比
| 特性 | Redis分布式ID | ZooKeeper分布式ID |
|---|---|---|
| 性能 | 高(QPS万级+) | 低(QPS千级) |
| 实现复杂度 | 简单(原子操作) | 中等(节点创建/删除) |
| 部署成本 | 低(单机即可,集群可选) | 高(需集群部署) |
| 适用并发 | 中高并发 | 低并发 |
| 典型场景 | 优惠券ID、活动ID、中小型订单ID | 配置中心标识、分布式锁关联ID |
实战Tips
- 优先复用Redis:如果系统已部署Redis,优先选Redis方案(性能高、实现简单);
- Redis ID前缀:建议加入业务前缀+日期前缀,避免ID重复和泄露敏感信息;
- ZooKeeper慎用高并发:ZooKeeper适合低并发场景,高并发下建议选雪花算法或号段模式;
- 高可用配置:Redis/ZooKeeper作为ID生成组件,必须保证高可用(集群/主从),否则会阻塞业务。
下一篇预告
如果你的系统已经使用了分布式数据库(如TiDB、OceanBase),还有更简单的方案------直接使用分布式数据库自带的全局ID功能,无需额外开发。
下一篇我们拆解"分布式数据库方案":TiDB和OceanBase的全局ID配置,帮你无缝集成分布式ID!
你在项目中用Redis还是ZooKeeper生成ID?评论区聊聊~