时间窗口设计
→ 返回 高并发
排行榜、限流、监控、风控都依赖时间窗口:在什么时间范围内聚合、何时切榜、何时落库。设计错误会导致边界双倍计数、榜错乱、Redis key 爆炸。
三种基础窗口
| 类型 | 定义 | 示例 |
|---|---|---|
| 滚动窗口(Tumbling) | 固定长度、首尾相接不重叠 | 每天 0:00~24:00 日榜 |
| 滑动窗口(Sliding) | 任意时刻看「过去 N 分钟」 | 最近 1 小时热搜 |
| 会话窗口(Session) | 间隔超时切分 | 用户连续点击会话 |
日历日榜(滚动):
|---- 6/26 ----|---- 6/27 ----|---- 6/28 ----|
滑动 1h(在 14:30 看 = [13:30, 14:30]):
|←── 60 min ──→|
14:30
微信步数:日历日窗口
| 决策 | 建议 |
|---|---|
| 切榜时刻 | 用户本地时区 0 点(或产品统一东八区) |
| Key | step:20240627:{userId} |
| 过期 | EXPIRE 2~7 天,自动清理 |
| 0 点瞬间 | 新 key 命名空间切换,旧榜只读 |
# 新日 key,避免与昨日混写
SET step:20240627:10001 8520 EX 604800不要用 TTL=86400 滑动代替自然日(会在首次写入时间滚动,榜语义错误)。
Redis 时间分桶
固定日历桶(推荐日榜)
rank:friend:{uid}:20240627 → ZSet
step:20240627:{uid} → String
滑动窗口限流(按分钟)
rate:{uid}:{yyyyMMddHHmm} INCR + EXPIRE 60
或 Redis Cell / 网关内置滑动窗口。
滑动窗口计数(热搜衰减)
多桶叠加近似滑动:
# 最近 5 个 1 分钟桶
hot:word:202406271430
hot:word:202406271431
...
score = sum(bucket[i] * weight[i])
更精确可用 Redis Stream 按时间 trim 或 Flink 事件时间窗口。
延迟窗口与结算
| 场景 | 设计 |
|---|---|
| 日榜定稿 | 0:05 停止接受昨日上报,或接受但记入今日 |
| 对账窗口 | T+1 凌晨批量校验 Redis vs DB |
| watermark | 流处理中「允许迟到 5 分钟」的事件仍计入昨日 |
23:58 上报 → 计入当日
00:02 上报昨日补传 → 产品规则:拒绝 / 记入当日 / 记入昨日(需明确)
流式 vs 批处理
| 路径 | 技术 | 适用 |
|---|---|---|
| 在线 | Redis + MQ 消费者 | 实时榜、实时步数 |
| 近线 | Flink / Spark Streaming | 复杂窗口、多维度聚合 |
| 离线 | 数仓按 dt 分区 | 审计、报表 |
Flink 窗口类型:
TumblingEventTimeWindowsSlidingEventTimeWindowsEventTimeSessionWindows
事件时间需处理 乱序(watermark)。
窗口与存储成本
| 风险 | 缓解 |
|---|---|
| key 按分钟爆炸 | 限流桶只保留最近 N 个;定期 SCAN 清理 |
| 跨时区 | 统一用 UTC 存储,展示层转换 |
| 夏令时 | 日历日规则单独测试 |