
在现代互联网架构中,Redis 作为一款高性能、多功能的内存型 NoSQL 数据库,早已成为后端开发的核心组件。它不仅能作为缓存缓解数据库压力,还能实现分布式锁 、消息队列 、实时统计等多种场景需求。本文将从 Redis 核心原理出发,结合 Spring Boot + Spring Data Redis 实战代码,完整覆盖数据结构、高级功能、缓存异常解决方案、Lua 原子操作及集群配置,助力开发者从入门到精通 Redis 实战应用。
一、Redis 核心基础
1.1 定义与核心定位
Redis(Remote Dictionary Server)是一款开源、基于内存、可持久化的 Key-Value 型 NoSQL 数据库,由意大利程序员 Salvatore Sanfilippo(antirez)于2009年发布。其核心定位分为四类:
-
高性能缓存:缓存热点数据,拦截80%以上的数据库查询,降低MySQL等关系型数据库压力;
-
临时数据库:适合高频读写、短期存储的数据(如验证码、会话信息);
-
中间件:实现分布式锁、消息队列、接口限流、发布订阅等功能;
-
统计引擎:用于签到、点赞、UV统计、排行榜等实时数据统计场景。
开源协议说明:Redis 7.2及更早版本采用宽松的BSD协议(商用无限制),Redis 8.0+ 改为SSPL/AGPL协议,企业私有化部署需注意协议约束。
1.2 核心架构与运行模型
1.2.1 单线程模型(核心优势)
Redis 命令执行核心全程采用单线程,所有CRUD、事务、Lua脚本均串行执行,这也是其高性能的关键原因之一:
-
避免多线程的锁竞争、上下文切换、死锁等开销;
-
纯内存操作,无需磁盘IO耗时,读写延迟可达微秒级;
-
通过IO多路复用(epoll/kqueue),单线程可监听万级客户端连接,支撑高并发。
注意:Redis 6.0+ 引入多线程IO优化,但仅用于连接建立、数据读写、协议解析,命令执行仍保持单线程,确保数据安全无并发冲突。
1.2.2 数据持久化(解决内存断电丢失问题)
Redis 数据默认存储在内存中,断电易丢失,提供两套持久化方案,可单独或组合使用,生产环境推荐混合持久化。
-
RDB(快照持久化):定时将全量内存数据生成二进制快照文件保存到磁盘,文件体积小、恢复速度快,适合冷备份和灾备,但两次快照之间的数据会丢失,实时性差;
-
AOF(日志持久化):记录所有写操作命令,以日志形式追加写入文件,支持三种刷盘策略(always/everysec/no),其中everysec(每秒刷盘)为生产默认,数据几乎不丢失,但日志文件大、重启恢复慢;
-
混合持久化(Redis 4.0+):结合RDB和AOF的优势,重启时先用RDB快速加载全量数据,再用AOF补充增量数据,兼顾恢复速度与数据安全性,是生产环境标配。
1.2.3 内存淘汰策略(面试必背)
当Redis内存占满时,会自动淘汰冷数据,常用策略如下(生产首选前2种):
-
allkeys-lru:淘汰所有key中最近最少使用的,适合热点数据集中的场景;
-
volatile-lru:只淘汰带有过期时间的key中最近最少使用的,适合部分数据需要长期存储的场景;
-
allkeys-lfu:淘汰所有key中使用频率最低的;
-
volatile-ttl:淘汰带有过期时间的key中剩余过期时间最短的;
-
random:随机淘汰key,适合无明显热点数据的场景。
二、Redis 九大数据结构(原理+Java实战)
Redis 的核心竞争力在于支持10种原生数据结构,远超普通KV存储,以下结合 Spring Boot + Spring Data Redis 实战代码,讲解每种结构的原理与应用场景(代码可直接复制到项目中运行)。
2.1 环境准备
2.1.1 Maven依赖
<!-- SpringBoot 整合 Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 用于JSON序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2.1.2 application.yml配置
spring:
redis:
host: 127.0.0.1 # 本地Redis地址,生产改为服务器地址
port: 6379 # Redis默认端口
password: # 若Redis设置了密码,填写对应密码
database: 0 # 操作的Redis数据库(默认0号库)
lettuce:
pool:
max-active: 8 # 连接池最大活跃连接数
max-idle: 8 # 连接池最大空闲连接数
min-idle: 2 # 连接池最小空闲连接数
max-wait: 1000ms # 连接池最大等待时间
2.1.3 Redis配置类(序列化配置)
解决Redis默认序列化乱码问题,采用String序列化key、JSON序列化value,适配所有数据类型。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 绑定Redis连接工厂
template.setConnectionFactory(factory);
// key 序列化(String类型,避免乱码)
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// value 序列化(JSON类型,支持对象序列化)
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
// 初始化模板
template.afterPropertiesSet();
return template;
}
}
2.1.4 全局Redis工具类(项目直接复用)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 普通存值(无过期时间)
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
// 存值并设置过期时间
public void set(String key, Object value, long time, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, time, unit);
}
// 取值
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
// 删除key
public void del(String key) {
if (key != null) {
redisTemplate.delete(key);
}
}
// 判断key是否存在
public boolean exists(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
// 原子自增(计数器常用)
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
// 原子自减
public Long decrement(String key) {
return redisTemplate.opsForValue().decrement(key);
}
}
2.2 九大数据结构实战
2.2.1 String 字符串
原理:二进制安全,可存储字符串、数字、序列化对象,单个value最大512MB,支持原子自增/自减、过期时间,核心场景:缓存、验证码、计数器、分布式ID。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis/string")
public class StringController {
@Autowired
private RedisUtil redisUtil;
// 存值(验证码示例,5分钟过期)
@GetMapping("/set/code")
public String setCode() {
String key = "phoneCode:13800001111";
redisUtil.set(key, "886666", 5, TimeUnit.MINUTES);
return "验证码存入成功,key:" + key;
}
// 取值(获取验证码)
@GetMapping("/get/code")
public String getCode() {
String key = "phoneCode:13800001111";
Object code = redisUtil.get(key);
return "获取验证码:" + (code == null ? "已过期" : code);
}
// 原子自增(文章阅读量统计)
@GetMapping("/increment/read")
public String incrementReadCount() {
String key = "article:read:1001"; // 文章ID=1001
Long readCount = redisUtil.increment(key);
return "文章1001阅读量:" + readCount;
}
}
2.2.2 Hash 哈希(存储对象首选)
原理:一个key对应多个field-value,类似Java的Map<String, Object>,支持字段级更新,无需序列化整个对象,节省内存,核心场景:用户信息、商品资料、系统配置。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/redis/hash")
public class HashController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 批量存入用户信息(Hash)
@GetMapping("/set/user")
public String setUser() {
String key = "user:info:1001"; // 用户ID=1001
Map<String, Object> userMap = new HashMap<>();
userMap.put("username", "张三");
userMap.put("age", 23);
userMap.put("sex", "男");
userMap.put("phone", "13800001111");
// 批量存入Hash
redisTemplate.opsForHash().putAll(key, userMap);
return "用户信息存入成功";
}
// 获取单个字段(获取用户年龄)
@GetMapping("/get/user/age")
public String getUserAge() {
String key = "user:info:1001";
Object age = redisTemplate.opsForHash().get(key, "age");
return "用户1001年龄:" + age;
}
// 获取全部字段(获取完整用户信息)
@GetMapping("/get/user/all")
public Map<Object, Object> getUserAll() {
String key = "user:info:1001";
return redisTemplate.opsForHash().entries(key);
}
// 更新单个字段(修改用户年龄)
@GetMapping("/update/user/age")
public String updateUserAge() {
String key = "user:info:1001";
redisTemplate.opsForHash().put(key, "age", 24);
return "用户年龄更新成功";
}
}
2.2.3 List 列表(简单消息队列)
原理:双向链表,两头操作(左进右出/右进左出)效率极高(O(1)),有序、可重复,核心场景:简单消息队列、任务队列、时间轴、评论列表。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/list")
public class ListController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 左进(添加任务到队列)
@GetMapping("/push/task")
public String pushTask() {
String key = "task:queue"; // 任务队列key
// 批量添加任务
redisTemplate.opsForList().leftPushAll(key, "任务1", "任务2", "任务3", "任务4");
return "任务添加到队列成功";
}
// 右出(从队列取出任务,先进先出)
@GetMapping("/pop/task")
public Object popTask() {
String key = "task:queue";
// 右出:取出队列最前面的任务
return redisTemplate.opsForList().rightPop(key);
}
// 获取队列所有任务
@GetMapping("/get/all/task")
public Object getAllTask() {
String key = "task:queue";
// 0:起始索引,-1:结束索引(获取所有)
return redisTemplate.opsForList().range(key, 0, -1);
}
}
2.2.4 Set 集合(自动去重)
原理:无序、元素唯一,自动去重,支持交集、并集、差集运算,核心场景:好友关系、标签筛选、权限控制、数据去重。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/set")
public class SetController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 添加标签(自动去重)
@GetMapping("/add/tag")
public String addTag() {
String key = "user:tag:1001"; // 用户1001的标签
// 添加重复标签(Java会自动去重)
redisTemplate.opsForSet().add(key, "Java", "Redis", "MySQL", "Java", "SpringBoot");
return "标签添加成功(自动去重)";
}
// 获取用户所有标签
@GetMapping("/get/all/tag")
public Object getAllTag() {
String key = "user:tag:1001";
return redisTemplate.opsForSet().members(key);
}
// 交集(获取两个用户的共同标签)
@GetMapping("/inter/tag")
public Object interTag() {
String key1 = "user:tag:1001";
String key2 = "user:tag:1002";
// 交集:两个用户都有的标签
return redisTemplate.opsForSet().intersect(key1, key2);
}
}
2.2.5 ZSet 有序集合(排行榜核心)
原理:元素唯一,绑定score(分数),按分数自动排序,底层采用跳表实现,兼顾排序与查询效率,核心场景:排行榜、积分排名、热度排名、延迟队列。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/zset")
public class ZSetController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 添加排行榜数据(文章热度排名)
@GetMapping("/add/rank")
public String addRank() {
String key = "rank:article:hot"; // 文章热度排行榜
// 参数:key, value(文章标题), score(热度分数)
redisTemplate.opsForZSet().add(key, "Redis实战教程", 98);
redisTemplate.opsForZSet().add(key, "SpringBoot入门", 85);
redisTemplate.opsForZSet().add(key, "MySQL优化", 92);
redisTemplate.opsForZSet().add(key, "Java并发编程", 95);
return "排行榜数据写入成功";
}
// 升序获取排行榜(从低到高)
@GetMapping("/get/rank/asc")
public Object getRankAsc() {
String key = "rank:article:hot";
// 0:起始索引,-1:结束索引(所有数据)
return redisTemplate.opsForZSet().range(key, 0, -1);
}
// 降序获取排行榜(从高到低,常用)
@GetMapping("/get/rank/desc")
public Object getRankDesc() {
String key = "rank:article:hot";
return redisTemplate.opsForZSet().reverseRange(key, 0, -1);
}
// 获取文章热度分数
@GetMapping("/get/score")
public Object getScore() {
String key = "rank:article:hot";
return redisTemplate.opsForZSet().score(key, "Redis实战教程");
}
}
2.2.6 Bitmap 位图(省内存统计)
原理:特殊的String类型,按bit位存储数据,极度节省内存(1字节可存储8个状态),核心场景:每日签到、用户在线状态、亿级状态标记。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/bitmap")
public class BitmapController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 签到(用户1001 2026年4月26日签到)
@GetMapping("/sign")
public String sign() {
String key = "sign:20260426"; // 日期作为key
long userId = 1001; // 用户ID作为bit位索引
// 设置第1001位为1(表示签到成功)
redisTemplate.opsForValue().setBit(key, userId, true);
return "用户1001 2026年4月26日签到成功";
}
// 查询签到状态
@GetMapping("/check/sign")
public String checkSign() {
String key = "sign:20260426";
long userId = 1001;
Boolean isSign = redisTemplate.opsForValue().getBit(key, userId);
return "用户1001 2026年4月26日" + (isSign ? "已签到" : "未签到");
}
}
2.2.7 HyperLogLog(基数统计)
原理:概率型数据结构,用极小的内存(约12KB)实现基数统计(独立数量),允许0.81%的误差,核心场景:页面UV、独立访客统计、不要求绝对精准的去重统计。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/bitmap")
public class BitmapController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 签到(用户1001 2026年4月26日签到)
@GetMapping("/sign")
public String sign() {
String key = "sign:20260426"; // 日期作为key
long userId = 1001; // 用户ID作为bit位索引
// 设置第1001位为1(表示签到成功)
redisTemplate.opsForValue().setBit(key, userId, true);
return "用户1001 2026年4月26日签到成功";
}
// 查询签到状态
@GetMapping("/check/sign")
public String checkSign() {
String key = "sign:20260426";
long userId = 1001;
Boolean isSign = redisTemplate.opsForValue().getBit(key, userId);
return "用户1001 2026年4月26日" + (isSign ? "已签到" : "未签到");
}
}
2.2.8 Geospatial 地理位置(LBS场景)
原理:封装经纬度坐标,支持距离计算、范围查找,核心场景:附近的人、门店定位、LBS(地理位置服务)应用。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis/geo")
public class GeoController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 添加门店坐标
@GetMapping("/add/shop")
public String addShop() {
String key = "shop:geo"; // 门店地理位置集合
// 参数:key, 经纬度(经度,纬度), 门店名称
redisTemplate.opsForGeo().add(key, new Point(116.40, 39.90), "门店A");
redisTemplate.opsForGeo().add(key, new Point(116.41, 39.91), "门店B");
redisTemplate.opsForGeo().add(key, new Point(116.39, 39.89), "门店C");
return "门店坐标添加成功";
}
// 查询范围内的门店(以116.40,39.90为中心,1公里内)
@GetMapping("/get/shop/near")
public Object getShopNear() {
String key = "shop:geo";
Point center = new Point(116.40, 39.90);
Distance distance = new Distance(1, org.springframework.data.geo.Metric.KILOMETERS);
Circle circle = new Circle(center, distance);
// 查询1公里内的门店
return redisTemplate.opsForGeo().radius(key, circle);
}
}
2.2.9 Stream 消息队列(可靠异步)
原理:Redis 5.0+ 新增的专业消息队列结构,支持消息持久化、消费者组、消息确认、死信队列、回溯消费,替代简单MQ,实现可靠异步业务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.data.redis.stream.Subscription;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.Map;
@RestController
@RequestMapping("/redis/stream")
public class StreamController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 发送消息(订单创建消息)
@GetMapping("/send/order")
public String sendOrderMessage() {
String key = "stream:order"; // 订单消息流key
// 消息内容(订单ID、用户ID、金额)
Map<String, Object> message = Collections.singletonMap("orderId", "1008611");
// 发送消息,返回消息ID
String messageId = redisTemplate.opsForStream().add(key, message);
return "订单消息发送成功,消息ID:" + messageId;
}
// 消费消息(简单消费,无消费者组)
@GetMapping("/consumer/order")
public Object consumerOrderMessage() {
String key = "stream:order";
// 读取消息(从消息ID 0开始,读取1条)
return redisTemplate.opsForStream().read(Collections.singletonMap(key, "0"), 1);
}
}
三、Redis 高频高级功能(Java实战)
3.1 分布式锁(生产级实现)
原理:利用Redis的SET NX EX原子命令(key不存在则创建,同时设置过期时间),解决分布式集群下的并发竞争问题(如秒杀、库存扣减),避免超卖、重复操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 尝试获取分布式锁
public boolean tryLock(String lockKey, long expireTime) {
// NX:key不存在才创建(保证原子性),EX:设置过期时间(防止死锁)
return Boolean.TRUE.equals(
redisTemplate.opsForValue().setIfAbsent(lockKey, "lock_value", expireTime, TimeUnit.SECONDS)
);
}
// 释放分布式锁
public void unLock(String lockKey) {
redisTemplate.delete(lockKey);
}
// 业务实战(秒杀场景示例)
public String seckill(Long goodsId, Long userId) {
String lockKey = "lock:seckill:goods:" + goodsId; // 按商品ID加锁,避免全局锁阻塞
try {
// 尝试获取锁,30秒过期(防止死锁)
boolean hasLock = tryLock(lockKey, 30);
if (!hasLock) {
return "系统繁忙,请稍后再试";
}
// 执行业务逻辑(库存扣减、订单创建等)
// 此处省略库存扣减代码,实际开发中需结合数据库或Redis库存
return "秒杀成功!商品ID:" + goodsId + ",用户ID:" + userId;
} finally {
// 无论业务是否成功,都释放锁(避免死锁)
unLock(lockKey);
}
}
}
3.2 Lua 脚本实现原子操作(Java示例)
原理:Lua脚本可将多条Redis命令封装为一个脚本,在Redis服务端原子执行,减少网络往返,同时保证命令的原子性(避免并发问题),核心场景:复杂原子操作(如库存扣减+判断)、分布式锁优化。
以下示例:Lua脚本实现"库存扣减"原子操作(判断库存是否充足,充足则扣减,不足则返回失败)。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class RedisLuaUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 1. 定义Lua脚本(库存扣减:key=库存key,argv[1]=扣减数量)
private static final String STOCK_DEDUCT_LUA = "local stockKey = KEYS[1]\n" +
"local deductNum = tonumber(ARGV[1])\n" +
"local currentStock = tonumber(redis.call('get', stockKey))\n" +
"if currentStock < deductNum then\n" +
" return 0 -- 库存不足,扣减失败\n" +
"end\n" +
"redis.call('decrby', stockKey, deductNum)\n" +
"return 1 -- 扣减成功";
// 2. 执行Lua脚本
public Integer deductStock(String stockKey, int deductNum) {
// 初始化Lua脚本
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(STOCK_DEDUCT_LUA);
redisScript.setResultType(Integer.class); // 脚本返回值类型
// 封装KEYS和ARGV
List<String> keys = Arrays.asList(stockKey); // KEYS[1] = stockKey
List<Object> args = Arrays.asList(deductNum); // ARGV[1] = deductNum
// 执行脚本并返回结果(1=成功,0=失败)
return redisTemplate.execute(redisScript, keys, args.toArray());
}
// 业务调用示例
public String doDeductStock() {
String stockKey = "stock:goods:1001"; // 商品1001的库存key
int deductNum = 1; // 扣减1件库存
Integer result = deductStock(stockKey, deductNum);
return result == 1 ? "库存扣减成功" : "库存不足,扣减失败";
}
}
补充说明:Lua脚本的核心优势是"原子性",脚本中的所有命令会串行执行,不会被其他命令打断,适合解决并发场景下的竞态问题。
四、Redis 缓存异常(穿透/击穿/雪崩)完整解决方案 + Java 代码
Redis 作为缓存使用时,会遇到三大经典异常:缓存穿透、缓存击穿、缓存雪崩,若不处理会导致数据库压力剧增,甚至服务宕机,以下是完整解决方案及Java实战代码。
4.1 缓存穿透(解决方案:布隆过滤器 + 空值缓存)
4.1.1 问题描述
缓存穿透是指:客户端请求的key在Redis和数据库中都不存在,导致所有请求都直接穿透到数据库,大量无效请求压垮数据库(如恶意查询不存在的用户ID、商品ID)。
4.1.2 解决方案(双重防护)
-
布隆过滤器:提前将数据库中所有存在的key(如商品ID、用户ID)存入布隆过滤器,请求先经过布隆过滤器校验,不存在的key直接返回,不进入Redis和数据库;
-
空值缓存:若布隆过滤器校验通过,但Redis中无该key,查询数据库后发现也不存在,将该key的空值(或特定标记)存入Redis并设置短期过期时间,避免后续相同请求再次穿透。
4.1.3 Java 实战代码
使用Google Guava的布隆过滤器(简单易用,适合中小规模场景),结合空值缓存实现防护。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@Service
public class CachePenetrationService {
@Autowired
private RedisUtil redisUtil;
// 1. 初始化布隆过滤器(假设数据库中有100万条商品数据,误判率0.01)
private static final BloomFilter<String> BLOOM_FILTER = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
1000000, // 预计数据量
0.01 // 误判率(越小越精准,占用内存越大)
);
// 模拟:项目启动时,将数据库中的商品ID加载到布隆过滤器
static {
// 实际开发中,此处应从数据库查询所有商品ID,批量添加到布隆过滤器
BLOOM_FILTER.put("goods:1001");
BLOOM_FILTER.put("goods:1002");
BLOOM_FILTER.put("goods:1003");
// ... 其他商品ID
}
// 2. 缓存查询方法(解决缓存穿透)
public Object getGoodsInfo(String goodsId) {
String key = "goods:info:" + goodsId;
// 第一步:布隆过滤器校验,不存在直接返回
if (!BLOOM_FILTER.mightContain(goodsId)) {
return "商品不存在";
}
// 第二步:查询Redis缓存
Object goodsInfo = redisUtil.get(key);
if (goodsInfo != null) {
// 缓存命中,返回数据(注意:若为null标记,需返回商品不存在)
return goodsInfo == "null" ? "商品不存在" : goodsInfo;
}
// 第三步:缓存未命中,查询数据库
Object dbGoodsInfo = queryGoodsFromDb(goodsId);
// 第四步:数据库查询结果处理
if (dbGoodsInfo == null) {
// 数据库中也不存在,缓存空值(短期过期,如5分钟)
redisUtil.set(key, "null", 5, TimeUnit.MINUTES);
return "商品不存在";
} else {
// 数据库中存在,缓存数据(设置合理过期时间,如1小时)
redisUtil.set(key, dbGoodsInfo, 60, TimeUnit.MINUTES);
return dbGoodsInfo;
}
}
// 模拟:从数据库查询商品信息
private Object queryGoodsFromDb(String goodsId) {
// 实际开发中,此处是JDBC/MyBatis查询数据库的逻辑
if ("goods:1001".equals(goodsId)) {
return "商品1001:Redis实战教程,价格:99元";
} else if ("goods:1002".equals(goodsId)) {
return "商品1002:SpringBoot入门,价格:88元";
} else {
return null; // 模拟商品不存在
}
}
}
4.2 缓存击穿(解决方案:互斥锁 + 热点key永不过期)
4.2.1 问题描述
缓存击穿是指:某个热点key(如热门商品、首页数据)过期时,大量并发请求同时访问该key,导致所有请求都穿透到数据库,瞬间压垮数据库。
4.2.2 解决方案(两种方案可选)
-
互斥锁:当缓存过期时,只允许一个请求去查询数据库,其他请求等待,查询完成后更新缓存,后续请求从缓存获取数据;
-
热点key永不过期:对于核心热点key,不设置过期时间,通过后台定时任务(如每小时)更新缓存数据,避免过期导致的击穿。
4.2.3 Java 实战代码(互斥锁方案,生产常用)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class CacheBreakdownService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private RedisDistributedLock distributedLock;
// 本地锁(单服务节点可用),分布式场景用Redis分布式锁
private final Lock localLock = new ReentrantLock();
// 缓存查询方法(解决缓存击穿)
public Object getHotGoodsInfo(String goodsId) {
String key = "goods:hot:info:" + goodsId;
Object goodsInfo = redisUtil.get(key);
// 缓存未命中(可能过期)
if (goodsInfo == null) {
try {
// 方案1:本地锁(单服务节点)
localLock.lock();
// 再次查询缓存(防止多线程等待后重复查询数据库)
goodsInfo = redisUtil.get(key);
if (goodsInfo != null) {
return goodsInfo;
}
// 方案2:分布式锁(多服务节点集群,推荐)
// String lockKey = "lock:hot:goods:" + goodsId;
// if (!distributedLock.tryLock(lockKey, 30)) {
// // 获取锁失败,重试或返回默认值
// return "系统繁忙,请稍后再试";
// }
// 查询数据库
goodsInfo = queryGoodsFromDb(goodsId);
// 更新缓存(设置合理过期时间,如1小时)
redisUtil.set(key, goodsInfo, 60, TimeUnit.MINUTES);
} finally {
// 释放锁
localLock.unlock();
// 分布式锁释放:distributedLock.unLock(lockKey);
}
}
return goodsInfo;
}
// 模拟:从数据库查询热门商品信息
private Object queryGoodsFromDb(String goodsId) {
// 实际开发中,此处是数据库查询逻辑
if ("goods:1001".equals(goodsId)) {
return "热门商品1001:Redis实战教程,价格:99元,库存:1000件";
}
return null;
}
}
4.3 缓存雪崩(解决方案:过期时间随机化 + 多级缓存 + 集群高可用)
4.3.1 问题描述
缓存雪崩是指:大量缓存key在同一时间过期,导致大量并发请求同时穿透到数据库,数据库无法承受压力而宕机;或Redis集群宕机,所有请求直接打向数据库。
4.3.2 解决方案(三重防护)
-
过期时间随机化:给每个缓存key设置过期时间时,增加随机偏移量(如10~60秒),避免大量key同时过期;
-
多级缓存:引入本地缓存(如Caffeine),请求先查询本地缓存,再查询Redis,最后查询数据库,减少Redis压力;
-
Redis集群高可用:部署Redis主从+哨兵或Redis Cluster集群,避免Redis单点故障,确保缓存服务稳定。
4.3.3 Java 实战代码
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service
public class CacheAvalancheService {
@Autowired
private RedisUtil redisUtil;
// 1. 本地缓存(Caffeine,高性能本地缓存)
private final LoadingCache<String, Object> localCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存5分钟过期
.maximumSize(1000) // 本地缓存最大容量
.build(this::queryRedisCache); // 本地缓存未命中时,查询Redis
// 2. 随机过期时间(避免大量key同时过期)
private final Random random = new Random();
private static final int BASE_EXPIRE = 60; // 基础过期时间(1小时)
private static final int RANDOM_RANGE = 50; // 随机偏移量(0~50秒)
// 缓存查询方法(解决缓存雪崩)
public Object getGoodsInfo(String goodsId) {
try {
// 第一步:查询本地缓存(Caffeine)
return localCache.get(goodsId);
} catch (Exception e) {
// 本地缓存查询失败,直接查询Redis和数据库
return getGoodsInfoFromRedisAndDb(goodsId);
}
}
// 本地缓存未命中,查询Redis
private Object queryRedisCache(String goodsId) {
String key = "goods:info:" + goodsId;
Object goodsInfo = redisUtil.get(key);
if (goodsInfo == null) {
// Redis未命中,查询数据库并更新缓存
goodsInfo = queryGoodsFromDb(goodsId);
if (goodsInfo != null) {
// 设置随机过期时间(1小时 ± 0~50秒)
int expireTime = BASE_EXPIRE + random.nextInt(RANDOM_RANGE);
redisUtil.set(key, goodsInfo, expireTime, TimeUnit.MINUTES);
}
}
return goodsInfo;
}
// 本地缓存异常时,直接查询Redis和数据库
private Object getGoodsInfoFromRedisAndDb(String goodsId) {
String key = "goods:info:" + goodsId;
Object goodsInfo = redisUtil.get(key);
if (goodsInfo == null) {
goodsInfo = queryGoodsFromDb(goodsId);
if (goodsInfo != null) {
int expireTime = BASE_EXPIRE + random.nextInt(RANDOM_RANGE);
redisUtil.set(key, goodsInfo, expireTime, TimeUnit.MINUTES);
}
}
return goodsInfo;
}
// 模拟:从数据库查询商品信息
private Object queryGoodsFromDb(String goodsId) {
// 实际开发中,此处是数据库查询逻辑
return "商品" + goodsId + ":Redis实战教程,价格:99元";
}
}
五、Redis 集群、哨兵 Java 连接配置
生产环境中,Redis 单机部署无法满足高可用和高并发需求,通常采用两种部署模式:主从+哨兵(中小规模)、Redis Cluster(大规模、大数据量),以下是两种模式的Java连接配置(Spring Boot)。
5.1 Redis 主从 + 哨兵 配置(中小规模高可用)
5.1.1 部署架构说明
1主2从3哨兵:1个主节点(Master,负责写操作)、2个从节点(Slave,负责读操作)、3个哨兵节点(监控主从,自动故障转移)。
5.1.2 Java 连接配置(application.yml)
<!-- Caffeine 本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
说明:配置哨兵后,Spring Data Redis 会自动感知主从节点状态,当主节点宕机,哨兵自动选举新主节点,Java客户端无需修改配置,自动切换连接。
5.2 Redis Cluster 集群配置(大规模、大数据量)
5.2.1 部署架构说明
Redis Cluster 采用哈希槽分片机制(共16384个哈希槽),将数据均匀分配到多个节点,每个节点负责部分哈希槽,集群内自带主从复制,支持故障转移,可水平扩容至千级节点。
5.2.2 Java 连接配置(application.yml)
spring:
redis:
password: 123456 # Redis密码(生产环境必须设置)
database: 0
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: 1000ms
sentinel:
master: mymaster # 哨兵监控的主节点名称(与哨兵配置一致)
nodes: 192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379 # 3个哨兵节点地址
配置说明:
-
nodes:配置集群中所有节点的地址(包括主节点和从节点),Spring Data Redis会自动识别主从关系,无需手动区分;
-
max-redirects:当请求的key不在当前节点负责的哈希槽时,Redis会返回重定向指令,该参数设置最大重定向次数,避免无限循环;
-
lettuce.cluster.refresh:集群节点信息刷新策略,adaptive(自适应刷新)会在节点连接失败时自动刷新,period(定期刷新)会按指定时间(30秒)主动刷新,确保客户端能及时感知集群节点变化(如故障转移、新增节点)。
5.2.3 集群配置补充(生产必配)
在实际生产环境中,需结合业务场景补充以下配置,避免集群连接异常、性能瓶颈:
spring:
redis:
# 超时配置(避免集群连接超时)
timeout: 5000ms
cluster:
nodes: 192.168.1.104:6379,192.168.1.105:6379,192.168.1.106:6379,192.168.1.107:6379,192.168.1.108:6379,192.168.1.109:6379
max-redirects: 3
lettuce:
pool:
max-active: 32 # 连接池最大活跃连接数,根据并发量调整(集群场景需比单机高)
max-idle: 16
min-idle: 8
max-wait: 1000ms
cluster:
refresh:
adaptive: true
period: 30000
shutdown-timeout: 1000ms # 连接关闭超时时间,避免关闭时阻塞
注意:Redis Cluster 不支持多数据库(database),默认只能使用0号库,配置中database参数可省略,若填写需保持为0,否则会报错。
5.3 集群连接注意事项(生产避坑)
-
节点配置:集群节点地址需填写所有主从节点,避免只填主节点,否则当主节点故障转移后,客户端无法连接新主节点;
-
密码配置:若集群中所有节点密码一致,直接配置spring.redis.password即可;若密码不一致,需通过自定义Redis配置类手动配置(生产环境建议所有节点密码统一,降低维护成本);
-
连接池配置:集群场景下,连接池max-active需根据业务并发量调整,建议设置为单机场景的2-3倍,避免连接不足导致请求阻塞;
-
超时配置:集群跨节点请求耗时比单机长,需合理设置timeout(建议3000-5000ms),避免因超时导致请求失败;
-
分片注意:Redis Cluster 按哈希槽分片,key的哈希值决定存储在哪个节点,若需指定key存储节点,可通过"{key前缀}key"的格式(如{user}1001),确保同一前缀的key落在同一节点(适合需批量操作的场景)。
六、Redis 生产环境优化(Java实战适配)
Redis 实战中,除了核心功能和集群配置,生产环境的优化的是保障服务稳定、高性能的关键。以下从Java客户端优化、Redis服务端优化、业务层面优化三个维度,结合实战场景给出具体方案。
6.1 Java客户端优化(Spring Boot场景)
6.1.1 连接池优化(核心)
Spring Data Redis 默认使用Lettuce客户端(替代早期的Jedis),Lettuce基于Netty实现,支持异步、连接池复用,性能优于Jedis,生产环境需重点优化连接池参数:
spring:
redis:
lettuce:
pool:
max-active: 16-32 # 核心参数,根据并发量调整(单机16,集群32)
max-idle: 8-16 # 最大空闲连接,建议为max-active的1/2
min-idle: 2-4 # 最小空闲连接,避免频繁创建连接
max-wait: 1000-3000ms # 最大等待时间,超时抛出异常,避免无限阻塞
shutdown-timeout: 1000ms # 关闭连接超时时间,防止服务停机时阻塞
补充说明:连接池参数需结合服务器配置(CPU、内存)和业务并发量调整,避免配置过大导致内存占用过高,或配置过小导致连接不足。
6.1.2 序列化优化(避免乱码、节省内存)
前文配置类采用"String序列化key + JSON序列化value",适合大多数场景,生产环境可进一步优化:
-
避免使用JDK默认序列化:JDK序列化会导致value体积过大、乱码,且不支持跨语言解析,优先使用JSON序列化(GenericJackson2JsonRedisSerializer);
-
自定义序列化:对于频繁存储的对象(如用户信息、商品信息),可自定义序列化器,减少JSON序列化的额外开销(如排除无用字段);
-
key序列化规范:key统一采用"业务模块:具体标识"格式(如user:info:1001),避免key混乱,同时便于Redis管理和排查问题。
// 优化后的Redis配置类(自定义序列化,排除无用字段)
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class OptimizedRedisConfig {@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // key序列化(String) template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // 自定义JSON序列化,排除对象中无用字段 ObjectMapper objectMapper = new ObjectMapper(); // 忽略未知字段,避免序列化失败 objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 排除带有@JsonIgnore注解的字段 objectMapper.addMixIn(Object.class, IgnoreUnknownPropertiesMixin.class); GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } // 自定义混入类,用于排除无用字段 @JsonIgnoreProperties(ignoreUnknown = true) private static class IgnoreUnknownPropertiesMixin {}}
}
6.1.3 避免频繁创建RedisTemplate实例
RedisTemplate是线程安全的,无需每次使用时创建新实例,生产环境需将其注入Spring容器(如前文配置类),通过@Autowired注入使用,避免重复创建导致的资源浪费。
6.2 Redis服务端优化(配合Java客户端)
6.2.1 内存优化(避免内存溢出)
-
设置合理的内存上限:通过Redis配置文件(redis.conf)设置maxmemory,避免Redis占用过多内存导致服务器宕机,建议设置为服务器内存的50%-70%;
-
选择合适的内存淘汰策略:生产环境首选allkeys-lru(淘汰所有key中最近最少使用的),配合定期清理冷数据,减少内存占用;
-
避免存储大value:单个value建议不超过100KB,若需存储大对象(如文件、图片),可将对象分片存储,或使用分布式文件系统(如MinIO),Redis仅存储文件地址。
6.2.2 持久化优化(兼顾性能与数据安全)
生产环境推荐使用Redis混合持久化(Redis 4.0+),优化配置如下(redis.conf):
# 开启混合持久化(AOF + RDB)
aof-use-rdb-preamble yes
# AOF刷盘策略(生产默认everysec,兼顾性能与数据安全)
appendfsync everysec
# 关闭AOF重写时的自动触发(手动触发或定时触发,避免影响业务)
auto-aof-rewrite-percentage 0
# RDB快照定时触发(避免频繁快照影响性能,建议每6小时一次)
save 21600 1 # 6小时内至少1个key变化,触发RDB快照
补充:AOF重写建议在业务低峰期(如凌晨)手动触发,避免重写过程中占用过多CPU和IO,影响Java客户端请求。
6.2.3 高并发优化
-
开启Redis持久化压缩:AOF文件和RDB文件启用压缩,减少磁盘占用和IO耗时;
-
限制客户端连接数:通过redis.conf设置maxclients,避免过多客户端连接导致Redis崩溃,建议设置为10000-20000;
-
避免慢查询:禁止在生产环境执行keys *、hgetall等全量查询命令,若需批量查询,使用scan命令(分批查询,避免阻塞Redis)。
6.3 业务层面优化
6.3.1 缓存设计优化
-
缓存key过期时间合理设置:根据业务场景设置过期时间,避免过期时间过长导致内存浪费,或过短导致缓存穿透/击穿;热点数据可设置较长过期时间,配合定时更新;
-
缓存预热:项目启动时,将热点数据(如首页数据、热门商品)提前加载到Redis,避免项目启动后大量请求穿透到数据库;
-
缓存更新策略:采用"Cache-Aside"(缓存旁路)策略,更新数据库后同步删除缓存(而非更新缓存),避免缓存与数据库数据不一致;高并发场景下,可配合Lua脚本实现原子更新。
6.3.2 避免缓存滥用
-
不缓存高频更新的数据:如实时库存(需结合业务场景,可采用Redis原子操作实现实时更新)、频繁变化的日志数据;
-
不缓存大数据量、低访问频率的数据:如历史订单(超过3个月的订单),可直接查询数据库,避免占用Redis内存;
-
避免缓存重复数据:同一数据不重复存储多个key,可通过合理的key设计(如统一前缀)避免重复缓存。
6.3.3 异常处理优化(Java代码层面)
生产环境中,需处理Redis连接异常、超时、宕机等场景,避免影响业务正常运行,示例代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Autowired
private RedisUtil redisUtil;
// 优化后的缓存查询方法(增加异常处理)
public Object getBusinessData(String key) {
try {
// 1. 查询缓存
Object data = redisUtil.get(key);
if (data != null) {
return data;
}
// 2. 缓存未命中,查询数据库
Object dbData = queryDataFromDb(key);
if (dbData != null) {
// 3. 更新缓存(设置合理过期时间)
redisUtil.set(key, dbData, 30, java.util.concurrent.TimeUnit.MINUTES);
}
return dbData;
} catch (RedisConnectionFailureException e) {
// 处理Redis连接异常(如Redis宕机),直接查询数据库,保证业务可用
System.err.println("Redis连接异常,直接查询数据库:" + e.getMessage());
return queryDataFromDb(key);
} catch (Exception e) {
// 处理其他异常(如超时、序列化失败)
System.err.println("缓存查询异常:" + e.getMessage());
return "系统繁忙,请稍后再试";
}
}
// 模拟查询数据库
private Object queryDataFromDb(String key) {
// 实际开发中为数据库查询逻辑
return "业务数据:" + key;
}
}
七、Redis 常见问题排查(Java实战场景)
生产环境中,Redis 可能出现连接失败、性能下降、数据不一致等问题,以下结合Java客户端场景,给出常见问题的排查思路和解决方案。
7.1 连接失败(Java客户端报错)
7.1.1 常见报错
RedisConnectionFailureException、Could not get a resource from the pool、Connection refused等。
7.1.2 排查思路与解决方案
-
检查Redis服务状态:登录Redis服务器,执行redis-cli ping,若返回PONG则服务正常,否则启动Redis服务;
-
检查连接配置:确认Java客户端配置的host、port、password是否正确,集群场景需确认所有节点地址正确;
-
检查防火墙/网络:确认Redis服务器防火墙开放6379(单机)、26379(哨兵)端口,Java客户端所在服务器能ping通Redis服务器;
-
检查连接池配置:若报错"Could not get a resource from the pool",说明连接池耗尽,需增大max-active参数,同时检查是否有连接未释放(如未关闭Redis连接);
-
检查Redis最大连接数:若Redis连接数达到maxclients,会拒绝新连接,需增大maxclients参数。
7.2 缓存与数据库数据不一致
7.2.1 常见原因
-
更新数据库后未删除缓存,导致缓存中存的是旧数据;
-
高并发场景下,查询缓存未命中 → 查询数据库 → 数据库更新 → 缓存未更新(并发竞争);
-
缓存过期时间设置过短,数据库更新后缓存已过期,重新查询时获取旧数据。
7.2.2 解决方案
-
采用"更新数据库 + 删除缓存"策略,而非"更新数据库 + 更新缓存";
-
高并发场景下,使用Lua脚本实现"查询缓存 → 缓存未命中 → 查询数据库 → 更新缓存"的原子操作,避免并发竞争;
-
热点数据设置合理的过期时间,配合定时任务定期更新缓存,确保缓存与数据库数据一致;
-
引入缓存一致性校验机制,定期对比缓存与数据库数据,发现不一致时及时修复。
7.3 Redis 性能下降(Java客户端请求耗时增加)
7.3.1 常见原因
-
Redis服务端存在慢查询(如keys *、hgetall),阻塞Redis线程;
-
Redis内存占用过高,触发内存淘汰策略,频繁淘汰数据导致缓存命中率下降;
-
Java客户端连接池配置不合理,连接不足导致请求阻塞;
-
Redis持久化(AOF重写、RDB快照)占用过多CPU和IO资源。
7.3.2 排查思路与解决方案
-
查看Redis慢查询日志:执行redis-cli slowlog get,排查慢查询命令,禁止在生产环境执行全量查询命令,替换为scan命令;
-
查看Redis内存使用情况:执行redis-cli info memory,若内存占用过高,清理冷数据、优化缓存策略,调整内存淘汰策略;
-
优化Java客户端连接池:增大max-active参数,调整max-wait、min-idle等参数,避免连接阻塞;
-
优化Redis持久化:在业务低峰期触发AOF重写和RDB快照,减少对业务的影响;
-
查看Redis CPU和IO占用:若CPU占用过高,检查是否有高频写入操作,可通过批量操作减少请求次数;若IO占用过高,优化持久化策略、启用压缩。
八、总结
本文从Redis核心原理出发,结合Spring Boot + Spring Data Redis 实战代码,完整覆盖了Redis基础原理、九大数据结构实战、高级功能(分布式锁、Lua脚本)、缓存异常解决方案(穿透/击穿/雪崩)、集群与哨兵配置、生产环境优化及常见问题排查,形成了一套从入门到精通的Redis实战指南。
Redis 实战的核心是"原理+场景+优化":掌握Redis单线程模型、持久化、内存淘汰等核心原理,是实现正确应用的基础;结合业务场景选择合适的数据结构和功能,是提升开发效率的关键;生产环境的优化和问题排查,是保障服务稳定、高性能运行的核心。
后续可结合具体业务场景(如秒杀、分布式会话、实时统计),进一步深化Redis的应用,同时关注Redis新版本特性(如Redis 8.0的新功能),持续优化Redis实战方案。
