Redis三大缓存问题与分布式锁

文章目录


一、缓存穿透:查不存在的数据,绕过缓存直连DB

1. 什么是穿透

用户查询数据库、缓存都不存在 的数据,比如传-1、负数用户ID、随机垃圾参数。

缓存查不到数据不会写入空记录,请求每次都会落到MySQL。黑客恶意批量请求不存在key,会直接压垮数据库。

2. 四种解决方案

  1. 缓存空值(最简单)
    查询DB返回null时,往Redis存入空字符串,设置短期过期(3~5分钟)。相同请求下次直接拦截,不用查库。
    缺点:会产生大量无效空key,占用少量内存。
  2. 参数白名单校验
    提前规定合法ID范围,前端/接口层直接拦截非法参数,不进入缓存查询逻辑。适合ID自增、固定枚举场景。
  3. 布隆过滤器(高并发最优)
    把所有合法数据ID存入布隆过滤器,请求先过过滤器,不存在直接拦截。
    优点:内存占用极小;缺点:存在微小误判概率,不支持删除数据。
  4. 实时监控预警
    监控Redis命中率,命中率骤降立刻排查异常IP,配置黑名单限流。

二、缓存击穿:热点key过期,并发打爆数据库

1. 概念区分

和穿透完全不同:数据真实存在,只是缓存key刚好过期。秒杀商品、首页热点资讯这类高并发key过期瞬间,成千上万个请求同时去查数据库,瞬间压垮DB。

2. 主流解决办法

  1. 热点key永不过期
    提前在项目启动/流量低谷时把热门数据写入Redis,代码不设置expire,后台定时主动更新缓存,杜绝过期场景。
  2. 过期时间随机偏移
    给同类key统一过期时间基础上加随机秒数(1~30分钟随机),避免大批量热点同时失效。
  3. Redis互斥锁(SETNX/SET NX EX)
    缓存失效时,只放行一个线程查询DB更新缓存,其他线程等待重试,避免并发查库。
    核心命令:set hot_goods_lock 1 nx ex 5,原子加锁,避免死锁。

三、缓存雪崩:大批量key同时过期,全量请求落库

1. 雪崩vs击穿区别

  • 击穿:单个热点key过期,并发打库
  • 雪崩:大批量key集中同一时间过期,大量请求同时访问数据库,直接宕机

2. 完整解决方案

  1. 过期时间打散(最常用)
    统一过期时间追加随机值,消除集中失效的时间点。
  2. 多级缓存架构
    Nginx本地缓存 + Redis分布式缓存 + 本地Caffeine缓存,多层兜底,Redis崩了还有上层拦截。
  3. 加队列/限流削峰
    过期更新请求放入队列,限制同时访问DB的线程数量,控制数据库并发压力。
  4. 后台定时主动刷新
    不用等key过期再更新,定时任务提前刷新缓存,从根源杜绝集体过期。

四、分布式锁:集群环境并发数据安全必备

1. 为什么需要分布式锁

单机项目synchronizedLock锁只作用单个JVM。分布式多台服务器部署时,本地锁失效,多服务同时操作共享数据(库存、计数器)会出现超卖、数据错乱,需要跨服务的分布式锁。

主流实现对比:

  • 数据库锁:性能差,并发高容易堵库
  • Zookeeper锁:可靠性高,性能弱
  • Redis锁:性能最优,互联网项目首选

2. Redis分布式锁踩坑全过程

坑1:只用SETNX上锁,无过期

redis 复制代码
setnx lock_user 1
del lock_user

业务代码异常崩溃,锁永远不释放,直接死锁。

坑2:SETNX + EXPIRE两条命令,非原子

redis 复制代码
setnx lock_user 1
expire lock_user 10

上锁成功后Redis宕机,过期命令没执行,依旧死锁。

解决:一条命令原子上锁

redis 复制代码
set lock_user uuid nx ex 10

NX不存在才创建,EX设置过期,单命令原子执行。

坑3:锁被其他线程误删

线程A业务卡顿,锁自动过期;线程B抢到锁执行业务,A执行完手动删除B的锁。

解决:锁value存入UUID,删锁前校验持有者身份,只删自己的锁。

坑4:判断UUID+DEL删除,非原子操作

先GET对比UUID再DEL,中间锁过期被抢走,依然误删。

终极方案:Lua脚本保证解锁原子性

lua 复制代码
-- KEYS[1]锁key,ARGV[1]当前线程uuid
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

整个脚本一次性执行,不存在中间间隙,完美避免误删。

3. 合格Redis分布式锁四大硬性要求

  1. 互斥性:同一时间只能一个客户端持有锁
  2. 防死锁:锁自带过期时间,宕机自动释放
  3. 身份校验:谁加锁谁解锁,不能删除别人的锁
  4. 原子操作:加锁、解锁全过程原子执行

4. SpringBoot简易代码实现

java 复制代码
@GetMapping("/stock/test")
public String stockTest() {
    // 生成唯一标识
    String uuid = UUID.randomUUID().toString();
    String lockKey = "stock_lock";
    // 原子上锁,过期3秒
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 3, TimeUnit.SECONDS);
    if (lock) {
        try {
            // 扣库存业务
            String stockStr = redisTemplate.opsForValue().get("goods_stock");
            int stock = Integer.parseInt(stockStr);
            if (stock > 0) {
                redisTemplate.opsForValue().set("goods_stock", stock - 1);
                return "扣减成功,剩余库存:" + (stock - 1);
            }
            return "库存不足";
        } finally {
            // Lua脚本原子解锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> script = new DefaultRedisScript<>();
            script.setScriptText(luaScript);
            script.setResultType(Long.class);
            redisTemplate.execute(script, Collections.singletonList(lockKey), uuid);
        }
    } else {
        // 抢锁失败,重试
        try {
            Thread.sleep(100);
            return stockTest();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "请求繁忙,请重试";
        }
    }
}

五、总结

  1. 缓存穿透:查询不存在数据,方案:缓存空值、布隆过滤器、参数校验
  2. 缓存击穿:单个热点key过期,并发打库,方案:永不过期、随机过期、互斥锁
  3. 缓存雪崩:批量key同时过期,方案:随机过期、多级缓存、定时刷新
  4. 分布式锁 :分布式并发控制,核心使用set key uuid nx ex原子上锁+Lua脚本原子解锁,满足互斥、防死锁、防误删、原子四大特性
相关推荐
DBA小马哥2 小时前
关系型数据库核心概念手册:SQL、事务与存储引擎的技术脉络
数据库·sql
丫头,冲鸭!!!2 小时前
记账网站3-连数据库
数据库·个人开发
灯澜忆梦2 小时前
【MySQL12】进阶篇 | SQL优化
数据库·sql·mysql·性能优化
叠层归一研究院2 小时前
AGI 系统(五):多主体 AGI 网络 — 分布式符号边界上的时序差同构传播
分布式·php·agi
IvorySQL2 小时前
PostgreSQL 日报|PG18.5 回归测试崩溃问题(8 月 12 日)
大数据·数据库·人工智能·postgresql
ltl2 小时前
学习型查询优化器:Neo、Bao、Balsa 与 LLM-CBO
数据库
ltl3 小时前
持久内存退场之后:ZNS SSD 与下一代非易失内存
数据库
ltl3 小时前
可观测性存储与成本:采样、下采样、冷热分层
运维
学代码的CJY3 小时前
Linux 库制作与原理:静态库、动态库、ELF 与动态链接完全指南
linux·运维·服务器