面试题答案
一键面试可能面临的挑战
- 不同节点间缓存数据一致性问题:在分布式系统中,多个节点可能同时访问和修改缓存数据,容易出现数据不一致的情况。
- 缓存雪崩:大量缓存数据在同一时间过期,导致大量请求直接穿透缓存到后端数据库,对数据库造成巨大压力,甚至可能使数据库崩溃,从而影响整个分布式系统的正常运行。
- 缓存穿透:查询一个不存在的数据,每次都会穿过缓存直接查询数据库,如果这类请求大量存在,会对数据库造成压力。
- 缓存击穿:一个设置了过期时间的 key,在某个时间点过期,此时大量并发请求同时访问该 key,这些请求会同时穿过缓存查询数据库,对数据库造成瞬时压力。
解决方案和优化思路
- 解决不同节点间缓存数据一致性问题
- 分布式锁:在更新缓存数据时,使用分布式锁保证同一时间只有一个节点能修改数据,避免并发修改导致的数据不一致。例如可以使用 Redis 的 SETNX 命令实现简单的分布式锁。
- 使用发布 - 订阅模式:当一个节点更新了缓存数据,通过 Redis 的发布 - 订阅功能,通知其他节点更新缓存。
- 应对缓存雪崩
- 随机化过期时间:为缓存数据设置一个随机的过期时间,避免大量数据同时过期。比如原本设置过期时间为 1 小时,可以改为 50 分钟到 70 分钟之间的随机值。
- 热点数据不过期:对于一些热点数据,不设置过期时间,定期在后台异步更新缓存数据,确保数据的实时性。
- 构建多级缓存:可以设置一级缓存(如本地缓存)和二级缓存(Redis 缓存),当一级缓存未命中时,再去查询二级缓存,这样可以减轻 Redis 的压力,即使 Redis 出现大量缓存过期,部分请求还能从一级缓存获取数据。
- 应对缓存穿透
- 布隆过滤器:在查询数据前,先通过布隆过滤器判断数据是否存在。布隆过滤器可以快速判断一个元素一定不存在或者可能存在,将不存在的数据直接拦截在缓存之外,避免查询数据库。
- 空值缓存:当查询到数据库中不存在的数据时,也将其缓存起来,设置一个较短的过期时间,这样后续相同的查询请求可以直接从缓存获取空值,而不会穿透到数据库。
- 应对缓存击穿
- 互斥锁:在缓存过期时,只允许一个请求去查询数据库并更新缓存,其他请求等待。可以使用 Redis 的 SETNX 命令实现互斥锁。
- 热点数据永不过期:同缓存雪崩解决方案中的热点数据不过期思路,确保热点数据不会因过期而出现击穿问题。
缓存淘汰策略调整
- LRU(最近最少使用):Redis 默认的淘汰策略之一。当缓存达到最大内存限制时,Redis 会淘汰最长时间没有被访问的数据。适用于存在热点数据的场景,能保证热点数据始终留在缓存中。
- LFU(最不经常使用):淘汰一段时间内使用次数最少的数据。相比于 LRU,LFU 更注重数据的使用频率,在数据访问模式复杂,热点数据不明显的场景下可能更有效。
- Random:随机淘汰数据。适用于对数据访问没有明显规律,且缓存数据对业务影响不大的场景。
- TTL:优先淘汰剩余时间(TTL)最短的数据。适用于有时间敏感性的数据缓存场景。
分布式缓存架构选择
- Redis Cluster:Redis 官方提供的分布式解决方案,采用无中心的架构,每个节点负责一部分数据,具有自动故障转移和数据分片功能,能提高缓存的读写性能和扩展性。
- Codis:由豌豆荚开源的 Redis 分布式解决方案,采用代理的方式,支持动态添加和删除 Redis 节点,易于管理和维护,同时对客户端透明,客户端无需感知分布式架构的细节。
- Twemproxy:一个轻量级的 Redis 代理,通过代理将请求均匀分配到多个 Redis 实例上,实现分布式缓存。它可以对客户端屏蔽后端 Redis 实例的细节,但是不支持动态扩容和缩容。