为什么在负载均衡环境下生成唯一ID更复杂
想象一下,你开了三家连锁奶茶店,每家店都能独立接单。突然有个顾客拿着一张订单号为1001的单子来取餐,结果三家店都有一张1001——这显然会出问题。系统也是一样,在负载均衡架构下,多个服务实例并行处理请求,如果每个实例都按自己的方式生成ID,冲突几乎是必然的。
传统单机环境里,靠数据库自增主键或者时间戳就能搞定唯一性。但一旦服务被部署在多台服务器上,这些方法就容易“撞车”。
常见方案对比
UUID 是最直接的选择。它基于随机数、MAC地址和时间戳组合生成,重复概率极低。比如 Java 中调用 UUID.randomUUID() 几乎能立刻拿到一个字符串 ID。但它也有缺点:长度太长(36位),存储浪费,且无序导致数据库插入性能下降。
雪花算法(Snowflake) 是 Twitter 提出的一种分布式ID生成策略。它把 64 位整数划分为时间戳、机器ID、序列号等部分,保证同一毫秒内不同机器产生的ID不重复。实现起来也不难:
public class SnowflakeIdGenerator {
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & 0xFFF;
if (sequence == 0) {
timestamp = waitNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22) | (datacenterId << 17) | (workerId << 12) | sequence;
}
}这个算法的关键是给每台机器分配唯一的 workerId 和 datacenterId,通常可以通过配置文件或启动参数设置。只要不重复,就能避免冲突。
结合数据库的方案
如果你不想自己维护机器编号,也可以借助数据库。比如创建一张专门发号的表:
CREATE TABLE id_generator ({
biz_type VARCHAR(32) PRIMARY KEY,
current_id BIGINT NOT NULL
);每次需要新ID时执行 UPDATE id_generator SET current_id = LAST_INSERT_ID(current_id + 1) WHERE biz_type='order'; SELECT LAST_INSERT_ID();。这种方式依赖数据库的原子操作,适合并发不是特别高的场景。
Redis 自增也是个选择
利用 Redis 的 INCR 命令可以高效生成递增数字。比如调用 INCR order_seq 每次返回一个新值。由于 Redis 是单线程执行命令,天然保证了原子性。而且支持持久化和集群模式,可靠性不错。不过要注意网络延迟和Redis宕机的情况,得有降级预案。
实际应用中的取舍
选哪种方式,得看你的业务特点。如果是订单系统,对ID长度敏感,又希望趋势递增,雪花算法更合适;如果是日志追踪,只求快速生成且不怕长一点,UUID 更省事;如果已有成熟的中间件支持,比如用了 Redis 集群,那用它来发号也能快速落地。
关键点在于:不能让ID生成成为系统的瓶颈,也不能因为换机器、扩容就出乱子。提前规划好机器标识、时间同步机制,比事后补救强得多。