Redis Stream 消息 ID 生成机制

Redis Stream 消息 ID 生成机制

Stream 消息 ID 的结构

Redis Stream 中的每条消息都有一个全局唯一的 ID,其格式为:

-

例如:1700000000000-01700000000001-5

两部分含义

  1. millisecondsTime(毫秒时间戳):消息生成时的 Unix 毫秒时间戳
  2. 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

  1. 业务相关的顺序:使用业务时间戳作为 ID
  2. 去重场景:业务方生成唯一 ID 保证幂等性
  3. 迁移场景:导入数据时保持原有 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
# 等价于查询这段毫秒范围内的所有消息

面试要点

核心知识点

  1. ID 格式毫秒时间戳-序列号
  2. 单调递增保证:Redis 确保每个新 ID 都大于之前的 ID
  3. 时钟回滚处理:使用最大 ID 继续递增,不依赖系统时钟
  4. 自动 vs 自定义:默认自动生成满足分布式唯一,自定义可控制顺序
  5. 序列号 64 位溢出:序列号是 64 位整数,几乎不可能溢出

常见问题

Q: 多条消息在同一毫秒内生成,顺序如何保证?
A: 同一毫秒内序列号从 0 开始递增,Redis 使用自增计数器保证顺序。

Q: Stream ID 可以作为全局唯一 ID 吗?
A: 可以。时间戳-序号 在单节点上是全局唯一的,但在 Redis 集群中不同节点可能生成相同的 ID,这时需要额外的节点标识。

Q: Stream ID 的长度有限制吗?
A: 理论无限(字符串),但越短性能越好,因为需要比较大小。

Q: 自定义 ID 时有什么限制?
A: 必须大于 Stream 中现有的最大 ID,格式必须是 数字-数字

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容