常见缓存策略精析 更新中 

缓存三兄弟 缓存穿透 是什么:大量请求查询数据库不存在的数据。缓存没有,持续击打数据库 防范方法: 缓存空值 数据库查不到也往缓存放一个空标记(TTL短,2分钟,该TTL过期了才能进行再次数据库查询) 下一次同样的id来了直接被挡住 优点:有效拦截大量穿透请求 缺点: 恶意攻击造成大量不同的不存在的key,缓存堆满无效数据,浪费内存 延迟了数据一致性,新数据必须等缓存过期 布隆过滤器(推荐) 启动时把所有合法id放入布隆过滤器; 布隆过滤器不给进就真的没有; 优点: 没有高内存占用风险 没有数据一致性风险 缺点: 有极小误判率 增删要维护。数据更新时,要同步更新布隆过滤器 接口层限流和校验 API网关或Controller层做拦截 参数校验、限流降级 互斥锁重建 互斥锁主要解决缓存击穿(热点key过期),但是在缓存空对象场景下,如果有大量并发请求一个不存在的key,可以使用锁 每次访问让一个线程去查DB,其他线程等待 大厂的实践 网关层 校验参数格式,IP限流(挡掉脚本攻击) 过滤器层 布隆过滤器 缓存层 缓存击穿 是什么:某个被疯狂访问的热点key,因为TTL一到,失效的一瞬间海量并发请求同时miss,同时到DB重建同一个key 防范方法: 互斥锁 只让第1个miss的线程去查DB 其他线程等一下再读缓存 高一致性:线程傻傻等待 低一致性:先给旧数据,下一次来查就是新的 逻辑过期 key永不物理过期,把过期时间存在value里面 如果发现逻辑上过期了,异步开个线程重建 当前请求先返回旧值 互斥锁的实现 如何搭建出正确的锁? v1:SETNX key 1 抢锁,DEL key 释放 SETNX=“不存在才设置成功”,天然互斥。但问题:抢到锁的线程崩了、DEL 没执行 → 锁永远不释放 → 死锁。 v2:给锁加过期间 崩了也能自动过期。但关键:必须用一条原子命令 SET key value NX EX 30。 陷阱:别写成 SETNX + 再 EXPIRE 两条——如果刚 SETNX 成功、还没 EXPIRE 就崩了,又变回 v1 的死锁。“抢锁"和"设过期"必须一步做完。 ...

2026-05-05 10:46:44 PM · 1 分钟

Redis LFU内存淘汰策略细究

Redis 的 LFU(Least Frequently Used,最不频繁使用)淘汰策略,是在 LRU 基础上做的“升级版”近似算法。它复用了对象头上同一个 24 位的 lru 字段,通过巧妙的编码和对数计数,用极小的内存代价,实现了对访问频率的近似跟踪。 下面从存储结构、计数器增减、淘汰决策到参数配置,逐一细究。 1. 24 位字段的位划分 每个 Redis 对象都有一个 lru 属性(24 位),在 LFU 模式下,它不再存放秒级时间戳,而是被拆成两段: 高 16 位:最后衰减时间(Last Decay Time,单位:分钟) 低 8 位:对数访问计数器(Logarithmic Counter,范围 0–255) 高 16 位:存储的是 (server.unixtime / 60) & 0xFFFF,即当前分钟时间戳的低 16 位。最大表示约 45 天,足够覆盖淘汰场景,即使回绕,只要间隔不超过 45 天就可以正确计算差值。 低 8 位:是一个 0–255 的频率计数器,但它不是访问次数的直接累加,而是经过对数平滑处理的“近似频率”。 当键被访问时,Redis 会调用 updateLFU(),先根据已流逝的时间衰减计数器,再概率性地递增计数器,最后把新的分钟时间戳和计数器重新编码写回 lru。 2. 计数器递增:对数增长 为了让 8 位计数器(0–255)既能表示低频也能区分高频,同时不让热门键快速打满,Redis 采用概率递增。 递增公式(源码级): double p = 1.0 / ((counter - LFU_INIT_VAL) * server.lfu_log_factor + 1); if ((random() & 0xFFFF) < p * 0xFFFF) counter++; LFU_INIT_VAL 默认为 5,新键的计数器初始值就是 5。 lfu_log_factor 是可配置的对数因子,默认 10。 随着 counter 增大,p 会越来越小,递增越来越难。 举例(factor = 10 时,典型访问次数与计数器值): ...

2024-07-19 01:10:54 PM · 2 分钟