Redis Stream 消息 ID 生成机制
Stream 消息 ID 的结构
Redis Stream 中的每条消息都有一个全局唯一的 ID,其格式为:
-
例如:1700000000000-0、1700000000001-5
两部分含义
- millisecondsTime(毫秒时间戳):消息生成时的 Unix 毫秒时间戳
- sequenceNumber(序列号):同一毫秒内的递增序号,从 0 开始
1700000000000-0 → 时间戳:1700000000000ms,序号:0
1700000000000-1 → 同一毫秒内的第二条消息
1700000000001-0 → 下一毫秒的第一条消息
ID 的自动生成
使用 XADD 自动生成
# 不指定 ID,由 Redis 自动生成
XADD mystream * field1 value1 field2 value2
# 返回: 1700000000000-0
星号 * 告诉 Redis 自动生成消息 ID。
自动生成的规则
// Redis 源码逻辑(简化)
function generateStreamID() {
var ms = current_time_ms();
var seq = get_sequence(ms); // 获取当前毫秒的序列号
if (ms < last_generated_ms) {
// 时钟回拨处理:使用最后的毫秒+自增序列
ms = last_generated_ms;
seq = last_sequence + 1;
} else if (ms == last_generated_ms) {
// 同一毫秒:序列号 +1
seq = last_sequence + 1;
} else {
// 新的毫秒:序列号从 0 开始
seq = 0;
}
update_state(ms, seq);
return format("%d-%d", ms, seq);
}
时钟回滚的处理
Redis 的设计考虑到了系统时钟回滚的情况。
时钟回滚场景
场景:系统时间从 1700000000100 回滚到 1699999999900
正常序列:1700000000100-0, 1700000000100-1
时钟回滚后试图生成:
1699999999900-0 → Redis 检测到时间戳小于最后生成的 ID
→ 会使用 1700000000100-2 继续生成
确保单调递增
# 即使时钟回滚,Redis 保证 ID 单调递增
XADD mystream * a 1 # 1700000000100-2(即使时钟已回滚)
XADD mystream * a 2 # 1700000000100-3
自定义 ID
何时需要自定义 ID
- 业务相关的顺序:使用业务时间戳作为 ID
- 去重场景:业务方生成唯一 ID 保证幂等性
- 迁移场景:导入数据时保持原有 ID
自定义 ID 的规则
# 自定义 ID 必须满足:
# 1. 比 Stream 中上一条消息的 ID 大
# 2. 格式为 ms-seq
# 合法的自定义 ID
XADD mystream 1700000000000-0 a 1 # 从 0 开始
# 非法的自定义 ID
XADD mystream abc-def a 1 # 格式错误
# 或者在已有 ID 1700000000000-0 后使用 1699999999999-0
XADD mystream 1699999999999-0 a 2 # 错误:不能小于最新 ID
使用场景示例
# 场景:导入旧系统的消息,保留原有顺序
old_messages = get_old_messages_sorted()
for i, msg in enumerate(old_messages):
# 使用原有时间戳生成 ID
custom_id = f"{msg['timestamp_ms']}-{i}"
redis.xadd('migrated_stream', custom_id, msg['data'])
ID 相关的关键命令
# 按 ID 范围查询
XRANGE mystream 1700000000000-0 1700000000000-100
# 获取 Stream 中的第一个和最后一个消息 ID
XLEN mystream # 消息总数
XRANGE mystream - + COUNT 1 # 第一条消息
XRANGE mystream - + COUNT 1 DESC # 最后一条消息
# 从指定 ID 之后开始读取
XREAD COUNT 10 BLOCK 0 STREAMS mystream 1700000000000-0
ID 对性能的影响
1. 基数树(Radix Tree)存储
Redis Stream 使用基数树存储消息,ID 是键值:
- ID 越连续,存储效率越高
- ID 跨度大,树的分支更多
2. 查询效率
# 按时间范围查询非常高效(因为 ID 包含时间戳)
XRANGE mystream 1700000000000 1700001000000
# 等价于查询这段毫秒范围内的所有消息
面试要点
核心知识点
- ID 格式:
毫秒时间戳-序列号 - 单调递增保证:Redis 确保每个新 ID 都大于之前的 ID
- 时钟回滚处理:使用最大 ID 继续递增,不依赖系统时钟
- 自动 vs 自定义:默认自动生成满足分布式唯一,自定义可控制顺序
- 序列号 64 位溢出:序列号是 64 位整数,几乎不可能溢出
常见问题
Q: 多条消息在同一毫秒内生成,顺序如何保证?
A: 同一毫秒内序列号从 0 开始递增,Redis 使用自增计数器保证顺序。
Q: Stream ID 可以作为全局唯一 ID 吗?
A: 可以。时间戳-序号 在单节点上是全局唯一的,但在 Redis 集群中不同节点可能生成相同的 ID,这时需要额外的节点标识。
Q: Stream ID 的长度有限制吗?
A: 理论无限(字符串),但越短性能越好,因为需要比较大小。
Q: 自定义 ID 时有什么限制?
A: 必须大于 Stream 中现有的最大 ID,格式必须是 数字-数字。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容