Redis TimeSeries Go采样压缩问题:目标序列为空或结果异常
Redis TimeSeries 采样/压缩功能问题分析与解决
问题描述
我编写了一段Go代码来理解Redis TimeSeries的采样/压缩功能,但遇到两个问题:
- 使用手动时间戳时,目标序列
td为空,预期得到值4和9; - 改用
client.AddAutoTs自动时间戳后,结果随机(如出现2和7或仅4)。
原代码
func Red() { keyname := "t" var client = rt.NewClient("127.0.0.1:6379", "nohelp", nil) client.CreateKeyWithOptions(keyname, rt.DefaultCreateOptions) client.CreateKeyWithOptions("td", rt.DefaultCreateOptions) client.CreateRule(keyname, rt.MaxAggregation, 5000, "td") for i := 0; i < 10; i++ { timestamp := time.Now() fmt.Println(time.Now().Unix(), timestamp.String()) value := float64(i) client.Add(keyname, timestamp.Unix(), value) time.Sleep(time.Second) } fmt.Println(client.RangeWithOptions(keyname, rt.TimeRangeMinimum, rt.TimeRangeMaximum, rt.DefaultRangeOptions)) fmt.Println(client.RangeWithOptions("td", rt.TimeRangeMinimum, rt.TimeRangeMaximum, rt.DefaultRangeOptions)) }
手动时间戳输出
O/P: [{1677776687 0} {1677776688 1} {1677776689 2} {1677776690 3} {1677776691 4} {1677776692 5} {1677776693 6} {1677776694 7} {1677776695 8} {1677776696 9}] <nil> [] <nil>
自动时间戳示例输出
[{1677777452390 0} {1677777453407 1} {1677777454422 2} {1677777455436 3} {1677777456468 4} {1677777457477 5} {1677777458490 6} {1677777459505 7} {1677777460518 8} {1677777461569 9}] <nil> [{1677777450000 2} {1677777455000 7}] <nil>
原因分析
1. 手动时间戳导致td为空
调用client.Add时传入的是timestamp.Unix()(秒级时间戳),但RedisTimeSeries默认使用毫秒级时间戳。这导致添加到t序列的时间戳被解析为1970年左右的极早时间,而查询td时用的是当前时间范围(rt.TimeRangeMinimum到rt.TimeRangeMaximum),自然查不到聚合数据。
2. 自动时间戳结果随机
client.AddAutoTs使用毫秒级时间戳,单位正确,但结果不符合预期的原因有两点:
- 固定窗口对齐:RedisTimeSeries的聚合窗口是从epoch开始的固定间隔(比如5秒窗口的起始时间为0、5000、10000...毫秒),并非从第一个数据点或规则创建时间开始。你的数据点会分散在不同窗口中,导致聚合结果的窗口范围和预期的"每5秒一组"不匹配。
- 惰性聚合机制:RedisTimeSeries不会实时生成聚合数据,只有当查询目标序列
td,或有新数据进入下一个窗口时,才会计算上一个窗口的聚合结果。如果最后一个数据点所在的窗口没有后续数据,该窗口的聚合结果就不会生成,导致查询不到9。
解决方案
1. 修正时间戳单位
将秒级时间戳改为毫秒级,使用timestamp.UnixMilli()替代timestamp.Unix(),确保时间戳和RedisTimeSeries的默认单位一致。
2. 触发最后窗口的聚合
为了让最后一个窗口的聚合结果生成,可以在循环结束后添加一个时间戳落在下一个窗口的数据,强制触发上一个窗口的计算。
3. 修改后的完整代码
func Red() { keyname := "t" client := rt.NewClient("127.0.0.1:6379", "nohelp", nil) // 清理旧键避免干扰 client.DeleteKey(keyname) client.DeleteKey("td") // 显式指定时间戳单位为毫秒,和Add参数保持一致 opts := rt.DefaultCreateOptions opts.TimestampUnit = rt.Milliseconds client.CreateKeyWithOptions(keyname, opts) client.CreateKeyWithOptions("td", opts) // 创建5秒窗口的最大值聚合规则 client.CreateRule(keyname, rt.MaxAggregation, 5000, "td") for i := 0; i < 10; i++ { timestamp := time.Now() fmt.Println(timestamp.UnixMilli(), timestamp.String()) value := float64(i) // 使用毫秒级时间戳添加数据 client.Add(keyname, timestamp.UnixMilli(), value) time.Sleep(time.Second) } // 添加一个落在下一个窗口的数据,触发最后一个窗口的聚合 lastTimestamp := time.Now().Add(6 * time.Second) client.Add(keyname, lastTimestamp.UnixMilli(), 0) // 查询并打印结果 fmt.Println("原始序列:", client.RangeWithOptions(keyname, rt.TimeRangeMinimum, rt.TimeRangeMaximum, rt.DefaultRangeOptions)) fmt.Println("聚合序列:", client.RangeWithOptions("td", rt.TimeRangeMinimum, rt.TimeRangeMaximum, rt.DefaultRangeOptions)) }
4. 预期结果
修改后,td序列会生成两个符合预期的聚合结果:
- 第一个窗口包含值0-4,最大值为4;
- 第二个窗口包含值5-9,最大值为9;
(额外添加的触发数据会生成一个包含0的窗口,可通过指定查询时间范围排除)
内容的提问来源于stack exchange,提问作者Guna Nikunj
相关产品推荐
相关产品推荐

