redis时间环结构-时序特征

一.应用场景:

需要频繁获取用户的聚合信息的场景,比如风控场景实时获取用户最近5s访问次数,最近5分钟访问次数,最近5小时访问次数,最近5天访问次数和最近半年访问次数。

二.思考

1.采用大数据flink进行实时预计算后存储,暂且不论。
2.采用redis,基于hash实现。
我这里因环境限制所以讲一下redis基于hash实现。
在写入的时根据滚动窗口实时计算出结果进行存储。
为了减少计算数据量我们按照常用的获取方式将时序的数据分为
60秒+60分钟+24小时+365天
那我们一共会在redishash中存储数据的key有509个。
先将需要更新的key变少。

我们再来考虑将更新次数变少。
笨办法是每次写入就更新509个key,但是lua中每次循环比较耗费资源,所以此处我们可以采用一个最小时间窗口的缓存key。
同一个时间窗口写缓存key,下一个时间窗口到达后再更新509个key,然后将本次的值写入到缓存key中进行覆盖。如此则减少了更新次数。

而我们再读取到时候,读取最近5分钟到访问次数,根据当前时间获取跨度定位到读取分钟key,通过当前时间戳/6000%60 + 60 = 读取下标。
读取hash 该下标的聚合值 + 缓存值 = 返回最终实时结果 。

下面是lua实现,但是目前我只实现了单一时间单位的key,多时间单位的还待实现中。且附上了性能压测结果:

三.实现方式

按时间段进行数据分片:划分为若干个时间槽,每个时间槽代表一段时间段。这样的划分能够为维度对数据进行切片存储,并在后续查询时能够轻松地按时间范围进行数据检索。

时间序列写入逻辑:在当前时间大于上次写入时间的情况下,根据时间差和时间槽长度,我们会选择性地将数据写入到不同的时间槽中,以实现高效的时间序列数据存储。

数据过期处理:如果数据超过一个完整周期,则重置所有槽位,防止数据累积过多导致性能问题。

支持时间倒退:处理当前时间小于上次写入时间的情况,以保证数据的一致性。

缓存当前时间的数据:使用一个缓存键来存储当前时间的数据,减少频繁的Redis写操作。

四.具体实现

1.说明
初始化槽位:

当没有上次写入时间时,初始化所有槽位为0,并记录当前时间和缓存值。

顺序写入:

当前时间大于上次写入时间时,计算当前槽位。

如果超过一个完整周期,重置所有槽位。

否则,根据当前时间和上次写入时间,更新有效槽位和无效槽位。

将当前时间的数据写入缓存键,并更新上次写入时间。

时间倒退处理:

如果当前时间小于上次写入时间,但在有效时间范围内,更新有效槽位。

五.代码实现

  • 按时间段进行数据分片:划分为若干个时间槽,每个时间槽代表一段时间段。这样的划分能够为维度对数据进行切片存储,并在后续查询时能够轻松地按时间范围进行数据检索。
  • 时间序列写入逻辑:在当前时间大于上次写入时间的情况下,根据时间差和时间槽长度,我们会选择性地将数据写入到不同的时间槽中,以实现高效的时间序列数据存储。
    • 数据过期处理:如果数据超过一个完整周期,则重置所有槽位,防止数据累积过多导致性能问题。
    • 支持时间倒退:处理当前时间小于上次写入时间的情况,以保证数据的一致性。
    • 缓存当前时间的数据:使用一个缓存键来存储当前时间的数据,减少频繁的Redis写操作。

1.说明

  • 初始化槽位
    • 当没有上次写入时间时,初始化所有槽位为0,并记录当前时间和缓存值。
  • 顺序写入
    • 当前时间大于上次写入时间时,计算当前槽位。
    • 如果超过一个完整周期,重置所有槽位。
    • 否则,根据当前时间和上次写入时间,更新有效槽位和无效槽位。
    • 将当前时间的数据写入缓存键,并更新上次写入时间。
  • 时间倒退处理
    • 如果当前时间小于上次写入时间,但在有效时间范围内,更新有效槽位。

2.1核心代码

写:
-- 该Lua脚本用于将数据写入Redis的指定槽位,并根据当前时间和上次写入时间来更新槽位。槽位的更新逻辑包括顺序写入、时间倒退的处理,以及槽位的重置 -- 获取输入参数 local key = KEYS[1] local curTime = tonumber(ARGV[1]) local slotCount = tonumber(ARGV[2]) local slotTsLength = tonumber(ARGV[3]) local value = tonumber(ARGV[4]) --- 更新槽位 local function updateSlots(key, currentSlot, slotCount, validSlotCount, value, reset)    for i = 0, slotCount - 1 do        local ix = (currentSlot + i) % slotCount        if i < validSlotCount then            -- 在有效槽位范围内递增            redis.call("hincrby", key, tostring(ix), value)        elseif reset then            -- 在无效槽位范围内重置            redis.call("hset", key, tostring(ix), "0")        end    end end --- 特殊键,用于记录上次写入时间 local lastWriterTimeKey = "-1" --- 当前窗口缓存键 local cacheKey = "-2" -- 获取上次写入时间 local lastWriterTime = tonumber(redis.call("hget", key, lastWriterTimeKey)) -- 初始化槽位 local firstSlot = 0

相关推荐

  1. redis时间结构-时序特征

    2024-07-19 18:16:01       22 阅读
  2. redis获取过期时间

    2024-07-19 18:16:01       62 阅读
  3. 数据结构——时间复杂度

    2024-07-19 18:16:01       49 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 18:16:01       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 18:16:01       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 18:16:01       62 阅读
  4. Python语言-面向对象

    2024-07-19 18:16:01       72 阅读

热门阅读

  1. 欢迎来到 Mint Expedition:Web3 和 NFT 的新时代开始

    2024-07-19 18:16:01       25 阅读
  2. MySQL运算符

    2024-07-19 18:16:01       22 阅读
  3. 【gradle中如何不使用插件将依赖打进jar包】

    2024-07-19 18:16:01       20 阅读
  4. web前端面向对象面试25题

    2024-07-19 18:16:01       20 阅读
  5. 【编程语言】C++和C的异同点

    2024-07-19 18:16:01       21 阅读
  6. 【React Hooks原理 - useSyncExternalStore】

    2024-07-19 18:16:01       16 阅读
  7. Ubuntu22.04:安装并配置nfs

    2024-07-19 18:16:01       21 阅读
  8. udp和tcp区别

    2024-07-19 18:16:01       23 阅读
  9. Leetcode 383. 赎金信

    2024-07-19 18:16:01       20 阅读
  10. 接口加密方案

    2024-07-19 18:16:01       19 阅读