☰
Redis实战指南
2026/9/29 23:13:36 网站建设 项目流程

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:tech"Spring Boot 3 发布"LRANGE news:tech0-1LPOP news:tech

2.4 Set

去重、集合运算。关注关系、标签。

SADD user:1001:following20012002SISMEMBER user:1001:following2001# 是否关注SINTER user:1001:following user:1002:following# 共同关注

2.5 ZSet(有序集合)

带分数的集合。排行榜、延迟队列。

ZADD rank:game1000"playerA"ZADD rank:game800"playerB"ZRANGE rank:game0-1WITHSCORES# 升序ZREVRANGE rank:game0-1# 降序(排行榜)ZINCRBY rank:game100"playerA"# 加分数

三、缓存三大经典问题

这是面试必问、生产必踩的三座山。

3.1 缓存穿透

现象:查一个不存在的 key——缓存没有,数据库也没有。每次请求都打到数据库。

危害:恶意请求用不存在的 id 刷接口,数据库直接被打挂。

解决:

  1. 缓存空值:查询结果为空也缓存,过期时间设短(如 5 分钟);
  2. 布隆过滤器:请求先过 Bloom Filter,不存在的 key 直接拦截(不用查库)。
// 方案一:缓存空值publicUsergetUser(Longid){Stringkey="user:"+id;Useruser=redis.get(key);if(user!=null)returnuser;user=userMapper.selectById(id);if(user==null){redis.set(key,"",300);// 缓存空值 5 分钟returnnull;}redis.set(key,user,3600);returnuser;}

3.2 缓存击穿

现象:某个热点 key 过期的瞬间,大量请求同时打到数据库。

区别:穿透是"查不存在",击穿是"查一个存在但缓存刚好过期"。

解决:

  1. 互斥锁:只有一个线程去查库,其他线程等待;
  2. 逻辑过期:key 永不过期,value 里带过期时间,异步刷新。
// 方案:互斥锁publicUsergetUser(Longid){Useruser=redis.get("user:"+id);if(user!=null)returnuser;StringlockKey="lock:user:"+id;booleanlocked=redis.setIfAbsent(lockKey,"1",30,TimeUnit.SECONDS);if(!locked){Thread.sleep(50);returngetUser(id);// 递归等待}try{user=userMapper.selectById(id);// 只有一个线程进来redis.set("user:"+id,user,3600);returnuser;}finally{redis.delete(lockKey);}}

3.3 缓存雪崩

现象:大量 key 同时过期,或 Redis 宕机,请求全部打到数据库。

解决:

  1. 过期时间加随机值:set key val (3600 + random(0,600)),避免同时过期;
  2. 多级缓存:本地缓存(Caffeine)+ Redis;
  3. Redis 高可用:主从 + 哨兵;
  4. 限流降级:数据库扛不住时直接降级。

三者对比:

问题场景核心解决
穿透查不存在的 key空值缓存 / 布隆过滤器
击穿热点 key 过期瞬间互斥锁 / 逻辑过期
雪崩大量 key 同时过期 / Redis 宕机随机过期 / 高可用 / 多级缓存

四、分布式锁

4.1 为什么需要分布式锁

单体时代用synchronized,多实例部署后(两个 JVM 各持一把锁)就不管用了——需要跨进程的锁。

场景:防止重复下单、库存扣减、定时任务重复执行。

4.2 基于 Redis 的分布式锁

版本一:简单版(不推荐生产用)

// 加锁Booleanok=redis.setIfAbsent("lock:order:1001","1",30,TimeUnit.SECONDS);if(ok){try{doBiz();}finally{redis.delete("lock:order:1001");}}

问题:

  • 误删锁:线程 A 的锁过期了,线程 B 拿到锁,A 执行完把 B 的锁删了——要校验 value 是自己的;
  • 过期时间太短:业务没执行完锁就过期了。

版本二:校验 value + 续期

Stringtoken=UUID.randomUUID().toString();Booleanok=redis.setIfAbsent(lockKey,token,30,TimeUnit.SECONDS);if(ok){// 看门狗续期(Redisson 自动做)try{doBiz();}finally{// 只删自己的锁if(token.equals(redis.get(lockKey))){redis.delete(lockKey);}}}

生产建议:直接用Redisson,它实现了看门狗自动续期、可重入锁、公平锁,别自己造轮子:

RLocklock=redisson.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 ←→ 从节点2
redis-sentinel sentinel.conf

作用:故障自动切换、通知客户端新主地址。

6.3 Cluster 集群

数据量大 + 写量大,用 Cluster 分片:

  • 16384 个 hash slot,key 按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 集成 Redis

8.1 依赖与配置

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
spring:data:redis:host:localhostport:6379lettuce:pool:max-active:20

8.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. 本地缓存ProductDetaillocal=localCache.getIfPresent(productId);if(local!=null)returnlocal;// 2. Redis(带逻辑过期防击穿)Stringkey="product:detail:"+productId;ProductDetailredisData=redis.get(key);if(redisData!=null){if(redisData.isExpired()){// 逻辑过期:异步刷新,先返回旧数据asyncRefresh(productId,key);}localCache.put(productId,redisData);returnredisData;}// 3. 互斥锁查库StringlockKey="lock:product:"+productId;booleanlocked=redis.setIfAbsent(lockKey,"1",30,TimeUnit.SECONDS);if(!locked){Thread.sleep(50);returngetDetail(productId);}try{ProductDetaildetail=productMapper.selectDetail(productId);if(detail==null){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 热门。

(全文完)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询