You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:48:01