点评--day04--全局唯一 ID

7 阅读4分钟

📚 2.3.1 全局唯一 ID

一、 核心痛点:为什么不能用数据库自增 ID?

在以前简单的单体项目中,我们习惯把数据库主键设置为 AUTO_INCREMENT(自增)。但在高并发的电商或秒杀场景中,这种做法有三大致命缺陷:

  1. id 规律太明显(安全性低): 自增 ID 是 1, 2, 3 连着的。如果你的订单 ID 是自增的,竞争对手只要今天下一单,明天下一单,两个 ID 一减,就能精确推算出你一天的真实订单量!
  2. 受单表数据量限制(无法分库分表): 当订单表数据达到千万级别,必须拆分成多个表(甚至多个库)。如果继续用自增,表 A 和表 B 都会生成 ID 为 1 的订单,这就会导致主键冲突
  3. 数据库性能瓶颈: 秒杀瞬间有极高并发的写请求,完全依赖 MySQL 来生成 ID,MySQL 的抗压能力会成为整个系统的短板。

二、 全局唯一 ID 的四大黄金标准

为了应对分布式高并发场景,我们需要自己设计或引入外部组件来生成 ID。一个合格的全局唯一 ID 必须满足以下条件:

  • 唯一性: 绝对不能出现重复的 ID(最基本的要求)。
  • 高可用与高性能: 生成 ID 的速度必须极快,且不能因为 ID 生成器宕机导致整个下单流程阻塞。
  • 递增性: ID 最好是整体趋势递增的。因为 MySQL InnoDB 引擎的索引是 B+ 树结构,递增的主键能极大提高数据库的插入性能,减少页分裂。
  • 安全性: ID 不能带有明显的连号规律,防止被恶意推算。

三、 主流 ID 生成策略大比拼

在业界,解决全局唯一 ID 通常有以下几种方案:

方案原理与特点优点缺点
UUID基于机器网卡、时间等生成的 32 位字符串。本地生成,性能极高,绝对唯一。无序的字符串,作为 MySQL 主键会导致索引性能极差;且太长,占用空间。
雪花算法 (Snowflake)Twitter 开源的算法,利用 64 位 Long 型划分出时间戳、机器号、序列号。性能极高,不依赖外部中间件,趋势递增。强依赖机器时钟,如果时钟回拨会导致 ID 重复或生成失败。
Redis 方案(本项目采用) 利用 Redis 的 INCR 命令实现自增,拼接时间戳。不依赖数据库,Redis 内存操作性能极高,天然单线程保证原子性。需要引入 Redis 中间件,增加了一次网络开销。

四、 本项目的 Redis 唯一 ID 设计图解

为了兼顾性能、递增性和安全性,本项目巧妙地利用了 Long 类型(64 个 bit 位)和 Redis 的自增特性,设计了如下的 ID 结构:

64 个 bit 位的具体划分:

  1. 符号位(1 bit): 永远为 0,保证 ID 都是正数。
  2. 时间戳(31 bit): 以秒为单位。记录的是当前时间减去某个自定义的起始时间(比如 2022-01-01 00:00:00)。31 个 bit 最大可以表示 23112^{31} - 1 秒,大约能用 69 年
  3. 序列号(32 bit): 真正的自增数字,秒内的并发号。支持每秒产生 2322^{32} 个不同的 ID(高达 42 亿),这对于任何秒杀系统都绝对够用了!

💡 Redis Key 的绝妙设计(按天自增):

在调用 Redis 的 INCR 命令时,我们不会用一个固定不变的 Key(比如 icr:order),而是会在 Key 后面拼接上当天的日期(比如 icr:order:20260308)。

  • 好处 1: 防止 32 位的序列号被无限累加最终溢出。每天从 0 重新开始算,绝对安全。
  • 好处 2(白嫖的统计功能): 因为 Key 是按天划分的,日后只要查一下 Redis 里某个日期的 Key 的值,就能直接知道这一天总共生成了多少个订单,连数仓统计都省了!

五、 学习总结

这一节的核心在于转变思维:从单机时代的“依赖数据库分配 ID”,转向分布式时代的“由应用程序或缓存中间件主动计算分配 ID”。 使用 Redis 拼接“时间戳 + 序列号”的方案,既保证了高性能,又隐藏了真实的单量信息。