SequenceGenerator 是高并发有序顺序号生成中间件的核心对外暴露接口。调用方只需注入此接口即可获取顺序号,无需关心底层 Redis / DB 的状态机流转与降级逻辑。
接口根据业务需求分为两大模式,共计 6 个核心方法:
- 日期模式(完整 ID):
业务前缀 + 日期 + 补齐序号,每日重置。 - 永久模式(纯序号):仅需
递增序号,永不重置,永久无极递增。
核心特征:
- 返回格式:
{前缀}{yyyyMMdd}{补齐序号}(例如:ORDER20260319001) - 重置周期:按天重置(每天的序号从头开始计算)
- 底层机制 (Redis):Key =
seq:{seqKey}:{yyyyMMdd},带有过期时间 (TTL, 默认48小时)。 - 底层机制 (DB降级):主键
curr_date为当天实际日期(如2026-03-19)。
功能:使用指定的业务前缀生成下一个完整的带日期的顺序号。
/**
* @param seqKey 业务标识(如 "ORDER", "USER")
* @return 例子:ORDER20260319001
*/
String nextId(String seqKey);功能:使用默认业务前缀生成下一个完整的带日期的顺序号。(默认前缀在 application.yml 中通过 sequence.generator.prefix 配置)。
/**
* @return 例子:SEQ20260319001
*/
String nextId();核心特征:
- 返回格式:纯数字递增序号。可以定长补齐为字符串 (如
"001"),也可以是原始数值 (如1L)。 - 重置周期:永不重置,永久递增。
- 底层机制 (Redis):Key =
seq:{seqKey},无日期后缀,不设置过期时间 (TTL)。 - 底层机制 (DB降级):主键
curr_date使用固定的哨兵日期9999-12-31,确保与日期模式共享同一张表的结构而互相不干扰。
功能:使用指定的业务标识,生成永久递增、并在左侧定长补齐的字符串序号。
/**
* @param seqKey 业务标识(如 "USER_SN")
* @return 例子:"001", "002" ... "999", "1000"
*/
String nextValue(String seqKey);功能:使用默认的业务标识,生成永久递增并定长补齐的字符串序号。
/**
* @return 例子:"001"
*/
String nextValue();功能:使用指定的业务标识,获取最原始的永久递增 long 类型数值。
适用场景:调用方不需要中间件进行格式化,而是希望拿到 long 数值后用自己的特定规则进行组合转换(如转化为36进制,或者进行雪花算法的位移)。
/**
* @param seqKey 业务标识(如 "USER_SN")
* @return 例子:1, 2, 3...
*/
long nextRawValue(String seqKey);功能:使用默认的业务标识,获取最原始的永久递增 long 类型数值。
/**
* @return 例子:1, 2, 3...
*/
long nextRawValue();假设在 application.yml 中的配置如下:
sequence:
generator:
prefix: JX # 默认前缀
seq-length: 4 # 补齐长度 4 位在 2026 年 3 月 19 日当天的调用结果对比:
| 调用方法 | 内部业务标识 | 是否带有日期 | 是否按天重置 | 输出结果示例 |
|---|---|---|---|---|
| nextId("ORDER") | ORDER |
✅ 是 (20260319) |
✅ 是 | "ORDER202603190001" |
| nextId() | JX (默认) |
✅ 是 (20260319) |
✅ 是 | "JX202603190001" |
| nextValue("VIP") | VIP |
❌ 否 | ❌ 否 (永久递增) | "0001" (第1次), "0002" |
| nextValue() | JX (默认) |
❌ 否 | ❌ 否 (永久递增) | "0001" (第1次), "0002" |
| nextRawValue("PAY") | PAY |
❌ 否 | ❌ 否 (永久递增) | 1 (类型为 long), 2 |
为您排查问题时参考:
Redis 层面:
- 完整 ID 对应的 Redis Key:
seq:ORDER:20260319(有 TTL 过期时间) - 纯序号 对应的 Redis Key:
seq:VIP(无过期时间,永不失效)
MySQL DB 降级层面 (sys_sequence 表):
| seq_key | curr_date | curr_value | version | 说明 |
|---|---|---|---|---|
| ORDER | 2026-03-19 | 15 | 15 | nextId("ORDER") 产生的降级/同步记录 |
| VIP | 9999-12-31 | 582 | 582 | nextValue("VIP") 产生的降级/同步记录,日期为固定哨兵值 |
在您的 pom.xml 中引入由于本工程打出的 Starter 包后,无需提供任何 @Bean,所有的客户端都可以直接注入 SequenceGenerator。
import com.jixu.sequence.core.SequenceGenerator;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderService {
@Autowired
private SequenceGenerator sequenceGenerator;
public void createOrder() {
// 使用默认前缀生成带日期的单号: JX20260319001
String orderNo = sequenceGenerator.nextId();
}
}整个组件暴露了 2 种模式、共计 6 个方法 供业务使用。
特点:按日重置序号,Redis Key 每天过期。
存储结构:{前缀}{yyyyMMdd}{补齐序号} (如:"ORDER20260319001")
- nextId(String seqKey): 使用自定义的前缀和业务标识(如
ORDER)。 - nextId(): 使用 application.yml 里
sequence.generator.prefix配置的默认前缀。
特点:Redis 序号永不过期,DB 中的日期采用统一的哨兵值 9999-12-31,序号长期累加。
- nextValue(String seqKey): 返回补齐好的定长字符串序号。如:"001", "002"。
- nextValue(): 使用默认业务标识,返回补齐的序号字符串。
- nextRawValue(String seqKey): 返回原始未格式化的
long类型的序号,如1L, 2L。 - nextRawValue(): 使用默认标识返回原始
long序号。
sequence:
generator:
# ------------------ 基础配置 ------------------
prefix: JX # 默认的业务前缀 (nextId() 等空参方法使用)
seq-length: 3 # 序号补全位数,不足会用 0 填充 (如 001)
recovery-interval: 100 # DB 降级态下,每隔多少次请求探活一次 Redis 是否恢复
expire-seconds: 172800 # Redis Key 默认的过期时间 (48小时)
max-retry: 5 # DB 乐观锁自增在遇到冲突时的最大重试次数
# ------------------ DB 同步配置 ------------------
# 负责控制 Redis 主数据每次自增后,如何异步传递给 MySQL 作为防抖备份。
# 可选值: THREAD_POOL 或 KAFKA
sync-mode: THREAD_POOL
# sync-mode = THREAD_POOL 时的专有配置
thread-pool:
core-size: 2 # 线程池常驻核心线程数
max-size: 8 # 线程池最大上限
queue-capacity: 2000 # 任务队列容量。打满后将降级由调用者线程直接入库,防丢失
thread-name-prefix: seq-db-sync-
# sync-mode = KAFKA 时的专有配置 (注意:使用此模式必须确保 pom.xml 引入了 spring-kafka)
kafka:
topic: sequence-db-sync # 生产者投递消息、消费者监听的共同 Topic
waste-topic: sequence-waste # 新增:记录被废弃未使用的序列号的专门 Topic
group-id: sequence-sync-consumer # 消费者属组如果您尚未创建防灾同步表,请在您配置的被连入的 MySQL 执行如下脚本:
CREATE TABLE IF NOT EXISTS `sys_sequence` (
`seq_key` VARCHAR(50) NOT NULL COMMENT '业务标识,如:JX, ORDER',
`curr_date` DATE NOT NULL COMMENT '当前日期,如:2026-03-19',
`curr_value` INT NOT NULL DEFAULT 0 COMMENT '当前已使用的最大序列号',
`version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
PRIMARY KEY (`seq_key`, `curr_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全局顺序号生成表';
CREATE TABLE IF NOT EXISTS `sys_sequence_waste` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`seq_key` VARCHAR(50) NOT NULL COMMENT '业务标识,如:JX, ORDER',
`waste_sequence` VARCHAR(100) NOT NULL COMMENT '废弃的具体序列号,包含前缀和日期的完整 ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '废号记录的时间',
INDEX `idx_seq_key` (`seq_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='废弃序列号存根表';本组件采用了 "Redis 主生成 + DB 异步备份 + DB 乐观锁降级" 的高可用架构设计。
- 极致性能:正常情况下,所有的 ID 生成请求全部由 Redis 的 Lua 脚本(或
INCR)在内存中原子完成,无锁、无阻塞。 - 数据防丢:生成序列号后,通过线程池(或 Kafka)异步将最新值同步到 DB 中(Upsert 记录最大值)。
- 平滑降级:当 Redis 宕机时,系统瞬间切换为 DB 乐观锁自增模式,继续提供服务。由于 DB 一直在异步备份,所以降级时不会出现序列号回退。
- 安全恢复:当探测到 Redis 恢复后,系统冻结所有请求,从 DB 中读取当前的最大序列号来初始化 Redis,确保恢复后也是严格递增的。
整个系统围绕三种状态进行运转:
stateDiagram-v2
[*] --> NORMAL : 启动初始化
NORMAL --> FAILOVER : Redis 连接超时/执行异常
FAILOVER --> RECOVERING : 达到 N 次请求探活发现 Redis 恢复
RECOVERING --> NORMAL : DB 最大值同步到 Redis 完成
RECOVERING --> FAILOVER : 恢复期间发生异常
sequenceDiagram
participant Client as 客户端线程
participant StateMachine as 状态机 (StateMachineManager)
participant Redis as Redis (主)
participant ThreadPool as 异步线程池 / Kafka
participant MySQL as MySQL (备份)
Client->>StateMachine: 请求 nextId()
StateMachine->>Redis: INCR 获取最新序号 (V)
Redis-->>StateMachine: 返回序号 V
StateMachine->>ThreadPool: 提交异步写 DB 任务 (seqKey, date, V)
StateMachine-->>Client: 组装前缀返回完整 ID
Note right of ThreadPool: 异步执行,不阻塞主线程
ThreadPool->>MySQL: UPSERT ... GREATEST(curr_value, V)
MySQL-->>ThreadPool: 写入成功
sequenceDiagram
participant Client as 客户端线程
participant StateMachine as 状态机 (StateMachineManager)
participant Redis as Redis (宕机)
participant MySQL as MySQL (降级提供服务)
Client->>StateMachine: 请求 nextId()
StateMachine->>Redis: 尝试 INCR
Redis--xStateMachine: Timeout Exception
Note over StateMachine: 捕获异常,将状态切为 FAILOVER
StateMachine->>MySQL: 查询当前最大记录
MySQL-->>StateMachine: 返回 current_value (如 100)
StateMachine->>MySQL: CAS 乐观锁 UPDATE sys_sequence SET curr_value=101 WHERE version=v
MySQL-->>StateMachine: 受到影响行数 = 1 (成功)
StateMachine-->>Client: 返回序号 101
sequenceDiagram
participant Client as 客户端线程 (触碰间隔 N)
participant StateMachine as 状态机
participant Redisson as 分布式锁
participant MySQL as MySQL
participant Redis as Redis (已恢复)
Client->>StateMachine: 请求 nextId(),触发 failoverCounter % N == 0
StateMachine->>Redisson: 尝试获取恢复锁 (tryLock)
Redisson-->>StateMachine: 获取成功
Note over StateMachine: 状态切换为 RECOVERING (阻塞后续请求)
StateMachine->>Redis: 探活 (PING)
Redis-->>StateMachine: PONG (存活)
StateMachine->>MySQL: 读取 DB 中最大序号 (如 500)
MySQL-->>StateMachine: 返回 500
StateMachine->>Redis: SET 强制重置 Redis 的值为 500 (对其 DB 进度)
Redis-->>StateMachine: OK
Note over StateMachine: 状态切回 NORMAL,释放锁
StateMachine->>Redis: INCR 生成最新序号 (501)
Redis-->>StateMachine: 501
StateMachine-->>Client: 返回序号 501
系统提供两种异步持久化 DB 的策略,可以通过 application.yml 的 sync-mode 参数灵活切换:
- 适用场景:单体应用、大多数微服务、对外部依赖要求少的场景。
- 机制:通过内置的有界队列线程池直接执行
sys_sequence表的 Upsert 操作。 - 防丢机制:线程池队列打满后,通过
CallerRunsPolicy降级为同步执行,牺牲一点性能但绝对不丢数据。
- 适用场景:超高并发写、系统本身已重度依赖 Kafka 并希望复用它来做削峰。
- 机制:Redis 拿到号后作为生产者将消息发入指定 Topic,由统一的
@KafkaListener消费者进行写库。 - 乱序容忍:DB 端采用
ON DUPLICATE KEY UPDATE curr_value = GREATEST(curr_value, newValue)语法,无惧消息的乱序消费,只记录最大值。