RedisTimeSeries TS.ADD 的 ON_DUPLICATE LAST 和 SUM 有什么区别
来源:17golang原创
时间:2026-11-05 12:35:11 185浏览 收藏
我第一次遇到这个问题,是在补采一段 RedisTimeSeries 数据时发现某个时间点的数值突然翻倍。写入请求本身没有报错,时间戳也没有变化,真正改变结果的是重试请求里带了 ON_DUPLICATE SUM。如果这类重试代表“同一条采样再次送达”,应该覆盖旧值;如果每次上报代表一笔需要累计的增量,才应该相加。
官方地址:https://redis.io/docs/latest/commands/ts.add/
一句话判断:ON_DUPLICATE LAST把同一时间戳的新值当作修正版,覆盖原值;ON_DUPLICATE SUM把新值当作同一时间戳下的新增量,与旧值相加。两者都只覆盖当前这一次TS.ADD调用,不会替你修改时间序列的长期配置。
一次重复上报为什么会影响整段指标
这类异常的影响面通常不在所有时间点,而只集中在重复写入的时间戳。设备采样、消息重放、网络重试和补数据脚本都可能把同一条记录送来两次。若原始采样是 10,重试请求又带着 12:
- 用
LAST,这个时间点最终表达“最新一次收到的值”,结果是 12; - 用
SUM,这个时间点最终表达“两个值的累计”,结果是 22。
所以排查时不要只看命令是否成功,还要先回答:重复请求是同一采样的重放,还是两次应该累计的增量?这个业务含义决定策略。

时间线里真正的触发条件:时间戳相同
TS.ADD 的时间戳以毫秒为单位,也可以使用 * 让服务端生成当前时间。只有两次采样落在同一个时间戳时,重复策略才会参与决定如何处理旧值。时间戳不同,就不是这篇文章讨论的重复点。
用两个独立的时间序列做对照,可以避免把前一个实验的结果混到后一个实验里:
# 固定一个毫秒时间戳,模拟同一条采样的首次写入 redis-cli TS.ADD meter:last 1710000000000 10 # 同一时间戳再次到达时,用新值覆盖旧值 redis-cli TS.ADD meter:last 1710000000000 12 ON_DUPLICATE LAST # 读取这个时间点,观察 LAST 的最终值 redis-cli TS.RANGE meter:last 1710000000000 1710000000000
这个例子中,第二次写入表达“12 是这个采样点的修正版”,因此最终值应按 12 理解。这里的关键不是第二次请求更新了整条序列,而是它在同一个时间戳上覆盖了原来的样本。
根因对比:LAST 是替换,SUM 是累计
我后来把异常请求拆成两个最小场景:一个场景模拟设备重试,另一个场景模拟电量脉冲或订单件数这类增量。命令结构很像,但读数含义完全不同。
# 首次写入 10,准备一个可以被重试覆盖的采样点 redis-cli TS.ADD meter:last 1710000000000 10 # 重试带来修正值 12,LAST 只保留这次新值 redis-cli TS.ADD meter:last 1710000000000 12 ON_DUPLICATE LAST # 首次写入 10,准备一个需要累加增量的采样点 redis-cli TS.ADD meter:sum 1710000000000 10 # 第二个请求代表新增 12,SUM 将旧值和新值相加 redis-cli TS.ADD meter:sum 1710000000000 12 ON_DUPLICATE SUM # 分别读取两个点,确认比较的是业务语义而不是命令返回格式 redis-cli TS.RANGE meter:last 1710000000000 1710000000000 redis-cli TS.RANGE meter:sum 1710000000000 1710000000000
对这组输入,meter:last 的点应理解为 12,meter:sum 的点应理解为 22。官方命令语义也是如此:LAST 用新值覆盖,SUM 在已有样本时计算“旧值 + 新值”;如果该时间戳还没有旧样本,SUM 就直接使用新值。
这解释了故障的根因:把“重复送达”误判成“新增事件”,会让 SUM 在消息重试时重复计数;反过来,把本应累加的脉冲数据改成 LAST,又会丢掉前一次增量。
修复动作:把策略限制在当前一次 TS.ADD
ON_DUPLICATE 是一次命令级覆盖参数。它只影响当前这条 TS.ADD 如何处理同一时间戳的重复样本,不会把时间序列的长期重复策略永久改掉。这一点很适合修复边界明确的回放任务:你可以让一次补数任务使用 LAST,而不必把整个序列的后续写入逻辑一起改掉。
修复时我会先给数据源分类,再把策略写在调用点:
| 数据来源 | 重复请求代表什么 | 单次 TS.ADD 建议 |
|---|---|---|
| 设备心跳、周期采样 | 同一采样的重试或重放 | ON_DUPLICATE LAST |
| 补采脚本修正历史值 | 人工确认后的新版本 | ON_DUPLICATE LAST |
| 脉冲计数、订单件数增量 | 应该合并的新增量 | ON_DUPLICATE SUM |
| 无法确定的混合来源 | 请求身份和业务含义不清 | 先暂停自动累加,补齐来源字段 |
需要特别注意:如果命令同时负责创建一个不存在的时间序列,ON_DUPLICATE 对这次创建没有可比较的旧样本,因此不会产生覆盖或累加效果。真正的策略差异出现在目标时间序列已经存在、且目标时间戳已有样本时。

防复发:让重试和新增量在入口就分开
只替换命令参数还不够。下一次排查时,至少要保留采样来源、业务事件标识和采样时间戳,让读者能判断两个请求是不是同一条数据。对于可重试的采样上报,调用方应保持同一个时间戳和事件身份;对于累计事件,应该明确每次请求到底代表一个新事件还是同一个事件的重放。
- 在日志中同时记录序列 key、时间戳、上报来源和选择的重复策略;
- 把“消息重试”与“业务新增量”分成不同的写入路径,不要只靠调用方口头约定;
- 修复历史数据前,先确认同一时间戳的旧值是否已经包含部分累计量;
- 将
LAST或SUM写入代码注释和接口文档,避免后续维护者把它当成可互换参数。
最后的判断
如果你面对的是“同一采样再次送达”,选 ON_DUPLICATE LAST;如果你面对的是“同一时间点还要合并一笔新发生的增量”,选 ON_DUPLICATE SUM。两者的命令形式几乎只差一个单词,但它们表达的是覆盖模型和累计模型的区别。发布修复前,先根据数据来源确认重复请求的业务含义,再把策略限定在需要它的那次 TS.ADD。
相关问题
ON_DUPLICATE SUM 第一次写入会不会把值加倍?
不会。指定时间戳没有旧样本时,SUM 使用新值;只有已有样本时才进行旧值与新值相加。
ON_DUPLICATE LAST 会修改整个时间序列的默认策略吗?
不会。它是当前命令的覆盖参数,文章讨论的范围只包括这一次 TS.ADD 的重复样本处理。
两个请求时间戳不同,还需要比较 LAST 和 SUM 吗?
如果时间戳不同,就不属于同一时间戳重复写入。此时应先检查采样时间是否被错误转换、截断或回退,再判断是否需要重复策略。
-
364 收藏
-
166 收藏
-
194 收藏
-
109 收藏
-
数据库 · Redis | 32分钟前 | Redis · 故障排查 · docker redis NAT Redis Cluster MOVED cluster-announce-ip cluster bus242 收藏
-
116 收藏
-
485 收藏
-
246 收藏
-
499 收藏
-
234 收藏
-
487 收藏
-
303 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习