Redis 是内存数据结构服务器,可以加速读取并协调有界操作。生产设计必须定义事实来源、一致性窗口、容量策略、故障行为以及每个键的所有权。持久化和复制改善恢复选项,但不会赋予缓存源数据库所具有的事务不变量。
Cache-Aside 和一致性窗口 #
Cache-Aside 将读写策略留给应用程序:
应用程序读取 Redis,从数据库加载未命中结果,使用 TTL 存储结果,然后返回结果。源数据写入必须使受影响的键失效或刷新。由于数据库提交和缓存变更不是一个原子操作,读取者可能在这两个事件之间观察到过时数据。应定义可接受窗口;当缓存失效必须经受进程故障时,使用事务性 outbox 或变更数据流。
当对不存在标识符的重复请求始终到达数据库时,会发生缓存穿透。在确认权威不存在后存储短期负哨兵,但绝不能让哨兵的生命周期超过创建操作必须可见的时间间隔。Bloom 过滤器可以在查找前拒绝确定不存在的值;误报仍会到达后端系统。
惊群和刷新 #
热门键过期时,并发未命中可能重复执行昂贵的加载。缓解措施包括:
- 添加随机 TTL 抖动,使相关键不会同时过期;
- 允许一个有界刷新者重建键,而其他请求使用过时数据;
- 合并进程内对同一键的请求;
- 在热门键硬过期前刷新它们。
刷新锁需要租约和故障路径。它不能将缓存未命中变成无限等待;由于 Redis 可能不可用,后端服务仍需要过载控制。
过期、驱逐和内存压力 #
以原子方式设置值和过期时间:
SET session:42 '{"user":42}' EX 1800Redis 在访问时被动删除过期键,并通过采样带 TTL 的键主动删除。因此,过期定义逻辑可见性,而不是精确的内存回收时刻。TTL 选择应考虑新鲜度、重新计算成本、对象大小和同步过期风险。
maxmemory 限制数据可用内存,maxmemory-policy 选择对压力的响应。策略包括 noeviction、所有键或易失键的 LRU/LFU 近似、随机驱逐以及基于易失 TTL 的驱逐。易失策略只考虑带过期时间的键;当没有符合条件的键时,它们可能表现得像 noeviction。应监控已用内存、碎片、驱逐、过期、被拒绝写入、延迟和缓存命中率,而不要假设配置的 TTL 保证键常驻。
分布式锁和栅栏 #
使用唯一所有权令牌和有界租约获取单实例 Redis 锁:
SET lock:invoice:42 4d1c9f... NX PX 30000仅当令牌仍然匹配时才释放,并使用一个原子脚本:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0租约限制了放弃,但不能证明独占访问。进程可能暂停超过 TTL、失去锁、恢复运行,然后与新持有者并发写入。当下游资源可能被破坏时,应发出单调递增的栅栏令牌,并要求该资源拒绝早于已观察到的最大令牌的令牌。
复制是异步的,时钟和暂停会影响租约推理,网络分区可能隐藏所有权变化。当数据库约束、条件写入或共识协调器已经保护该不变量时,应优先使用它们。Redis 锁应保护已记录故障模型下有界且可重试的工作。
固定窗口速率限制 #
固定窗口为每个主体和时间桶存储一个计数器。原子地递增、设置初始过期时间并作出决定:
local count = redis.call("INCR", KEYS[1])
if count == 1 then
redis.call("EXPIRE", KEYS[1], tonumber(ARGV[1]))
end
if count > tonumber(ARGV[2]) then
return {0, count}
end
return {1, count}固定窗口需要恒定状态,但一个桶结束和下一个桶开始时的流量可能产生接近名义限制两倍的突发。应一致地推导桶标识;客户端时钟可能不一致,因此在边界精度重要时,应在脚本中使用服务器时间,或将一个主体路由到一致的时钟域。
滑动窗口和桶 #
有序集合滑动日志为每个请求存储一个唯一成员,并将时间戳作为分数。脚本会删除窗口之前的条目,统计剩余条目,有条件地添加请求并设置 TTL。该算法执行滚动计数,但内存消耗与接受的事件数成正比。即使多个请求共享时间戳,成员也必须保持唯一。
令牌桶存储令牌余额和上次补充时间。每个请求根据经过的时间补充令牌,直到达到容量,然后在令牌可用时消耗一个。它执行长期速率,同时允许配置的突发。漏桶则以固定速率排出排队工作,使输出平滑,但需要有界排队和明确的拒绝策略。
Lua 脚本和 Redis Functions 使每个决策在一个 Redis 节点上具有原子性。应保持脚本具有确定性且有界;长脚本会阻塞其他命令执行。仅当这些值准确反映所选算法时,才返回限制、剩余容量以及重置或重试时间。
集群放置、重试和可观测性 #
Redis Cluster 要求一个事务、脚本或函数触及的每个键属于同一哈希槽。使用 rate:{account-42}:write 这样的哈希标签将相关键放在一起,而不会将不相关账户强制放入一个槽。一个热门主体仍可能使其所在槽过载。
超时不能证明修改命令失败。盲目重试 INCR、XADD 或速率限制脚本可能重复产生效果,因此在需要精确一次计账时附加操作 ID,或让脚本检测重复请求。限制重试次数并公开 Redis 错误,不要静默地将严格限制替换为无限访问;安全敏感系统必须有意选择故障开放或故障关闭。
记录脚本延迟、错误、拒绝和允许计数、热门键、槽分布、副本延迟、故障转移、内存压力和时钟偏移。这些信号可以区分策略行为、Redis 饱和以及拓扑变化。
评论
在 GitHub 上阅读或评论(发表评论需要登录)