冬风起头像
关注

Redis 之 【缓存架构、分布式锁】

目录

1. Redis 缓存体系(Cache)

1.1. 为什么需要缓存

1.2. 为什么 Redis 适合做 MySQL 的缓存

1.3. 缓存的更新与淘汰策略

1.4. 缓存四大灾难与防御体系

2. Redis 分布式锁(Distributed Lock)

2.1. 分布式锁的演进史

3. 面试高频接口与命令速查

设计一个高可用的 Redis 缓存和分布式锁架构


1. Redis 缓存体系(Cache)

1.1. 为什么需要缓存

  • 核心思路:将常用数据放到访问速度更快的地方
  • 硬件访问速度层级:CPU 寄存器 > 内存 > 硬盘 > 网络
  • 二八定律:20% 的热点数据,能够应对 80% 的访问场景。缓存空间有限且成本高昂,因此只缓存热点数据,性价比最高

1.2. 为什么 Redis 适合做 MySQL 的缓存

MySQL 性能瓶颈在于:

  1. 磁盘 IO:数据存于硬盘,随机访问慢
  2. 索引失效:导致全表遍历,增加 IO 次数
  3. SQL 解析:解析、校验、优化消耗 CPU
  4. 复杂查询:联合查询产生笛卡尔积,效率极低

高并发下,MySQL 资源极易耗尽导致宕机。应对策略分为开源(集群、分库分表)和节流(引入缓存)。Redis 数据在内存中,且仅支持简单的 K-V 存储,能挡住绝大多数读请求

  • 缓存是为了加快“读操作”的,如果是“写操作”,仍需老老实实写数据库,缓存不能提高写性能

1.3. 缓存的更新与淘汰策略

如何识别热点数据

  • 定期生成:通过日志统计(Hadoop/Spark),挑选高频词。缺点:实时性差,无法应对突发事件(如春节期间的“春晚”)
  • 实时生成:设定容量上限(maxmemory)。读请求先查 Redis,没有则查 DB 并写入 Redis。如果 Redis 满了,触发淘汰策略

Redis 内置淘汰策略对比表

淘汰策略作用域核心算法/逻辑适用场景
volatile-lru设置了过期时间的 key淘汰最近最少使用的缓存同时包含热点数据和持久数据
allkeys-lru所有 key淘汰最近最少使用的纯缓存场景,推荐使用
volatile-lfu设置了过期时间的 key淘汰访问频率最少的(Redis 4.0+)防止偶发冷数据挤掉热点数据
allkeys-lfu所有 key淘汰访问频率最少的(Redis 4.0+)纯缓存且访问频率差异大
volatile-random设置了过期时间的 key随机淘汰无明显热点,要求极速淘汰
allkeys-random所有 key随机淘汰同左
volatile-ttl设置了过期时间的 key优先淘汰最早过期的相当于局限于过期 key 的 FIFO
noeviction所有 key不淘汰,写入报错(默认)不允许数据丢失的场景

1.4. 缓存四大灾难与防御体系

灾难名称核心现象根本原因解决方案
缓存预热系统刚上线/Redis 重启/大促前,MySQL 压力骤增缓存为空,冷启动提前统计热点数据,启动时写入 Redis;定时预热、大促前预热
缓存穿透请求的 key 在 Redis 和 DB 都不存在恶意攻击、参数缺乏校验、数据误删1. 参数合法性校验 2. 缓存空值(设短过期时间) 3. 布隆过滤器
缓存雪崩大量 key 同时失效或 Redis 宕机,MySQL 压力骤增甚至宕机大量 key 相同过期时间;Redis 整体不可用1. 高可用 Redis 集群 + 监控 2. 过期时间加随机因子 3. 热点数据不设过期或逻辑过期 4. 多级缓存、限流降级
缓存击穿单个热点 key 突然过期,大量请求直击 DB热点 key 过期1. 热点 key 永不过期或逻辑过期 2. 互斥锁/分布式锁 3. 服务降级

2. Redis 分布式锁(Distributed Lock)

在分布式系统中,多个节点访问公共资源,传统的 synchronized(Java)或 std::mutex(C++)只能保证单进程内的线程安全,跨进程/跨主机无能为力

因此分布式锁的本质是使用一个公共服务器来记录加锁状态

这个公共的服务器可以是 Redis, 也可以是其他组件(⽐如 MySQL 或者 ZooKeeper 等), 还可以
是我们自己写的⼀个服务

2.1. 分布式锁的演进史

阶段一:基础实现 SETNX

  • 做法:SETNX key value(Set if Not eXists)。成功则视为加锁,操作完成后 DEL 删除
  • 致命缺陷:如果服务器 1 加锁后意外宕机,DEL 无法执行,锁永远无法释放(死锁)

阶段二:引入过期时间

  • 做法:使用 SET key value NX EX 10(原子操作)
  • 避坑:不能分开写 SETNX 和 EXPIRE!分开写有中间失败风险;用事务可以保证一起执行,但 Redis 事务不支持回滚,且不是最优解;最佳实践是 SET NX EX 单命令
  • 遗留缺陷:服务器 2 可能会误删服务器 1 的锁(例如业务逻辑阻塞,锁过期后,服务器 2 加锁,此时服务器 1 恢复并执行了 DEL)

阶段三:引入校验 ID

  • 做法:value 设为服务器编号(如 "服务器1")。删除前先 GET 校验 value 是否匹配
  • 伪代码逻辑:
if (redis.get(key) == serverId) {
    redis.del(key);
}
  • 遗留缺陷:GET 和 DEL 是两步操作,不具备原子性。如果在 GET 成功之后,DEL 执行之前,key 刚好过期,服务器 2 加锁成功,此时服务器 1 执行 DEL,依然会误删服务器 2 的锁

阶段四:引入 Lua 脚本(原子性)

  • 做法:利用 Redis 单线程执行 Lua 脚本的原子性,将“判断”和“删除”合二为一
  • Lua 脚本示例:
if redis.call('get', KEYS[1]) == ARGV[1] then 
    return redis.call('del', KEYS[1]) 
else 
    return 0 
end

阶段五:引入 Watch Dog(看门狗 - 自动续期)

  • 问题:业务还没执行完,锁提前过期了怎么办?设置太长,服务器宕机又会导致长时间无法释放锁
  • 核心思想:动态调整。加锁的服务器上开启一个单独的线程(Watch Dog)
  • 机制:假设初始过期时间 10s,看门狗每隔 3s 检测一次。如果任务未完成,将过期时间重写为 10s(续约);如果任务完成,直接 Lua 脚本释放锁。如果服务器宕机,看门狗线程随之挂掉,无人续约,锁在 10s 后自动过期,其他服务器可获取锁

阶段六:引入 Redlock 算法(解决主从失效)

  • 极端场景:服务器 1 向 Master 加锁成功,Master 挂掉,Slave 升级为新 Master,但 key 尚未同步。此时服务器 2 依然能加锁成功,锁形同虚设
  • Redlock 核心思想:多节点投票,少数服从多数
  • 算法流程:
  1. 部署 5 个独立的 Redis Master 节点(相互独立,非集群)
  2. 客户端依次向 5 个节点申请加锁,设置超时时间(如 50ms),如果超时就跳过该节点
  3. 如果成功加锁的节点数 超过半数(≥3),且总耗时小于锁的有效时间,则视为加锁成功
  4. 释放锁时,需要向所有节点(包括加锁失败的节点)发送释放请求
  • 结论:分布式系统中任何节点都不可靠,Redlock 通过多节点备份将可靠性大幅提升

3. 面试高频接口与命令速查

业务场景Redis 命令/API作用与注意事项
缓存读取GET key内存操作,O(1) 时间复杂度,拦截绝大多数读请求
缓存写入SET key value [EX seconds]配合 maxmemory 和淘汰策略使用
加锁(原子)SET key value NX EX 10唯一正确的新式加锁命令,务必使用单条命令
解锁(原子)EVAL "lua script" 1 key value确保“校验 ID”和“删除 Key”的原子性
设置缓存上限CONFIG SET maxmemory 2gb触发淘汰策略的阈值
内存淘汰策略CONFIG SET maxmemory-policy allkeys-lru动态调整淘汰算法

设计一个高可用的 Redis 缓存和分布式锁架构

  1. 缓存方面,我会采用 Redis 作为 MySQL 的护盾。首先通过二八定律筛选热点数据,利用 allkeys-lru 或 allkeys-lfu 策略进行实时淘汰。同时,为防穿透,我会接入布隆过滤器并缓存空值;为防雪崩,过期时间会加上随机因子;针对击穿,热点数据采用互斥锁或逻辑过期
  2. 在分布式锁方面,我会演进式地考虑。首选 SET key value NX EX 原子加锁,配合 Lua 脚本实现带有校验 ID 的原子解锁。为了应对业务长耗时,引入看门狗机制进行动态续约;如果是对可靠性要求极高的金融级场景,我会采用 Redlock 算法,向半数以上的独立节点申请锁,以此规避主从异步复制带来的锁失效问题。当然,在实际开发中,我会直接使用 redis-plus-plus 框架,避免重复造轮子

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/zl_dfq/article/details/166645934

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--