Redis应用(缓存和分布式锁)

目录

一、缓存

1.1、缓存的本质

1.2、使用Redis作为缓存

1.3、缓存更新策略

1、定时生成

2、实时生成

3、淘汰策略

1.4、缓存四大核心问题

1、缓存预热

2、缓存穿透

3、缓存雪崩

4、缓存击穿

二、分布式锁

2.1、基本概念

2.2、分布式锁的逐步实现

1、基础实现:SETNX命令

2、添加过期时间

3、引入校验ID

4、引入Lua

[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 组);

整个加锁过程总耗时 ≤ 预设总超时时间。

相关推荐
小二·12 小时前
Redis 内存溢出(OOM)排查与恢复实战
数据库·redis·bootstrap
pqk6V6Vep12 小时前
Redis 分布式锁进阶第一篇讲解
数据库·redis·分布式
giaz14n9X12 小时前
Redis 分布式锁进阶第六十一篇
数据库·redis·分布式
洛水水13 小时前
消息队列与Kafka详解
分布式·kafka
鸿乃江边鸟15 小时前
Spark中怎么做Spark canonicalize归一化
大数据·分布式·spark
JAVA面经实录91715 小时前
Redis 知识体系(完整版)
java·redis·nosql数据库·nosql
SLD_Allen15 小时前
Kafka分区与消费者的关系kafka分区和消费者线程的关系
分布式·kafka
he___H15 小时前
数据密集型应用系统设计--其一
分布式
颜笑晏晏16 小时前
长输入短输出场景下的 SGLang 推理性能实测前缀缓存、PD 分离配比与参数调优
缓存·推理优化·sglang·ai infra·pd分离
ManageEngine卓豪16 小时前
数据库可观测性:MySQL与Redis监控核心监控指标与全栈运维解决方案
数据库·redis·mysql·数据库性能·数据库监控