目录
[Spring Boot 集成 Redis 实战:从基础配置到企业级进阶](#Spring Boot 集成 Redis 实战:从基础配置到企业级进阶)
[一、核心价值:为什么选择 Spring Boot + Redis?](#一、核心价值:为什么选择 Spring Boot + Redis?)
[1. Redis 的核心优势](#1. Redis 的核心优势)
[2. Spring Boot 集成 Redis 的优势](#2. Spring Boot 集成 Redis 的优势)
[3. 典型应用场景](#3. 典型应用场景)
[二、基础搭建:Spring Boot 整合 Redis 环境配置](#二、基础搭建:Spring Boot 整合 Redis 环境配置)
[1. 版本选型(兼容关键)](#1. 版本选型(兼容关键))
[2. 依赖配置(pom.xml)](#2. 依赖配置(pom.xml))
[3. 核心配置(application.yml)](#3. 核心配置(application.yml))
[4. 自定义 RedisTemplate 配置(解决序列化问题)](#4. 自定义 RedisTemplate 配置(解决序列化问题))
[三、基础用法:Redis 核心数据结构操作](#三、基础用法:Redis 核心数据结构操作)
[1. 实体类准备(用于对象存储测试)](#1. 实体类准备(用于对象存储测试))
[2. String 类型:最常用的键值对存储](#2. String 类型:最常用的键值对存储)
[3. Hash 类型:适合存储对象属性(无需序列化整个对象)](#3. Hash 类型:适合存储对象属性(无需序列化整个对象))
[4. List 类型:有序集合(支持双向操作,适用于消息队列、排行榜)](#4. List 类型:有序集合(支持双向操作,适用于消息队列、排行榜))
[5. Set 类型:无序唯一集合(适用于去重、好友关系)](#5. Set 类型:无序唯一集合(适用于去重、好友关系))
[6. ZSet 类型:有序唯一集合(适用于排行榜、计分系统)](#6. ZSet 类型:有序唯一集合(适用于排行榜、计分系统))
[1. Spring Cache 整合 Redis:注解式缓存开发](#1. Spring Cache 整合 Redis:注解式缓存开发)
[2. Redis 分布式锁:解决分布式并发问题](#2. Redis 分布式锁:解决分布式并发问题)
[3. Redis 管道(Pipeline):提升批量操作性能](#3. Redis 管道(Pipeline):提升批量操作性能)
[1. 需求场景](#1. 需求场景)
[2. 核心代码实现](#2. 核心代码实现)
[1. 序列化乱码问题](#1. 序列化乱码问题)
[2. 缓存穿透问题](#2. 缓存穿透问题)
[3. 缓存击穿问题](#3. 缓存击穿问题)
[4. 缓存雪崩问题](#4. 缓存雪崩问题)
[5. Redis 连接超时问题](#5. Redis 连接超时问题)
Spring Boot 集成 Redis 实战:从基础配置到企业级进阶
在分布式系统架构中,Redis 作为高性能的键值对数据库,以其内存存储、多数据结构支持、高可用特性,成为缓存、分布式锁、消息队列、限流等场景的首选中间件。Spring Boot 提供了 spring-boot-starter-data-redis 依赖,通过自动配置简化了 Redis 的集成流程,让开发者无需手动编写复杂的连接配置,即可快速实现 Redis 操作。本文将从核心价值、环境搭建、基础用法、进阶特性到实战案例,全面拆解 Spring Boot 与 Redis 的整合方案,覆盖企业级开发的核心需求。
一、核心价值:为什么选择 Spring Boot + Redis?
1. Redis 的核心优势
Redis 相比传统关系型数据库,在性能和功能上具备显著优势,也是其在分布式系统中广泛应用的原因:
- 超高性能:基于内存存储,读写速度极快(单机 QPS 可达 10 万 +),支持批量操作和管道技术,大幅提升数据访问效率;
- 多数据结构支持:内置 String、Hash、List、Set、ZSet、BitMap、Geo 等多种数据结构,适配不同业务场景(如缓存、排行榜、好友关系);
- 丰富的附加功能:支持持久化(RDB/AOF)、过期策略、事务、发布订阅、分布式锁,满足企业级复杂需求;
- 高可用架构:支持主从复制、哨兵模式、集群模式,保障服务的稳定性和可用性,应对高并发场景;
- 轻量级:部署简单、资源占用低,支持跨平台运行,可快速集成到各类项目中。
2. Spring Boot 集成 Redis 的优势
Spring Boot 对 Redis 的集成做了大量优化,相比原生 Redis 客户端(Jedis/Lettuce),大幅降低了开发成本:
- 自动配置 :引入
spring-boot-starter-data-redis后,自动配置 Redis 连接工厂、RedisTemplate 等核心组件,无需手动编写配置类; - 统一 API :通过
RedisTemplate和StringRedisTemplate封装了 Redis 的操作方法,屏蔽了底层客户端的差异(默认使用 Lettuce,可切换为 Jedis); - 序列化支持:支持多种序列化方式(JDK、Jackson、FastJSON),解决了 Redis 存储对象时的乱码问题;
- 无缝集成 Spring 生态:可与 Spring Cache 深度整合,通过注解快速实现缓存功能,无需手动操作 Redis;
- 灵活配置 :支持通过
application.yml/application.properties配置 Redis 连接信息、连接池参数,便于环境切换和运维管理。
3. 典型应用场景
Spring Boot + Redis 的组合在企业级项目中应用广泛,核心场景包括:
- 数据缓存:缓存热点数据(如用户信息、商品详情、接口响应结果),减轻数据库压力,提升接口响应速度;
- 分布式锁:解决分布式系统中的并发问题(如订单创建、库存扣减),保证操作的原子性;
- 会话存储:存储用户会话信息(如登录状态),实现分布式系统的会话共享;
- 计数器 / 排行榜:基于 Redis 的 incr 命令和 ZSet 结构,实现点赞数统计、商品销量排行榜等功能;
- 限流降级:基于 Redis 的计数器或滑动窗口,实现接口限流,防止系统被高并发请求压垮;
- 消息队列:基于 Redis 的 List 或 Pub/Sub 结构,实现简单的消息异步通信。
二、基础搭建:Spring Boot 整合 Redis 环境配置
1. 版本选型(兼容关键)
为避免版本冲突,推荐使用稳定兼容的版本组合:
- Spring Boot:2.7.x(稳定版,生态完善)
- Redis:6.2.x(稳定版,支持更多新特性)
- 底层客户端:Lettuce 6.x(Spring Boot 默认,非阻塞 IO,性能更优)
- 序列化:Jackson 2.13.x(Spring Boot 内置,方便对象序列化)
2. 依赖配置(pom.xml)
只需引入 spring-boot-starter-data-redis 核心依赖,若需使用连接池(优化 Lettuce 性能),额外引入 commons-pool2:
XML
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring Boot Redis 核心依赖(自动配置 Lettuce 客户端) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lettuce 连接池依赖(提升连接复用性,优化性能) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Spring Web(用于接口测试) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok(简化实体类代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
3. 核心配置(application.yml)
在 application.yml 中配置 Redis 连接信息、连接池参数,这是整合的关键步骤:
# 服务器配置
server:
port: 8080
servlet:
context-path: /demo
# Redis 配置
spring:
redis:
# 连接信息
host: 127.0.0.1 # Redis 服务器地址(本地/远程IP)
port: 6379 # Redis 端口号(默认6379)
password: "" # Redis 密码(若未设置密码,留空即可)
database: 0 # 操作的 Redis 数据库索引(默认0,共16个数据库:0-15)
timeout: 3000 # 连接超时时间(毫秒,超过该时间未连接成功则抛异常)
# Lettuce 连接池配置(核心优化,提升连接复用性)
lettuce:
pool:
max-active: 8 # 连接池最大活跃连接数(默认8,根据业务调整)
max-idle: 8 # 连接池最大空闲连接数(默认8)
min-idle: 2 # 连接池最小空闲连接数(默认0,建议设置2-5)
max-wait: -1 # 连接池最大等待时间(毫秒,-1表示无限制等待)
配置说明:
database:Redis 默认提供 16 个独立数据库(索引 0-15),可通过该配置指定操作的数据库,不同数据库的数据相互隔离;lettuce.pool:Lettuce 连接池参数,合理配置可减少连接创建 / 销毁的开销,提升性能;timeout:避免因 Redis 服务不可用导致应用线程阻塞,建议设置合理的超时时间。
4. 自定义 RedisTemplate 配置(解决序列化问题)
Spring Boot 自动配置的 RedisTemplate 默认使用 JdkSerializationRedisSerializer 序列化方式,存在两个问题:① 存储对象时会生成额外的序列化前缀,导致 Redis 客户端查看数据乱码;② 不支持 JSON 格式序列化,可读性差。因此,我们需要自定义 RedisTemplate,配置 Jackson 序列化方式:
java
package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 自定义配置类:解决序列化乱码问题
*/
@Configuration
public class RedisConfig {
/**
* 自定义 RedisTemplate<String, Object>
* 序列化方式:key 使用 String 序列化,value 使用 Jackson JSON 序列化
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1. 创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 2. 设置 Redis 连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 3. 创建 Jackson JSON 序列化器(解决 value 序列化乱码)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 配置 ObjectMapper:允许访问私有属性、添加类型信息(反序列化时识别对象类型)
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(objectMapper);
// 4. 创建 String 序列化器(解决 key 序列化乱码)
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// 5. 配置序列化方式
redisTemplate.setKeySerializer(stringSerializer); // key 序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key 序列化
redisTemplate.setValueSerializer(jacksonSerializer); // value 序列化
redisTemplate.setHashValueSerializer(jacksonSerializer); // Hash value 序列化
redisTemplate.afterPropertiesSet(); // 初始化配置
return redisTemplate;
}
}
补充 :StringRedisTemplate 是 Spring Boot 提供的另一个 Redis 操作类,默认使用 StringRedisSerializer 序列化方式,适用于存储 String 类型数据,无需额外配置,可直接注入使用。
三、基础用法:Redis 核心数据结构操作
Redis 支持多种数据结构,Spring Boot 通过 RedisTemplate 封装了对应的数据操作方法。以下是 5 种核心数据结构的实战用法,基于上述自定义 RedisTemplate 实现。
1. 实体类准备(用于对象存储测试)
java
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 用户实体类(用于 Redis 对象存储测试)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id; // 用户ID
private String username; // 用户名
private String email; // 邮箱
private LocalDateTime createTime; // 创建时间
}
2. String 类型:最常用的键值对存储
String 是 Redis 最基础的数据结构,适用于存储简单的键值对(如缓存用户信息、计数器、验证码等)。
java
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisStringService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 1. 存储 String 类型数据(普通键值对)
*/
public void setString(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 2. 存储 String 类型数据(带过期时间)
* @param key 键
* @param value 值
* @param timeout 过期时间
* @param timeUnit 时间单位(秒/分钟/小时)
*/
public void setStringWithExpire(String key, Object value, long timeout, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 3. 存储对象(通过 Jackson 序列化为 JSON 格式)
*/
public void setUser(String key, User user, long timeout, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, user, timeout, timeUnit);
}
/**
* 4. 获取 String 类型数据
*/
public Object getString(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 5. 获取对象(自动反序列化为 User 类型)
*/
public User getUser(String key) {
Object value = redisTemplate.opsForValue().get(key);
return value == null ? null : (User) value;
}
/**
* 6. 原子递增(计数器场景:点赞数、访问量)
*/
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 7. 原子递减
*/
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().decrement(key, delta);
}
/**
* 8. 判断键是否存在
*/
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 9. 删除指定键
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}
3. Hash 类型:适合存储对象属性(无需序列化整个对象)
Hash 是一种键值对的集合(类似 Java 中的 Map),适用于存储对象的部分属性,便于单独更新对象的某个字段,无需重新存储整个对象。
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class RedisHashService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 1. 存储 Hash 类型数据(单个字段)
*/
public void putHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
/**
* 2. 存储 Hash 类型数据(多个字段,批量插入)
*/
public void putAllHash(String key, Map<String, Object> hashMap) {
redisTemplate.opsForHash().putAll(key, hashMap);
}
/**
* 3. 获取 Hash 中单个字段的值
*/
public Object getHashValue(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
/**
* 4. 获取 Hash 中所有字段和值
*/
public Map<Object, Object> getAllHash(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 5. 删除 Hash 中指定字段
*/
public Long deleteHashField(String key, String... hashKeys) {
return redisTemplate.opsForHash().delete(key, (Object[]) hashKeys);
}
/**
* 6. 判断 Hash 中是否存在指定字段
*/
public boolean hasHashField(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* 7. Hash 字段值原子递增
*/
public Long incrHashValue(String key, String hashKey, long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}
}
4. List 类型:有序集合(支持双向操作,适用于消息队列、排行榜)
List 是有序的字符串列表,基于双向链表实现,支持从头部和尾部添加 / 删除元素,适用于实现简单的消息队列、最新消息列表等场景。
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RedisListService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 1. 从 List 尾部添加元素(右入队:消息队列生产者)
*/
public Long rPush(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 2. 从 List 头部添加元素(左入队)
*/
public Long lPush(String key, Object... values) {
return redisTemplate.opsForList().leftPushAll(key, values);
}
/**
* 3. 从 List 尾部弹出元素(右出队:消息队列消费者,弹出后元素删除)
*/
public Object rPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 4. 从 List 头部弹出元素(左出队,弹出后元素删除)
*/
public Object lPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 5. 获取 List 中指定范围的元素(分页查询,不删除元素)
* @param key 键
* @param start 起始索引(0 表示第一个元素)
* @param end 结束索引(-1 表示最后一个元素)
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 6. 获取 List 长度
*/
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}
/**
* 7. 删除 List 中指定数量的指定元素
*/
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
}
5. Set 类型:无序唯一集合(适用于去重、好友关系)
Set 是无序的字符串集合,元素具有唯一性(自动去重),支持交集、并集、差集等集合操作,适用于实现好友列表、标签去重、共同好友查询等场景。
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class RedisSetService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 1. 向 Set 中添加元素(自动去重)
*/
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* 2. 获取 Set 中所有元素
*/
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 3. 判断元素是否在 Set 中
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 4. 删除 Set 中指定元素
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 5. 获取两个 Set 的交集(共同好友、共同标签)
*/
public Set<Object> sIntersect(String key1, String key2) {
return redisTemplate.opsForSet().intersect(key1, key2);
}
/**
* 6. 获取两个 Set 的并集
*/
public Set<Object> sUnion(String key1, String key2) {
return redisTemplate.opsForSet().union(key1, key2);
}
/**
* 7. 获取 Set 长度
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
}
6. ZSet 类型:有序唯一集合(适用于排行榜、计分系统)
ZSet(Sorted Set)是有序的 Set 集合,每个元素都会关联一个分数(score),Redis 会根据分数对元素进行升序 / 降序排序,元素具有唯一性,适用于实现商品销量排行榜、用户积分排名、定时任务等场景。
java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class RedisZSetService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 1. 向 ZSet 中添加元素(指定分数)
*/
public Boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 2. 获取 ZSet 中指定元素的分数
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 3. 递增 ZSet 中指定元素的分数(排行榜加分)
*/
public Double zIncrementScore(String key, Object value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 4. 获取 ZSet 中指定排名范围的元素(降序:从高到低,排行榜常用)
* @param key 键
* @param start 起始排名(0 表示第一名)
* @param end 结束排名(-1 表示最后一名)
*/
public Set<Object> zRevRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 5. 获取 ZSet 中指定分数范围的元素(升序)
*/
public Set<Object> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 6. 获取 ZSet 长度
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 7. 删除 ZSet 中指定元素
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
}
四、进阶特性:企业级核心功能实战
1. Spring Cache 整合 Redis:注解式缓存开发
Spring Cache 提供了一套统一的缓存注解,与 Redis 整合后,可通过 @Cacheable、@CachePut、@CacheEvict 等注解快速实现缓存功能,无需手动调用 RedisTemplate,大幅简化缓存开发。
(1)开启缓存注解支持
在 Spring Boot 主启动类上添加 @EnableCaching 注解,开启缓存功能:
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 开启 Spring Cache 注解支持
public class RedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedisDemoApplication.class, args);
}
}
(2)注解式缓存实战
java
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* Spring Cache + Redis 注解式缓存实战
*/
@Service
public class UserCacheService {
/**
* @Cacheable:查询时缓存
* 原理:先从缓存中查询,若存在则直接返回;若不存在则执行方法,将结果存入缓存
* value:缓存名称(必填,相当于缓存前缀)
* key:缓存键(SpEL 表达式,#id 表示方法参数 id)
* unless:条件表达式,满足条件则不缓存(此处:用户为 null 时不缓存)
*/
@Cacheable(value = "userCache", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
// 模拟数据库查询(实际开发中替换为 MyBatis/MP 查询)
System.out.println("查询数据库,用户ID:" + id);
return new User(id, "张三" + id, "zhangsan" + id + "@example.com", LocalDateTime.now());
}
/**
* @CachePut:更新时缓存(方法一定会执行,执行后更新缓存)
* 适用场景:用户信息更新后,同步更新缓存,避免缓存与数据库数据不一致
*/
@CachePut(value = "userCache", key = "#user.id")
public User updateUser(User user) {
// 模拟数据库更新
System.out.println("更新数据库,用户ID:" + user.getId());
return user;
}
/**
* @CacheEvict:删除时缓存(删除数据库数据后,删除对应缓存)
* allEntries:是否删除该缓存名称下的所有缓存(默认 false,仅删除指定 key)
*/
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
// 模拟数据库删除
System.out.println("删除数据库,用户ID:" + id);
}
}
2. Redis 分布式锁:解决分布式并发问题
在分布式系统中,多个服务实例同时操作同一资源时(如库存扣减、订单创建),会出现并发问题,Redis 分布式锁基于 setIfAbsent(原子操作)实现,可有效解决该问题。
(1)分布式锁核心实现
java
package com.example.demo.service;
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.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Redis 分布式锁实战(解决死锁问题,保证原子性)
*/
@Service
public class RedisDistributedLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 锁前缀
private static final String LOCK_PREFIX = "distributed:lock:";
// 锁默认过期时间(避免死锁)
private static final long DEFAULT_LOCK_EXPIRE = 30L;
// 锁默认重试时间
private static final long DEFAULT_RETRY_TIME = 100L;
/**
* 获取分布式锁
* @param lockKey 锁标识(如:order:1001)
* @return 锁唯一标识(释放锁时需要)
*/
public String getLock(String lockKey) {
return getLock(lockKey, DEFAULT_LOCK_EXPIRE, TimeUnit.SECONDS, DEFAULT_RETRY_TIME);
}
/**
* 带参数的分布式锁获取
*/
public String getLock(String lockKey, long expireTime, TimeUnit timeUnit, long retryTime) {
// 生成唯一标识(避免误删其他线程的锁)
String lockValue = UUID.randomUUID().toString();
String key = LOCK_PREFIX + lockKey;
// 循环重试获取锁
while (true) {
// setIfAbsent:原子操作,只有当 key 不存在时才设置值,返回 true;否则返回 false
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, lockValue, expireTime, timeUnit);
if (Boolean.TRUE.equals(success)) {
return lockValue; // 获取锁成功,返回唯一标识
}
// 获取锁失败,休眠后重试
try {
Thread.sleep(retryTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}
/**
* 释放分布式锁(使用 Lua 脚本保证原子性:判断标识 + 删除锁一步完成)
* @param lockKey 锁标识
* @param lockValue 锁唯一标识
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String lockValue) {
if (lockValue == null) {
return false;
}
String key = LOCK_PREFIX + lockKey;
// Lua 脚本:判断当前锁的标识是否与传入的一致,一致则删除
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 执行 Lua 脚本
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), lockValue);
return result != null && result > 0;
}
/**
* 模拟库存扣减(使用分布式锁保证并发安全)
*/
public boolean deductStock(String productId, int num) {
String lockKey = "product:stock:" + productId;
String lockValue = null;
try {
// 1. 获取分布式锁
lockValue = getLock(lockKey);
if (lockValue == null) {
return false; // 获取锁失败
}
// 2. 模拟查询库存
String stockKey = "product:stock:count:" + productId;
Integer stock = (Integer) redisTemplate.opsForValue().get(stockKey);
if (stock == null || stock < num) {
return false; // 库存不足
}
// 3. 扣减库存
redisTemplate.opsForValue().set(stockKey, stock - num);
System.out.println("商品 " + productId + " 库存扣减成功,剩余库存:" + (stock - num));
return true;
} finally {
// 4. 释放分布式锁
if (lockValue != null) {
releaseLock(lockKey, lockValue);
}
}
}
}
核心亮点:
- 使用
setIfAbsent+ 过期时间:避免获取锁后服务宕机导致死锁; - 生成唯一
lockValue:避免误删其他线程获取的锁; - 使用 Lua 脚本:保证 "判断锁标识 + 删除锁" 的原子性,避免并发释放锁的问题。
3. Redis 管道(Pipeline):提升批量操作性能
Redis 管道允许客户端一次性发送多个命令,无需等待单个命令的响应,减少网络往返次数,大幅提升批量操作的性能(如批量插入、批量查询)。
java
/**
* Redis 管道批量操作
*/
public void batchOperation() {
// 执行管道操作
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
// 获取原生连接(Lettuce)
StringRedisSerializer stringSerializer = (StringRedisSerializer) redisTemplate.getKeySerializer();
Jackson2JsonRedisSerializer<Object> jacksonSerializer = (Jackson2JsonRedisSerializer<Object>) redisTemplate.getValueSerializer();
// 批量添加 1000 条数据
for (int i = 0; i < 1000; i++) {
String key = "batch:key:" + i;
User user = new User((long) i, "批量用户" + i, "batch" + i + "@example.com", LocalDateTime.now());
// 序列化 key 和 value
byte[] keyBytes = stringSerializer.serialize(key);
byte[] valueBytes = jacksonSerializer.serialize(user);
// 添加 set 命令到管道
connection.set(keyBytes, valueBytes);
}
return null;
});
System.out.println("批量操作完成,影响行数:" + results.size());
}
五、实战案例:用户缓存管理系统
1. 需求场景
实现一个用户缓存管理系统,包含以下功能:
- 缓存用户信息(String 类型,带过期时间);
- 更新用户信息时同步更新缓存;
- 删除用户信息时同步删除缓存;
- 批量查询用户信息(使用 Hash 类型优化);
- 防止缓存穿透(缓存空值)。
2. 核心代码实现
java
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.RedisStringService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/users")
public class UserCacheController {
@Autowired
private RedisStringService redisStringService;
// 缓存前缀
private static final String USER_CACHE_PREFIX = "user:info:";
/**
* 1. 获取用户信息(缓存优先,缓存不存在则查询数据库,并存入缓存)
*/
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
String cacheKey = USER_CACHE_PREFIX + id;
// 1. 先从缓存查询
User user = redisStringService.getUser(cacheKey);
if (user != null) {
return ResponseEntity.ok(user);
}
// 2. 缓存不存在,查询数据库(模拟)
user = new User(id, "张三" + id, "zhangsan" + id + "@example.com", LocalDateTime.now());
// 3. 存入缓存(设置 30 分钟过期时间),并缓存空值(防止缓存穿透)
redisStringService.setUser(cacheKey, user == null ? new User() : user, 30, TimeUnit.MINUTES);
return ResponseEntity.ok(user);
}
/**
* 2. 更新用户信息(同步更新缓存)
*/
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
String cacheKey = USER_CACHE_PREFIX + id;
// 1. 更新数据库(模拟)
// 2. 更新缓存(带 30 分钟过期时间)
redisStringService.setUser(cacheKey, user, 30, TimeUnit.MINUTES);
return ResponseEntity.ok(user);
}
/**
* 3. 删除用户信息(同步删除缓存)
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
String cacheKey = USER_CACHE_PREFIX + id;
// 1. 删除数据库(模拟)
// 2. 删除缓存
redisStringService.delete(cacheKey);
return ResponseEntity.noContent().build();
}
}
六、常见问题与解决方案
1. 序列化乱码问题
- 现象 :Redis 客户端查看数据时显示乱码(如
\xAC\xED\x00\x05t\x00\x03123); - 原因 :使用了 Spring Boot 默认的
JdkSerializationRedisSerializer序列化方式; - 解决方案 :自定义
RedisTemplate,配置StringRedisSerializer(key)和Jackson2JsonRedisSerializer(value),参考本文第三部分的RedisConfig类。
2. 缓存穿透问题
- 现象:恶意请求不存在的资源(如用户 ID=-1),导致请求直接穿透到数据库,造成数据库压力过大;
- 解决方案:① 缓存空值:查询结果为 null 时,存入空对象到缓存,并设置较短的过期时间;② 接口参数校验:拦截非法参数(如负数 ID);③ 使用布隆过滤器:提前过滤不存在的资源。
3. 缓存击穿问题
- 现象:某个热点缓存(如热门商品)过期瞬间,大量请求同时穿透到数据库;
- 解决方案:① 互斥锁:缓存过期时,只有一个线程去查询数据库,其他线程等待;② 缓存永不过期:定时更新缓存,不设置过期时间;③ 热点数据预热:系统启动时提前加载热点缓存。
4. 缓存雪崩问题
- 现象:大量缓存同时过期,导致大量请求穿透到数据库,造成数据库宕机;
- 解决方案:① 过期时间随机化:给每个缓存设置不同的过期时间(如基础过期时间 + 随机数);② 缓存集群:使用 Redis 集群,避免单个 Redis 节点宕机导致缓存全部失效;③ 服务熔断 / 降级:当数据库压力过大时,熔断部分非核心接口,返回默认值。
5. Redis 连接超时问题
- 现象 :应用启动时报
RedisConnectionTimeoutException,或运行中出现连接超时; - 解决方案 :① 检查 Redis 服务器地址、端口是否正确,Redis 服务是否启动;② 检查防火墙是否开放 Redis 端口;③ 优化 Redis 连接池参数(如
max-active、max-wait);④ 增加spring.redis.timeout配置。
七、总结
Spring Boot 与 Redis 的整合是企业级分布式项目的必备技能,本文从基础环境搭建、核心数据结构操作,到进阶的注解式缓存、分布式锁、管道技术,再到实战案例和问题排查,全面覆盖了开发中的核心需求。
核心要点回顾:
- 依赖配置:引入
spring-boot-starter-data-redis和commons-pool2,简化 Redis 集成; - 序列化配置:自定义
RedisTemplate,解决 key/value 序列化乱码问题; - 基础操作:掌握 String、Hash、List、Set、ZSet 五种核心数据结构的用法;
- 进阶功能:通过 Spring Cache 实现注解式缓存,通过 Redis 实现分布式锁,提升系统性能和并发安全性;
- 问题排查:针对缓存穿透、击穿、雪崩等常见问题,采用对应的解决方案,保障系统稳定运行。