目录
[5、引入watch dog](#5、引入watch dog)
6、引入Redlock算法(解决集群中因主从切换导致锁丢失的问题)
一、缓存
1.1、缓存的本质
缓存是将高频访问的热点数据存储在 "更快的存储介质" 中(如内存),减少对底层存储(如 MySQL)的访问压力,核心遵循 "二八定律"(20% 热点数据支撑 80% 访问)。
1.2、使用Redis作为缓存

客户端访问业务服务器发起查询请求
业务服务器先查询Redis,如果数据在Redis中已经存在就直接返回不必访问MySQL,如果不存在在查询MySQL
优势:
- 纯内存存储,访问速度比硬盘数据库快 3-4 个数量级;
- 支持键过期、淘汰策略,适配缓存场景;
- 单线程模型 + IO 多路复用,高并发支撑能力强;
- 支持多种数据结构,适配不同缓存需求(如字符串存用户信息、哈希存商品属性)。
1.3、缓存更新策略
缓存的核心问题是"如何保证缓存和数据库数据一致",常见的更新策略如下
1、定时生成
每隔一定的周期(一天/一周/一个月),对于访问的数据频次进行统计。挑选出访问频次最高的前N%的数据
适用场景:非实时场景
2、实时生成
先给缓存设定容量,接下来查询的时候,如果在Redis中查到了,就直接返回;如果Redis中不存在,就从数据库中查,把查到的结果同时也写入Redis
如果缓存达到上限,就触发缓存淘汰策略,把相对不热门的数据淘汰掉
3、淘汰策略
(1)通用淘汰策略
FIFO (First In First Out)先进先出: 把缓存中存在时间最久的(也就是先来的数据)淘汰掉.
LRU(Least Recently Used)淘汰最久未使用的: 记录每个key的最近访问时间.把最近访问时间最老的key淘汰掉.
LFU(Least Frequently Used)淘汰访问次数最少的: 记录每个key最近一段时间的访问次数.把访问次数最少的淘汰掉.
Random随机淘汰**:**从所有的key中抽取幸运儿被随机淘汰掉.
(2)Redis内置淘汰策略
- **volatile-lru:**淘汰设置过期时间的 key 中最近未使用的;
- **allkeys-lru:**淘汰所有 key 中最近未使用的(最常用);
- **volatile-lfu:**淘汰设置过期时间的 key 中访问次数最少的;
- **allkeys-lfu:**淘汰所有 key 中访问次数最少的;
- **noeviction:**默认策略,缓存满时拒绝新写入(推荐生产环境,提前扩容更安全)。
1.4、缓存四大核心问题
1、缓存预热
**定义:**Redis 启动或大量 key 失效后,提前将热点数据写入缓存,避免缓存空窗期导致数据库压力骤增。
实现方式:
离线统计热点数据(如通过日志分析),启动时批量加载;
服务启动后,模拟用户请求预热高频 key。
2、缓存穿透
**定义:**访问的 key 在 Redis 和数据库中均不存在,导致请求直接穿透到数据库,引发高负载。
原因:
(1)业务设计不合理,如缺少必要的参数检验环节,导致非法的key被进行查询
(2)开发/运维误操作,不小心把部分数据从数据库上误删了
(3)黑客恶意攻击
解决方案:
(1)参数合法性校验(如手机号格式校验);
(2)空值缓存:数据库不存在的 key,在 Redis 中存储空值(如 ""),设置短期过期时间;
(3)布隆过滤器:先使用布隆过滤器判定key是否存在,在真正查询;(布隆过滤器结合了hash+bitmap的思想,用较少空间判定某个元素是否存在)
3、缓存雪崩
**定义:**短期内大量key在缓存上失效,导致数据库压力骤增,甚至直接宕机
原因:
(1)Redis故障
(2)批量key设置相同过期时间
解决方案:
(1)过期时间设置为随机值,分散过期时间;
(2)部署 Redis 高可用集群(主从 + 哨兵 / 集群),避免单点故障;
4、缓存击穿
**定义:**热点 key 突然过期,大量并发请求直接访问数据库(缓存雪崩的 "单点案例")
解决方案:
(1)热点 key 永不过期;
(2)分布式锁限流:数据库查询加锁,同一时间仅允许一个请求查询数据库并回写缓存;
(3)延长过期时间:热点 key 过期前,通过后台任务自动续约。
二、分布式锁
2.1、基本概念
**1、定义:**分布式系统中也会涉及到多个节点访问同一个公共资源的情况,此时需要锁来做互斥控制,解决"跨进程、跨主机"的并发问题
本质是使用一个公共服务器来记录加锁的状态
这个公共服务器可以是Redis,也可以是其他组件
**2、核心要求:**互斥性、安全性(避免死锁)、可用性(集群部署)、原子性(加锁 / 解锁操作原子)。
**3、Redis实现优势:**高性能、支持过期时间、原子命令(SETNX、Lua 脚本)。
2.2、分布式锁的逐步实现
1、基础实现:SETNX命令
**核心逻辑:**SET key 服务器ID NX(key 不存在时设置成功,视为加锁),解锁时DEL key。
**问题:**服务器宕机会导致解锁操作不执行,其他服务器始终无法获取到锁
2、添加过期时间
核心逻辑: SET key 服务器ID NX EX 10(原子操作,同时设置过期时间10s)
**解决:**可以解决死锁问题(过期自动释放)
注意:此处过期时间只能使用一个命令的方式设置
如果分开多个操作,比如SETNX之后再来一个EXPIRE来设置过期时间,由于Redis多个指令之间不关联,即使使用事务也不能保证两个操作都成功,因此就可能出现SETNX成功,EXPIRE失败的情况,这种情况就和第一种实现方式一样,会导致死锁问题
**问题:**其他服务器可能误删锁(如服务器 1 加锁后超时未完成,锁自动过期,服务器 2 加锁,服务器 1 完成后删除服务器 2 的锁)。
3、引入校验ID
**核心逻辑:**加锁时存储 "服务器唯一 ID",解锁前先校验 ID 是否匹配,如果是当初加锁的服务器才能真正删除,如果不是就不能删除
**问题:**校验和删除非原子操作(可能校验时锁未过期,删除时已过期,仍可能误删)。
4、引入Lua
**核心逻辑:**解锁时执行 Lua 脚本,原子完成 "校验 + 删除":
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
**解决:**避免校验和删除的竞态条件,保证解锁原子性
**问题:**还是可能会存在任务未执行完,key就过期的情况
5、引入watch dog
**核心逻辑:**加锁后启动独立线程,每隔 3 秒校验任务是否完成,如果已完成就使用Lua脚本释放锁,未完成则将锁过期时间续约至 10 秒
**解决:**避免锁过期时间过短,任务未完成导致锁提前释放
6、引入Redlock算法(解决集群中因主从切换导致锁丢失的问题)
**核心场景:**Redis 集群中,主节点加锁后宕机,从节点未同步锁信息,导致重复加锁。
核心思想:"少数服从多数",避免单点集群的一致性问题。
算法逻辑:
部署多组Redis节点(每组节点要包含1个主节点和若干从节点)
按预设顺序,向每组集群的 主节点 发送原子加锁命令
单个主节点加锁设超时阈值(如 50ms),超时未成功则跳过这个节点,给下一个节点加锁
满足两个条件则加锁成功:
加锁成功的主节点数 超总组数的一半(5 组需≥3 组,3 组需≥2 组);
整个加锁过程总耗时 ≤ 预设总超时时间。
