redis

缓存穿透/缓存击穿/缓存雪崩

缓存穿透:

查询的某个key,在redis中没有,mysql也没有,这个key也不会被更新到redis中

这样的数据查多少次都没有,如果还存在很多还反复查询,一样也会给mysql带来很大压力

改进业务和加强监控报警是补救措施

而靠谱的方案:

  1. 参数校验:对非法参数直接拦截
  2. 接口限流:对同一IP和同一key的频繁查询做限流
  3. 如果发现这个key在两者都不存在,仍然写入redis,value设成一个非法值
  4. 引入布隆过隆器,每次查询redis/mysql之前先判定一下key是否可能不存在,如果是布隆过滤器说"不存在"则直接返回,若存在再查

缓存击穿:

相当于是缓存雪崩的特殊情况,针对的是"热点key"

解决方案:

  1. 基于统计的方式发现热点key并设置永不过期
  2. 互斥锁:只允许一个线程去查数据库重建缓存
  3. 补充方案:服务降级,类似于省电模式,在特定情况下适当的关闭一些不重要的功能,只保留核心功能
    访问数据库的时候使用分布式锁,限制同时请求数据库的并发量

缓存雪崩:

由于短时间内,redis上大规模的key失效,导致缓存命中率陡然下降,mysql压力迅速上升

可能原因:

1.redis直接挂了 解决方案:加强监控报警,加强redis集群可用性的保证

2.redis正常,但是之前短时间设置了很多key的过期时间是相同的

解决方案:

  1. 不给key设置过期时间或者设置时添加随机的因子(避免同一时刻过期)
  2. 多级缓存:本地缓存(Caffeine)+Redis缓存,本地缓存扛第一波
  3. 限流降级:使用Sentinel对数据库访问做限流
  4. 数据预热:提前把热点数据加载到Redis,避免启动后瞬间打满

在redis的集群中,如何根据键定位到对应的节点

redis集群把数据分布到16384个哈希槽上,每个key通过CRC16算法算出哈希值,在对16384取模得到槽位编号

为啥是16384个槽
  1. 心跳包大小 :集群节点之间通过心跳包同步槽信息,16384 个槽的位图正好是 2KB(16384/8 = 2048 字节)
  2. 集群规模:集群不太可能超过1000个节点,这个大小刚好
  3. CRC16 算法:CRC16 生成 16 位结果,范围 0-65535,取 16384 是 2^14,适合位图存储
Redis集群中节点怎样通信

通过Gossip协议在集群中传播节点状态,故障信息,槽位分配等,使得集群的拓扑信息能快速的传播至所有节点,实现了去中心化,

什么是MOVED重定向?它和ASK重定向有什么区别?

MOVED重定向表示这个槽永久性地属于另一个节点,客户端应更新本地槽映射并以后直接目标节点。而ASK重定向表示这个槽正在迁移中,数据可能在源节点也可能在目标节点,这次去目标节点问一下,但不用更新本地缓存

MOVED错误:比如客户端要找的key不在这个节点上,这个节点就会返回一个MOVED错误,里面带着相应节点的ip和端口

ASK的ASKING请求:因为迁移还没完成,目标节点理论上还没正式接管这个槽,相当于时一个临时授权,如果不发的话,目标节点可能会拒绝管理

跳跃表的结构以及与红黑树相比它的优势在哪里?

跳跃表时一种基于有序链表的多层索引结构,每一层都是下一层的的快速通道,查找时从最高层开始,逐步缩小范围,最后在底层找到目标,平均时间复杂符o(log n)。

redis作者解释过选择跳跃表而不是红黑树基于以下几点

  1. redis的设计哲学是keep it Simple,Stupid。跳跃表的实现比红黑树简单的多,代码只要几百行,而红黑树需要几千行且容易出错
  2. 跳跃表的结构更容易可视化和调试,出现问题,则打印每一层链表就能定位
  3. redis的Sorted Set的需求是范围查询直接利用底层链表,排名计算则利用跨度字段(它记录当前层到下一个节点的距离,用于计算元素的排名),跳跃表完美符合。红黑树实现复杂且性能也差
  4. 跳跃表虽然比红黑树更占内存,但对内存数据库来说,这点开销完全能接收,换来的是代码的简洁性和可维护性

为什么要用redis,用场景举例,优化了什么

  1. redis数据存储在内存中读写速度时百万级QPS,而mysql是基于磁盘,读写是千级QPS,内存比磁盘快1000倍以上
  2. redis核心处理是单线程的,避免了多线程的竞争和锁开销,加上epoll这样的IO复用机制,使得在高并发下非常稳定
    场景1:电商页面
    未用redis,每次请求都去查mysql,mysql在高并发下出现磁盘i/o延迟,索引丢失和单表数据量过大导致查询性能骤降,使用后并发骤降
    场景2:分布式锁
    秒杀系统,只有很少的库存然后几十万人同时抢,多个请求读到还剩一件,容易出现超卖,而mysql本身的锁性能很差,使用setnx实现分布式锁实现了高并发下的数据一致性

既然redis是单线程那它为什么还能这么快?

redis的核心瓶颈从来不是cpu而是网络I0,之所以单线程就能支撑如此高的并发是因为它IO多路复用,让单个线程同时监听多个客户端,当有事情线程采取处理,没有任务就阻塞或处理其他任务。单线程就是解决网络IO的高效方法之一,因为它避免了多线程下的复杂加锁机制

那为什么这几年单线程设计被打破了

因为随着硬件的发展,网络IO的延迟被降低了,然后redis存的数据越来越复杂了,它引入的多线程并不是将读写命令变成多线程,而是只用于处理网络IO的读写,核心的执行命令依然是单线程

瓶颈是什么

搞垮redis的不是QPS,而是慢查询:特别复杂的命令,这样会使单线程的redis陷入长时间的阻塞

redis混合持久化断电时一般丢多少数据

混合持久化是AOF重写的格式优化,数据丢失量由AOF的appendfsync的策略决定,

与RDB无关,always:最多丢1条,everysec:最多丢1秒,no:丢失不可控,约30秒

混合持久化只加快恢复速度,不提升数据安全性

如何平衡redis的缓存命中率和安全性

一、核心矛盾
目标 做法 副作用
提高命中率 延长过期时间、不淘汰热点 key 数据可能变脏(不一致)
提高安全性 缩短过期时间、频繁更新缓存 命中率下降,数据库压力增大
二、具体平衡策略
1. 过期时间策略
  • 热点 key 设置永不过期 + 后台异步更新
    • 命中率最高,但数据可能短暂不一致
    • 适合对一致性要求不高的场景(如商品详情页、排行榜)
  • 非热点 key 设置合理的 TTL + 随机偏移
    • 避免大量 key 同时过期导致雪崩
    • 命中率适中,安全性较好
2. 淘汰策略的选择
策略 命中率 安全性 适用场景
allkeys-lru 通用场景,淘汰最久未使用的
volatile-lru 只淘汰设置了过期时间的 key
allkeys-lfu 访问频率差异大的场景
noeviction 不允许丢数据的场景(如分布式锁)

平衡做法 :使用 volatile-lru,只对设置了过期时间的 key 做淘汰,永不过期的热点 key 不受影响。

3. 缓存更新模式
  • Cache Aside Pattern(旁路缓存)

    • 读:先查缓存,miss 则查 DB 并回写
    • 写:先更新 DB,再删除缓存(延迟双删可提高安全性)
    • 命中率中等,安全性较高
  • Read Through / Write Through

    • 缓存层代理 DB 操作,保证一致性
    • 命中率高,但实现复杂
  • 异步刷新(推荐)

    • 热点 key 永不过期,后台定时刷新
    • 命中率最高,安全性通过版本号最后修改时间来保证
4. 多级缓存架构
复制代码
客户端 → 本地缓存(Caffeine) → Redis 缓存 → 数据库
  • 本地缓存:命中率极高(毫秒级),但容量小,适合热点数据
  • Redis 缓存:命中率中等,容量大,适合温数据
  • 数据库:兜底

平衡点:本地缓存设置较短的过期时间(秒级),Redis 设置较长的过期时间(分钟级),既保证命中率,又能在数据更新后快速失效。

5. 数据一致性保证
场景 做法 命中率影响 安全性
读多写少 缓存永不过期 + 后台刷新 中(短暂不一致)
读写均衡 Cache Aside + 延迟双删
写多读少 直接查 DB,不缓存 最高
四、总结

平衡的核心是按数据分级

数据等级 命中率要求 安全性要求 策略
L1(热点) 极高 永不过期 + 异步刷新
L2(温数据) 较长 TTL + 随机偏移
L3(冷数据) 较短 TTL + Cache Aside
L4(敏感数据) 极高 不缓存或短 TTL + 强一致性

redis的锁什么时候会失效

  • 持有锁的线程执行时间超过过期时间:合理预估+看门狗机制
  • 主从切换导致锁丢失:使用Redlock或者Zookeeper
  • 解锁时误删其他线程的锁:用Lua脚本保证原子性,value使用唯一标识
  • 网络异常导致锁无法续约:使用fencing token
    (每次加锁生成递增的token,写入数据库时校验token是否有效)

什么是脑裂问题?

脑裂:是指在分布式系统中,由于网络分区或节点故障,集群被分裂成两个或多个独立的子集群,每个子集群都认为自己是"主的",各自独立运行,导致数据不一致。

常见三个场景:

  • 网络分区:主节点与哨兵/集群断开,但与客户端连接。导致客户端继续写旧的主节点,数据丢失
  • 主节点假死:主节点负载过高,响应慢,被误判为下线。同上
  • 集群中的部分节点故障:形成两个独立的集群。数据分片不一样
    解决方案:
  1. 哨兵模式的配置参数;
    最少需要多少个哨兵节点同意才能进行故障转移(quorum机制,多数派决策)
    主节点和哨兵断连多少时间才会判定为主观下线
    故障转移超时时间
    Raft算法:发生故障转移的时候,通过Raft算法率先获得半数投票的哨兵称为Leader
  2. Redis集群的配置参数
    集群节点间通信超时时间
    当集群中任何节点不可用时,整个服务停止服务(默认yes,改成no)
  3. 最关键的参数:min-replicas-to-write和min-replicas-max-lag
    min-replicas-to-write 1 min-replicas-max-lag 10
    主节点至少要有几个从节点连接,且延迟不超过几秒,才接受写请求 作用:当发生脑裂时,旧主节点与从节点断开连接,to-write会拒绝写请求,而避免数据丢失
    代价:牺牲了可用性,换来了数据一致性