AI摘要
作者因分库分表放弃数据库自增ID,对比UUID、Leaf-segment、Redis后选型Snowflake,借助ZooKeeper持久化workerId并校验时间戳,解决时钟回拨,实现Spring Boot开箱即用的IdGenerator,线上稳定运行。
当数据库开始分库分表,自增主键的局限性暴露无遗。我经历了从UUID到Redis序列,最终选择并深度实践Snowflake算法的全过程。本文不仅对比方案,更分享了我们在生产环境中解决Snowflake时钟回拨问题的最佳实践。
- 为什么需要分布式ID?
- 场景: 用户表数据量过大,决定分库分表(如分成4个库,每个库8张表)。
- 问题: 如果继续使用数据库自增ID,会导致不同表、甚至不同库中出现相同的ID,无法作为全局唯一标识。同时,自增ID有连续性,容易暴露业务量,且可能被猜测。
- 各种方案对比与我踩的坑
方案一:UUID
- 优点: 生成简单,本地生成,无需网络调用,全球唯一。
缺点(我踩的坑):
- 无序性: 作为数据库主键,插入时会导致B+树频繁的页分裂,严重影响写入性能。
- 长度长: 存储空间大,查询效率相对较低。
- 结论: 不适合作为数据库主键,尤其在高并发写入场景下。
方案二:数据库号段模式(Leaf-segment)
- 原理: 在数据库中维护一张表,记录业务标识和当前最大ID。每次申请一个号段(如1-1000),用完再申请。
- 优点: 趋势递增,生成的ID是数字,性能尚可。
- 缺点: 强依赖数据库,数据库挂则系统停。有网络开销。
方案三:Redis INCR
- 原理: 利用Redis的原子操作
INCR或INCRBY来生成序列。 - 优点: 性能比数据库好。
- 缺点: 同样存在单点依赖问题。需要保证Redis的高可用,增加了系统复杂性。
- 原理: 利用Redis的原子操作
- 最终选择:Snowflake算法及其深度实践
算法原理: 一个64位的Long型数字,结构为:
符号位(0) + 时间戳(41位) + 工作机器ID(10位) + 序列号(12位)。- 时间戳: 毫秒级,可用约69年。
- 工作机器ID: 可配置,支持最多1024个节点。
- 序列号: 同一毫秒内最多生成4096个ID。
- 优点: 本地生成,性能极高(每秒百万级);ID趋势递增;数字类型,存储查询效率高。
核心挑战:时钟回拨问题及我们的解决方案
- 问题: 当服务器本地时钟发生回拨(如与网络时间服务器同步时),可能会导致生成的ID重复。
- 解决方案1(默认,轻量级): 如果回拨时间很短(如毫秒级、秒级),则让算法等待相应的毫秒数再继续生成。
// 伪代码
long currentTimestamp = timeGen();
if (currentTimestamp < lastTimestamp) { // 发生时钟回拨
long offset = lastTimestamp - currentTimestamp;
if (offset <= 5) { // 假设最大容忍5毫秒的回拨
try {
wait(offset); // 等待直到时间追平
currentTimestamp = timeGen();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 回拨时间过长,无法等待,需要更复杂的处理
throw new ClockMovedBackwardsException("时钟回拨异常");
}
}解决方案2(我们的生产级方案): 将工作机器ID(workerId)与序列号序列分开持久化。我们使用ZooKeeper(或Etcd)的持久顺序节点来分配workerId。即使时钟发生较大回拨,服务重启后,通过ZooKeeper能保证获取到与之前相同的workerId,然后我们可以在启动时检查上次服务停止时的时间戳,如果当前时间小于它,则不允许启动,并发出严重报警,需要人工介入校对时钟
- 落地与使用
- 我们使用Hutool工具包封装的
Snowflake类,并对其进行了二次封装,解决了时钟回拨问题,并通过Spring Boot自动配置,在应用中注入一个IdGeneratorBean即可使用。 - 代码示例:
@Service
public class OrderService {
@Autowired
private IdGenerator idGenerator; // 我们封装的雪花算法生成器
public void createOrder(OrderDTO orderDTO) {
Order order = new Order();
// 生成分布式ID
order.setId(idGenerator.nextId());
// ... 其他业务逻辑
orderMapper.insert(order);
}
}总结
- 选型思考: 没有完美的方案,只有最适合的。Snowflake在性能、存储、全局唯一性上取得了很好的平衡,是当前互联网公司最主流的选择。
- 关键点: 实现Snowflake算法不难,难的是在生产环境中稳定运行。时钟回拨问题是核心,必须根据业务的容忍度设计可靠的应对策略。