在分布式系统开发中,Redis 凭借其高性能、多数据结构的特性,成为缓存、分布式锁、限流等场景的首选中间件。Spring Boot 作为主流的 Java 开发框架,通过自动配置机制简化了 Redis 的集成流程,让开发者无需关注复杂的底层实现,即可快速上手。本文将从环境准备、核心配置、API 实操、典型场景四个维度,详细讲解 Spring Boot 中 Redis 的使用方法,帮助开发者快速落地实战。
一、环境准备:搭建基础开发环境
1.1 安装 Redis 服务
Redis 支持 Windows、Linux、Mac 多平台部署,推荐使用 Docker 快速搭建(避免环境配置冲突):
- 拉取 Redis 镜像:
docker pull redis:6.2.6(选择稳定版) - 启动容器:
docker run -d -p 6379:6379 --name redis-demo redis --requirepass "123456"- 暴露 6379 端口,设置密码 123456,容器名称为 redis-demo
- 验证连接:使用 Redis 客户端执行
redis-cli -h localhost -p 6379 -a 123456,输入ping返回PONG即成功。
1.2 项目依赖配置
创建 Spring Boot 项目(推荐 2.7.x 版本,兼容性更优),在 pom.xml 中引入 Redis 核心依赖:
xml
<!-- Spring Boot Redis starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖(Spring Boot 2.x 默认使用 Lettuce) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
spring-boot-starter-data-redis包含 Redis 自动配置类和核心 APIcommons-pool2提供连接池支持,优化 Redis 连接性能- 若需切换为 Jedis 客户端,排除 Lettuce 依赖并引入 Jedis 即可。
二、核心配置:自定义 Redis 连接与序列化
2.1 基础连接配置
在 application.yml 中配置 Redis 连接信息,覆盖默认自动配置:
yaml
spring:
redis:
# 连接信息
host: localhost
port: 6379
password: 123456
database: 0 # 选择第 0 个数据库(Redis 默认 16 个数据库)
# 连接池配置(Lettuce)
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 2 # 最小空闲连接
max-wait: 1000ms # 连接等待超时时间
timeout: 5000ms # 命令执行超时时间
- 数据库索引
database用于隔离不同业务数据,避免键名冲突 - 连接池参数需根据业务压力调整,避免连接泄露或资源浪费
2.2 序列化配置(关键优化)
Spring Boot 默认使用 JdkSerializationRedisSerializer 序列化对象,存在可读性差、占用空间大的问题。推荐自定义序列化配置,使用 Jackson2JsonRedisSerializer 实现 JSON 序列化:
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;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// JSON 序列化配置
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 支持 Java 8 时间类型(LocalDateTime 等)
objectMapper.registerModule(new JavaTimeModule());
// 序列化时包含对象类型信息(避免反序列化失败)
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(objectMapper);
// 字符串序列化配置(键名使用 String 序列化)
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// 配置序列化方式
template.setKeySerializer(stringSerializer); // 键序列化
template.setValueSerializer(jacksonSerializer); // 值序列化
template.setHashKeySerializer(stringSerializer); // 哈希键序列化
template.setHashValueSerializer(jacksonSerializer); // 哈希值序列化
template.afterPropertiesSet();
return template;
}
}
- 键名使用
StringRedisSerializer,确保键名可读性 - 值使用 JSON 序列化,支持复杂对象和时间类型,且序列化后的数据可直接通过 Redis 客户端查看
- 若仅需操作字符串类型,可直接使用
StringRedisTemplate(默认 String 序列化,无需额外配置)
三、API 实操:Redis 核心数据结构操作
Spring Boot 提供 RedisTemplate 和 StringRedisTemplate 两大核心 API,前者支持任意类型对象,后者专注字符串操作。以下结合 Redis 五大核心数据结构,讲解常用操作。
3.1 字符串(String):最基础的键值存储
字符串是 Redis 最常用的数据结构,适用于缓存单个对象、计数器等场景:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class RedisStringTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
public void testStringOps() {
// 1. 存入键值对(无过期时间)
redisTemplate.opsForValue().set("user:name", "张三");
// 2. 存入键值对(设置 1 小时过期)
redisTemplate.opsForValue().set("user:age", 25, 1, TimeUnit.HOURS);
// 3. 获取值
String name = (String) redisTemplate.opsForValue().get("user:name");
Integer age = (Integer) redisTemplate.opsForValue().get("user:age");
System.out.println("姓名:" + name + ",年龄:" + age);
// 4. 自增(计数器场景)
redisTemplate.opsForValue().increment("article:view:1001", 1); // 阅读量 +1
Long viewCount = (Long) redisTemplate.opsForValue().get("article:view:1001");
System.out.println("文章 1001 阅读量:" + viewCount);
// 5. 批量操作
redisTemplate.opsForValue().multiSet(new HashMap<String, Object>() {{
put("user:gender", "男");
put("user:city", "北京");
}});
List<Object> multiGet = redisTemplate.opsForValue().multiGet(Arrays.asList("user:gender", "user:city"));
System.out.println("批量获取结果:" + multiGet);
}
}
3.2 哈希(Hash):适合存储对象属性
哈希结构以键值对集合形式存储数据,适用于缓存对象的多个属性(如用户信息、商品详情),支持单独修改某个属性:
@Test
public void testHashOps() {
// 1. 存入哈希数据(用户 ID 为 1001 的信息)
HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
hashOps.put("user:1001", "name", "李四");
hashOps.put("user:1001", "age", 30);
hashOps.put("user:1001", "email", "lisi@xxx.com");
// 2. 获取单个属性
String email = (String) hashOps.get("user:1001", "email");
System.out.println("用户 1001 邮箱:" + email);
// 3. 获取所有属性
Map<Object, Object> userMap = hashOps.entries("user:1001");
System.out.println("用户 1001 完整信息:" + userMap);
// 4. 批量存入属性
Map<Object, Object> newAttrs = new HashMap<>();
newAttrs.put("gender", "男");
newAttrs.put("city", "上海");
hashOps.putAll("user:1001", newAttrs);
// 5. 删除某个属性
hashOps.delete("user:1001", "email");
}
3.3 列表(List):有序集合,支持队列 / 栈操作
Redis 列表是有序的字符串集合,基于双向链表实现,适用于消息队列、最新消息排行等场景:
@Test
public void testListOps() {
ListOperations<String, Object> listOps = redisTemplate.opsForList();
String key = "message:queue";
// 1. 左进(队列:先进先出)
listOps.leftPush(key, "消息1");
listOps.leftPush(key, "消息2");
listOps.leftPush(key, "消息3");
// 2. 右出(获取并移除队尾元素)
Object msg1 = listOps.rightPop(key);
Object msg2 = listOps.rightPop(key);
System.out.println("消费消息:" + msg1 + "," + msg2);
// 3. 获取列表范围(0 到 -1 表示所有元素)
List<Object> allMsg = listOps.range(key, 0, -1);
System.out.println("剩余消息:" + allMsg);
// 4. 栈操作(左进左出)
listOps.leftPush("stack", "元素A");
listOps.leftPush("stack", "元素B");
Object stackMsg1 = listOps.leftPop("stack");
System.out.println("栈弹出元素:" + stackMsg1);
// 5. 设置列表长度
listOps.trim(key, 0, 0); // 只保留第一个元素
}
3.4 集合(Set):无序去重集合
Set 是无序、不重复的字符串集合,支持交集、并集、差集运算,适用于标签、好友关系等场景:
@Test
public void testSetOps() {
SetOperations<String, Object> setOps = redisTemplate.opsForSet();
// 1. 向集合添加元素
setOps.add("user:tags:1001", "Java", "Redis", "Spring Boot");
setOps.add("user:tags:1002", "Java", "MySQL", "Docker");
// 2. 获取集合所有元素
Set<Object> tags1001 = setOps.members("user:tags:1001");
System.out.println("用户 1001 标签:" + tags1001);
// 3. 交集(共同标签)
Set<Object> commonTags = setOps.intersect("user:tags:1001", "user:tags:1002");
System.out.println("共同标签:" + commonTags);
// 4. 并集(所有标签)
Set<Object> allTags = setOps.union("user:tags:1001", "user:tags:1002");
System.out.println("所有标签:" + allTags);
// 5. 差集(用户 1001 独有的标签)
Set<Object> diffTags = setOps.difference("user:tags:1001", "user:tags:1002");
System.out.println("用户 1001 独有标签:" + diffTags);
// 6. 移除元素
setOps.remove("user:tags:1001", "Redis");
}
3.5 有序集合(ZSet):带分数的有序集合
ZSet 是有序集合,每个元素关联一个分数(score),按分数排序,适用于排行榜、限流等场景:
@Test
public void testZSetOps() {
ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
String key = "article:rank";
// 1. 添加元素(分数为文章阅读量)
zSetOps.add(key, "文章1001", 100);
zSetOps.add(key, "文章1002", 200);
zSetOps.add(key, "文章1003", 150);
// 2. 按分数升序排列(0 到 -1 表示所有元素)
Set<Object> ascRank = zSetOps.range(key, 0, -1);
System.out.println("阅读量升序排行:" + ascRank);
// 3. 按分数降序排列(排行榜常用)
Set<Object> descRank = zSetOps.reverseRange(key, 0, -1);
System.out.println("阅读量降序排行:" + descRank);
// 4. 增加元素分数(阅读量 +50)
zSetOps.incrementScore(key, "文章1001", 50);
// 5. 获取元素分数
Double score = zSetOps.score(key, "文章1001");
System.out.println("文章1001 最新阅读量:" + score);
// 6. 获取元素排名(降序,从 0 开始)
Long rank = zSetOps.reverseRank(key, "文章1001");
System.out.println("文章1001 排名:第" + (rank + 1) + "名");
}
四、典型场景落地:从理论到实践
4.1 缓存实现(@Cacheable 注解)
Spring Cache 整合 Redis 可快速实现缓存功能,无需手动调用 RedisTemplate:
-
在启动类添加
@EnableCaching开启缓存支持 -
在 Service 方法上添加缓存注解:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class UserService {// 查询用户时缓存结果,key 为 "user:id:{id}" @Cacheable(value = "user", key = "'user:id:' + #id", unless = "#result == null") public User getUserById(Long id) { // 模拟数据库查询(实际开发中替换为 DAO 操作) System.out.println("查询数据库,用户 ID:" + id); return new User(id, "张三", 25, "北京"); } // 更新用户时删除缓存 @CacheEvict(value = "user", key = "'user:id:' + #user.id") public void updateUser(User user) { // 模拟数据库更新 System.out.println("更新数据库,用户:" + user); }}
@Cacheable:查询时先查缓存,缓存不存在则执行方法并缓存结果@CacheEvict:更新 / 删除数据时删除对应缓存,避免缓存不一致value为缓存名称,key为缓存键名(支持 SpEL 表达式)
4.2 分布式锁(基于 Redis 实现)
分布式系统中,使用 Redis 可实现简单高效的分布式锁,解决并发抢占资源问题:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 锁的过期时间(避免死锁)
private static final long LOCK_EXPIRE = 30000;
// 锁的等待时间(避免长时间阻塞)
private static final long LOCK_WAIT = 5000;
/**
* 获取分布式锁
*/
public boolean tryLock(String lockKey, String lockValue) {
long start = System.currentTimeMillis();
try {
while (true) {
// 使用 SET NX EX 命令原子性获取锁
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
if (success != null && success) {
return true; // 获取锁成功
}
// 等待一段时间后重试
Thread.sleep(50);
// 超过等待时间则放弃
if (System.currentTimeMillis() - start > LOCK_WAIT) {
return false;
}
}
} catch (InterruptedException e) {
return false;
}
}
/**
* 释放分布式锁(使用 Lua 脚本保证原子性)
*/
public boolean releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
return result != null && result > 0;
}
}
- 使用
setIfAbsent命令(NX + EX)原子性获取锁,避免并发问题 - 释放锁时通过 Lua 脚本验证锁值,确保只有锁持有者能释放
- 锁设置过期时间,防止服务宕机导致死锁
4.3 接口限流(令牌桶算法)
基于 Redis ZSet 可实现令牌桶限流,控制接口单位时间内的访问次数:
j
@Component
public class RedisRateLimiter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 限流判断
* @param key 限流键(如用户 ID、接口路径)
* @param limit 单位时间内最大访问次数
* @param period 时间周期(秒)
* @return 是否允许访问
*/
public boolean isAllowed(String key, int limit, int period) {
String redisKey = "rate:limiter:" + key;
long now = System.currentTimeMillis();
ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
// 1. 删除过期的令牌
zSetOps.removeRangeByScore(redisKey, 0, now - period * 1000);
// 2. 统计当前令牌数
Long count = zSetOps.zCard(redisKey);
if (count != null && count < limit) {
// 3. 新增令牌(分数为当前时间戳)
zSetOps.add(redisKey, now, now);
// 4. 设置键过期时间(避免垃圾数据)
redisTemplate.expire(redisKey, period, TimeUnit.SECONDS);
return true;
}
return false;
}
}
- 每个访问请求对应一个令牌,存储在 ZSet 中(分数为时间戳)
- 每次请求前删除过期令牌,统计当前令牌数是否超过限制
- 适用于接口限流、短信发送频率控制等场景
五、注意事项与优化建议
- 缓存一致性:更新数据库后需及时删除对应缓存,或使用延迟双删策略,避免缓存脏数据
- 序列化问题 :确保所有存储到 Redis 的对象实现
Serializable接口,或使用 JSON 序列化(如本文配置) - 连接池优化:根据业务并发量调整连接池参数,避免连接数不足导致阻塞
- 缓存穿透 / 击穿 / 雪崩 :
- 穿透:使用布隆过滤器过滤无效键,或缓存空值
- 击穿:热点 key 设置永不过期,或使用互斥锁
- 雪崩:缓存过期时间添加随机值,避免同时过期
- Redis 集群:生产环境建议使用 Redis 集群(主从 + 哨兵或 Redis Cluster),提高可用性
总结
Spring Boot 与 Redis 的整合核心在于 RedisTemplate 的配置与使用,通过自动配置机制大幅降低了集成门槛。本文从环境搭建、配置优化、API 实操到场景落地,全面覆盖了 Redis 在 Spring Boot 中的核心用法,包括字符串、哈希、列表等数据结构操作,以及缓存、分布式锁、限流等典型场景。
实际开发中,需根据业务需求选择合适的 API 和数据结构,同时关注缓存一致性、性能优化等问题。Redis 的功能远不止于此,后续还可探索 Redis 持久化、哨兵机制、Lua 脚本等高级特性,进一步提升系统性能与稳定性。