Redis——缓存 Redis可以作为缓存增加访问速度这篇文章是简单说一说Redis作为缓存的一些问题缓存更新策略定期生成服务器会把一段时间内用户访问的条目存放在磁盘文件中然后一个程序会定期一天、一个月等这取决于业务统计文件中的所有条目分别出现的个数并进行排序最终挑出出现次数最多的前几个条目更新到缓存中。实时生成不同于定期生成更新策略会一下子把热点数据保存下来而剔除冷门数据。实时生成更新需要在在一段时间后才能让缓存中的条目几乎都是热点从而达到”动态平衡“如果缓存命中就直接从缓存中取数据如果缓存没命中就将对应数据加载到缓存中直到缓存被用尽。当缓存用尽的时候就是用淘汰策略淘汰掉一些缓存条目并插入新的缓存条目。下面是一些淘汰策略先进先出FIFOFirst In First Out这是最直观的策略思想类似于排队。它会记录每个键值对进入缓存的时间戳淘汰时优先移除在缓存中存活时间最久的数据也就是“先来后走”。但这种策略不考虑数据的实际热度即使一个老数据被频繁访问只要它来得早依然可能被优先淘汰。最近最少使用LRULeast Recently Used相较于 FIFOLRU 更关注数据的“时效性”。它会为每个键维护一个最近访问时间戳淘汰时选择最长时间未被访问的键。如果一个键很久没被读过系统就认为它在短期内大概率也不会被访问从而将其移除。最不经常使用LFULeast Frequently UsedLFU 侧重于统计数据的“访问频率”。它会记录每个键在最近一段时间内被访问的总次数淘汰时优先移除访问次数最少的键。这种策略能很好地抵御“突发冷数据”的冲击确保那些长期被高频访问的热点数据得以保留因为即使一段时间一直访问某个关键词之前的热点数据也不会因为一段时间不被访问而淘汰。随机淘汰Random这是最简单粗暴的策略。Redis 会从当前所有键中随机抽取一个“幸运儿”进行淘汰。虽然毫无“智慧”可言但它对 CPU 的消耗极小在某些访问模式极其均匀的场景下效率反而非常高。redis内部也对标这些淘汰策略提供了相应策略基于 LRU最近最少使用算法volatile-lru仅在设置了过期时间expire的键中使用 LRU 算法进行淘汰。如果过期键集合为空则退化为noeviction行为。allkeys-lru在所有键无论是否设置过期时间中使用 LRU 算法进行淘汰。这是最常用的通用策略之一适合大多数“冷热数据分明”的业务场景。基于 LFU最不经常使用算法volatile-lfu仅在设置了过期时间的键中根据最近一段时间的访问频次进行淘汰。allkeys-lfu在所有键中根据最近一段时间的访问频次进行淘汰。适合需要长期统计热点、且不希望突发流量如秒杀冲走历史高频数据的场景。基于 Random随机算法volatile-random仅在设置了过期时间的键中随机挑选一个进行淘汰。allkeys-random在所有键中随机挑选一个进行淘汰。如果数据访问分布非常均匀无显著热点此策略效率极高。基于 TTL生存时间的近似 FIFOvolatile-ttl仅在设置了过期时间的键中挑选剩余存活时间TTL最短即越早过期的键优先淘汰。本质上可视为一种限定在过期键范围内的先进先出FIFO变种——因为它遵循“谁先到期谁先被清理”的逻辑。默认策略禁止淘汰noeviction这是 Redis 的默认策略。当内存不足时新写入操作如 SET、LPUSH 等会直接返回错误但读操作GET和删除操作依然可以正常进行。该策略保证了数据不会被自动删除但会中断写入服务适合需要严格保证数据不丢失的核心业务但需提前规划好内存扩容。缓存预热和缓存雪崩在使用 Redis 作为 MySQL 前置缓存的典型架构中缓存层的“冷启动”或“集中失效”往往是系统压力的高危时刻。当 Redis 刚刚启动缓存层为空、或是redis突然崩掉了没有及时重启、或是大量 Key 在同一时间段集中过期时Redis 内部相当于一片“真空”地带无法拦截任何读请求。此时原本应由缓存承担的流量会瞬间全部穿透到 MySQL形成缓存雪崩的隐患对数据库造成巨大的瞬时压力甚至直接宕机。为了解决这一问题我们必须在系统面临高流量之前主动将热点数据提前加载进 Redis。这个准备动作被称为缓存预热并提高redis的可用性。预热的核心在于“热点数据”的选择。这部分数据不必追求绝对精准也无需涵盖全量冷数据。我们完全可以依据业务的历史访问日志、实时计数器或者离线统计任务挖掘出近期访问频率最高的 Top N 数据。即使预热的数据集与当前实时流量存在少量偏差只要它能拦截住绝大部分例如 80% 以上的读请求就已经能为 MySQL 撑起有效的保护伞防止数据库因突发流量而宕机缓存穿透在缓存架构中除了缓存雪崩还有一种更为棘手的情况——缓存穿透。缓存穿透指的是客户端请求的 key 在 Redis 缓存中不存在同时在数据库如 MySQL中也不存在。由于缓存层无法命中每次请求都会直接穿透缓存打到数据库上。更严重的是这样的 key 不会被写入缓存因此后续任何对该 key 的重复请求依然会重复绕过缓存持续访问数据库。这会导致数据库瞬间承受大量无效查询压力陡增甚至可能因过载而宕机。产生这种情况的原因通常有三类业务设计不合理系统缺乏必要的参数校验环节。例如本该是合法手机号的查询却传入了乱码或空值导致非法 key 被放行并查询数据库。开发/运维误操作在维护过程中可能不小心物理删除了数据库中的某些数据。恶意攻击或爬虫攻击者利用系统漏洞故意构造大量数据库中根本不存在的随机 key 发起请求意图耗尽服务器资源。针对上述问题业界通常采用以下三种主流方案进行防御严格的合法性校验前置拦截在业务入口处对查询参数的格式、类型、长度进行强校验。例如查询用户信息时必须校验 ID 是否符合规范如正整数或 UUID 格式查询手机号时必须符合正则表达式规则。将绝大多数非法请求在业务层直接拒绝避免它们触及数据库。缓存空对象兜底占位当发现某个 key 在数据库中不存在时仍然将其存入 Redis但将对应的 value 设置为一个空值如空字符串或序列化的空对象并设置一个较短的过期时间例如 5 分钟。这样后续对该 key 的重复访问会直接从缓存中获取空值从而有效保护数据库。这种方法实现简单但需注意若短时间内大量随机 key 进来会在缓存中堆积大量无用空键建议配合过期时间使用。布隆过滤器超前拦截在查询缓存之前引入一个布隆过滤器Bloom Filter。该过滤器结合了哈希算法与位图Bitmap思想能够使用极小的内存空间以极高的效率判断一个 key “绝对不存在”或“可能存在”。我们可以在系统启动时先将数据库中所有存在的 key 同步到布隆过滤器中。当请求到来时先经由布隆过滤器判断若过滤器判定该 key 不存在则直接返回连缓存层都不必查询若判定“可能存在”才放行进入后续的缓存-数据库查询流程。策略选择建议参数校验是基础防线几乎零成本必须执行缓存空对象适用于 key 数量有限、重复访问较高的场景简单有效布隆过滤器则适合应对海量随机 key 的恶意攻击虽然实现稍复杂但它能将绝大多数无效请求扼杀在“门外”防护能力最强。在实际工程中通常会根据业务敏感度组合使用上述两种甚至三种方案以构建纵深防御体系。缓存击穿是缓存雪崩的特殊情况指的是大批量热点数据过期导致一时间数据库因承担大量请求宕机。解决方法基于统计发现热点 Key并设置逻辑永不过期进行必要的服务降级分布式锁限流