Redis 实战指南缓存、分布式锁、持久化Redis 是 CSDN 上后端技术流量最高的主题之一也是大厂面试必考。本文从数据结构讲起覆盖缓存三大问题穿透/击穿/雪崩、分布式锁、持久化、集群、性能调优全部带可运行代码。一、Redis 是什么Redis 是内存数据库——数据存在内存里读写纳秒级。相比 MySQL磁盘它快几个数量级代价是数据不是主要存储。用途缓存加速读分布式锁并发控制计数器点赞、库存消息队列List/Stream排行榜ZSet会话共享Session 存 Redis。核心特点单线程执行命令6.0 后多线程处理网络 IO但命令执行仍单线程——所以命令都是原子的丰富的数据结构支持持久化RDB/AOF支持主从、哨兵、集群。二、五种基本数据结构2.1 String最常用。存 JSON、计数、分布式锁。SET user:1001{name:张三,age:25}GET user:1001 INCR visit:count# 自增SETNX lock:order1# 不存在才设置分布式锁用EXPIRE user:10013600# 设置过期2.2 Hash适合存对象字段可单独更新某个字段。HSET user:1001 name 张三 age25HGET user:1001 name HINCRBY user:1001 score10为什么比 String 好对象场景下Hash 只改一个字段不用整体反序列化内存占用也低。2.3 List双向链表。消息队列、时间线。LPUSH news:techSpring Boot 3 发布LRANGE news:tech0-1LPOP news:tech2.4 Set去重、集合运算。关注关系、标签。SADD user:1001:following20012002SISMEMBER user:1001:following2001# 是否关注SINTER user:1001:following user:1002:following# 共同关注2.5 ZSet有序集合带分数的集合。排行榜、延迟队列。ZADD rank:game1000playerAZADD rank:game800playerBZRANGE rank:game0-1WITHSCORES# 升序ZREVRANGE rank:game0-1# 降序排行榜ZINCRBY rank:game100playerA# 加分数三、缓存三大经典问题这是面试必问、生产必踩的三座山。3.1 缓存穿透现象查一个不存在的 key——缓存没有数据库也没有。每次请求都打到数据库。危害恶意请求用不存在的 id 刷接口数据库直接被打挂。解决缓存空值查询结果为空也缓存过期时间设短如 5 分钟布隆过滤器请求先过 Bloom Filter不存在的 key 直接拦截不用查库。// 方案一缓存空值publicUsergetUser(Longid){Stringkeyuser:id;Useruserredis.get(key);if(user!null)returnuser;useruserMapper.selectById(id);if(usernull){redis.set(key,,300);// 缓存空值 5 分钟returnnull;}redis.set(key,user,3600);returnuser;}3.2 缓存击穿现象某个热点 key 过期的瞬间大量请求同时打到数据库。区别穿透是查不存在击穿是查一个存在但缓存刚好过期。解决互斥锁只有一个线程去查库其他线程等待逻辑过期key 永不过期value 里带过期时间异步刷新。// 方案互斥锁publicUsergetUser(Longid){Useruserredis.get(user:id);if(user!null)returnuser;StringlockKeylock:user:id;booleanlockedredis.setIfAbsent(lockKey,1,30,TimeUnit.SECONDS);if(!locked){Thread.sleep(50);returngetUser(id);// 递归等待}try{useruserMapper.selectById(id);// 只有一个线程进来redis.set(user:id,user,3600);returnuser;}finally{redis.delete(lockKey);}}3.3 缓存雪崩现象大量 key 同时过期或 Redis 宕机请求全部打到数据库。解决过期时间加随机值set key val (3600 random(0,600))避免同时过期多级缓存本地缓存Caffeine RedisRedis 高可用主从 哨兵限流降级数据库扛不住时直接降级。三者对比问题场景核心解决穿透查不存在的 key空值缓存 / 布隆过滤器击穿热点 key 过期瞬间互斥锁 / 逻辑过期雪崩大量 key 同时过期 / Redis 宕机随机过期 / 高可用 / 多级缓存四、分布式锁4.1 为什么需要分布式锁单体时代用synchronized多实例部署后两个 JVM 各持一把锁就不管用了——需要跨进程的锁。场景防止重复下单、库存扣减、定时任务重复执行。4.2 基于 Redis 的分布式锁版本一简单版不推荐生产用// 加锁Booleanokredis.setIfAbsent(lock:order:1001,1,30,TimeUnit.SECONDS);if(ok){try{doBiz();}finally{redis.delete(lock:order:1001);}}问题误删锁线程 A 的锁过期了线程 B 拿到锁A 执行完把 B 的锁删了——要校验 value 是自己的过期时间太短业务没执行完锁就过期了。版本二校验 value 续期StringtokenUUID.randomUUID().toString();Booleanokredis.setIfAbsent(lockKey,token,30,TimeUnit.SECONDS);if(ok){// 看门狗续期Redisson 自动做try{doBiz();}finally{// 只删自己的锁if(token.equals(redis.get(lockKey))){redis.delete(lockKey);}}}生产建议直接用Redisson它实现了看门狗自动续期、可重入锁、公平锁别自己造轮子RLocklockredisson.getLock(lock:order:orderId);if(lock.tryLock(3,30,TimeUnit.SECONDS)){try{doBiz();}finally{lock.unlock();}}4.3 Redlock 与主从问题Redis 主从切换时锁可能丢主挂了从没同步到锁。极端场景用 Redlock多节点加锁但复杂且有争议。大多数业务场景单节点 Redis 哨兵 合理过期时间就够别过度设计。五、持久化Redis 数据在内存重启会丢——持久化解决这个问题。5.1 RDB快照定时把内存数据 dump 到磁盘。# redis.conf save 900 1 # 900秒内1次写就触发 save 300 10 save 60 10000优点文件小、恢复快、适合备份。缺点可能丢最后一次快照后的数据。5.2 AOF追加日志每次写命令追加到日志文件。appendonly yes appendfsync everysec # 每秒刷盘推荐优点最多丢 1 秒数据。缺点文件大、恢复慢可 AOF rewrite 压缩。5.3 怎么选能丢 1 分钟数据RDB 够不能丢数据订单、支付AOF everysec生产标配RDB AOF 都开。六、高可用架构6.1 主从复制一个主节点写多个从节点读读写分离。# 从节点配置replicaof192.168.1.106379主节点写命令同步到从节点读压力大时读请求走从节点缺点主挂了要手动切换用哨兵解决。6.2 哨兵Sentinel监控主节点挂了自动选新主Sentinel 集群3 个以上 ↓ 监控 Redis 主节点 ←→ 从节点1 ←→ 从节点2redis-sentinel sentinel.conf作用故障自动切换、通知客户端新主地址。6.3 Cluster 集群数据量大 写量大用 Cluster 分片16384 个 hash slotkey 按CRC16(key) % 16384分配到节点每个节点管一部分 slot支持自动扩容/缩容。选型建议规模方案单机 10G主从 哨兵读写分离主从数据量大/写量大Cluster云厂商直接用云 Redis自带高可用七、Redis 性能调优7.1 慢查询SLOWLOG GET10# 看最近慢命令configsetslowlog-log-slower-than10000# 阈值 10ms常见慢命令KEYS *全量扫、大 key 的HGETALL、SMEMBERS。禁止 KEYS生产环境KEYS *会阻塞 Redis 几秒。用SCAN游标式遍历。7.2 大 Key 问题单个 key 值超过 10KB 或集合元素超过 1 万个叫大 key读写慢、阻塞删除时卡顿。处理拆分 key、压缩值、删除用UNLINK异步删除。7.3 热点 Key某个 key 被超高并发访问单节点扛不住本地缓存JVM 里缓存热点数据Redis 再挂也是走本地读写分离读走多个从节点key 分片key:1、key:2…分散到不同节点。7.4 内存淘汰策略maxmemory 4gb maxmemory-policy allkeys-lru # 淘汰最近最少使用策略allkeys-lru所有 key 按 LRU 淘汰常用volatile-lru只淘汰有过期时间的allkeys-lfu按访问频率热数据稳定场景。八、Spring Boot 集成 Redis8.1 依赖与配置dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencyspring:data:redis:host:localhostport:6379lettuce:pool:max-active:208.2 封装工具类ComponentpublicclassRedisUtil{AutowiredprivateStringRedisTemplateredis;publicvoidset(Stringkey,Stringvalue,longtimeout){redis.opsForValue().set(key,value,timeout,TimeUnit.SECONDS);}publicStringget(Stringkey){returnredis.opsForValue().get(key);}publicbooleansetIfAbsent(Stringkey,Stringvalue,longtimeout){returnredis.opsForValue().setIfAbsent(key,value,timeout,TimeUnit.SECONDS);}publicvoiddelete(Stringkey){redis.delete(key);}}坑RedisTemplate默认 JDK 序列化存进去的 key 带乱码前缀。用StringRedisTemplate或配置 JSON 序列化器。九、实战案例商品详情缓存ServicepublicclassProductService{// 商品详情本地缓存 → Redis → 数据库publicProductDetailgetDetail(LongproductId){// 1. 本地缓存ProductDetaillocallocalCache.getIfPresent(productId);if(local!null)returnlocal;// 2. Redis带逻辑过期防击穿Stringkeyproduct:detail:productId;ProductDetailredisDataredis.get(key);if(redisData!null){if(redisData.isExpired()){// 逻辑过期异步刷新先返回旧数据asyncRefresh(productId,key);}localCache.put(productId,redisData);returnredisData;}// 3. 互斥锁查库StringlockKeylock:product:productId;booleanlockedredis.setIfAbsent(lockKey,1,30,TimeUnit.SECONDS);if(!locked){Thread.sleep(50);returngetDetail(productId);}try{ProductDetaildetailproductMapper.selectDetail(productId);if(detailnull){redis.set(key,,300);// 缓存空值防穿透returnnull;}redis.set(key,detail,3600);localCache.put(productId,detail);returndetail;}finally{redis.delete(lockKey);}}}这一套组合拳同时防了穿透、击穿、雪崩空值缓存防穿透互斥锁防击穿随机过期本地缓存防雪崩。本章小结Redis 看似简单set/get但生产级的难点全在缓存一致性、高可用、性能边界这些细节上。掌握三大问题 分布式锁 持久化 集群就掌握了 Redis 的核心战场。下一篇讲 Docker——部署的标准化和 Redis 一样是 CSDN 热门。全文完