Spring Boot 使用 Redis 实战指南
- 发布时间:2026-05-01 06:02:42
- 本文热度:浏览 10 赞 0 评论 0
- 文章标签: Spring Boot Redis RedisTemplate
- 全文共1字,阅读约需1分钟
为什么在 Spring Boot 中使用 Redis
在 Spring Boot 项目里接入 Redis,通常不是为了“把数据再存一份”,而是为了利用 Redis 的高性能和丰富数据结构,解决下面几类问题:
- 缓存热点数据,减轻数据库压力
- 保存会话信息,支持分布式登录
- 实现分布式锁
- 做计数器、排行榜、限流
- 发布订阅、延迟任务、简单消息场景
如果只是“会用”,你可能只会 opsForValue().set()。但在实际项目里,更重要的是知道:Spring Boot 应该怎么配 Redis、怎么序列化、哪些场景适合、哪些坑最常见。
Spring Boot 集成 Redis 的基本方式
Spring Boot 使用 Redis,最常见的是通过 spring-boot-starter-data-redis,底层默认客户端通常是 Lettuce。
1. 引入依赖
Maven:
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Jackson,用于 JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
Gradle:
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'com.fasterxml.jackson.core:jackson-databind'
2. 配置 Redis 连接
application.yml:
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
如果是旧版本 Spring Boot,也可能使用:
spring:
redis:
host: 127.0.0.1
port: 6379
实际开发中要根据你项目所用的 Spring Boot 版本选择正确的配置前缀。
最常用的两种使用方式
Spring Boot 中操作 Redis,主要有两种思路:
- 直接使用
RedisTemplate - 使用 Spring Cache 注解
这两种都很常见,但定位不一样。
方式一:使用 RedisTemplate
RedisTemplate 更灵活,适合需要明确控制 key、value、过期时间、数据结构的场景。
1. 配置 RedisTemplate
默认的 RedisTemplate 序列化方式不够友好,常见问题是:
- key 可读性差
- value 被序列化成二进制
- 不同服务之间不方便互通
通常会自定义成:key 用字符串,value 用 JSON。
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.BasicPolymorphicTypeValidator;
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;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder().allowIfSubType(Object.class).build(),
ObjectMapper.DefaultTyping.NON_FINAL
);
jacksonSerializer.setObjectMapper(objectMapper);
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
2. 基本读写示例
package com.example.demo.service;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public void setWithExpire(String key, Object value, long seconds) {
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(seconds));
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}
3. 测试接口
package com.example.demo.controller;
import com.example.demo.service.RedisService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/redis")
public class RedisController {
private final RedisService redisService;
public RedisController(RedisService redisService) {
this.redisService = redisService;
}
@PostMapping("/set")
public String set(@RequestParam String key, @RequestParam String value) {
redisService.setWithExpire(key, value, 300);
return "ok";
}
@GetMapping("/get")
public Object get(@RequestParam String key) {
return redisService.get(key);
}
@DeleteMapping("/delete")
public Boolean delete(@RequestParam String key) {
return redisService.delete(key);
}
}
方式二:使用 Spring Cache + Redis
如果你的 Redis 主要是用来做缓存,那么直接用 Spring Cache 更省事。
1. 开启缓存
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 配置缓存管理器
package com.example.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializationContext.SerializationPair<String> keyPair =
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
RedisSerializationContext.SerializationPair<Object> valuePair =
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(new ObjectMapper())
);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(keyPair)
.serializeValuesWith(valuePair)
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
3. 使用缓存注解
package com.example.demo.service;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#id")
public String getUserById(Long id) {
System.out.println("查询数据库...");
return "user-" + id;
}
@CachePut(value = "userCache", key = "#id")
public String updateUser(Long id) {
System.out.println("更新数据库...");
return "updated-user-" + id;
}
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
System.out.println("删除数据库...");
}
}
4. 三个常用注解的区别
| 注解 | 作用 |
|---|---|
@Cacheable |
先查缓存,没有才执行方法并写入缓存 |
@CachePut |
执行方法,并强制刷新缓存 |
@CacheEvict |
删除缓存 |
在“查多改少”的业务里,这种方式比手写 RedisTemplate 更省代码。
Redis 常见数据结构在 Spring Boot 中的使用
Redis 不只是字符串缓存,它的数据结构很适合业务建模。
String:最常用
适合:
- 验证码
- token
- 简单对象缓存
- 计数器
redisTemplate.opsForValue().set("code:13800000000", "834521", 5, TimeUnit.MINUTES);
Hash:适合对象字段存储
适合:
- 用户信息片段
- 配置项
- 商品属性
redisTemplate.opsForHash().put("user:1001", "name", "Tom");
redisTemplate.opsForHash().put("user:1001", "age", 18);
Object name = redisTemplate.opsForHash().get("user:1001", "name");
List:适合消息队列、最新记录
redisTemplate.opsForList().leftPush("news:list", "article1");
redisTemplate.opsForList().leftPush("news:list", "article2");
Set:适合去重场景
redisTemplate.opsForSet().add("user:sign:202604", "1001", "1002", "1003");
ZSet:适合排行榜
redisTemplate.opsForZSet().add("rank:score", "zhangsan", 98);
redisTemplate.opsForZSet().add("rank:score", "lisi", 99);
var top = redisTemplate.opsForZSet().reverseRange("rank:score", 0, 9);
Spring Boot 使用 Redis 的典型场景
1. 查询缓存
最典型。比如商品详情、用户资料、配置数据。
常见模式:
- 查缓存
- 缓存未命中则查数据库
- 回写 Redis
- 设置过期时间
这类场景优先考虑 @Cacheable。
2. 验证码和短信码
这类数据时效性强,Redis 很适合。
redisTemplate.opsForValue().set("sms:code:13800000000", "123456", 300, TimeUnit.SECONDS);
3. 登录状态 / Token
可以把登录态写入 Redis,适合多实例部署。
例如:
login:token:xxx -> userInfo- 设置 30 分钟过期
- 用户活跃时刷新 TTL
4. 分布式锁
例如防止重复下单、定时任务重复执行。
简单写法:
Boolean success = redisTemplate.opsForValue()
.setIfAbsent("lock:order:1001", "1", 10, TimeUnit.SECONDS);
拿到锁后执行业务,结束后删除锁。
但生产环境里要注意:
- 不能无脑
delete - 需要校验锁的持有者
- 锁续期、异常释放都要考虑
复杂场景建议使用 Redisson,而不是手写。
5. 接口限流
可以基于 Redis 计数器做简单限流。
Long count = redisTemplate.opsForValue().increment("rate:user:1001");
if (count != null && count == 1) {
redisTemplate.expire("rate:user:1001", 60, TimeUnit.SECONDS);
}
if (count != null && count > 100) {
throw new RuntimeException("访问过于频繁");
}
表示某个用户 60 秒内最多请求 100 次。
实战中最容易踩的坑
1. 序列化问题
这是最常见的坑。
默认 JDK 序列化会导致:
- Redis 里内容不可读
- 跨语言服务不兼容
- 占用空间更大
建议:
- key 统一使用
StringRedisSerializer - value 优先考虑 JSON 序列化
2. 缓存穿透
查询一个根本不存在的数据,每次都会打到数据库。
解决方式:
- 缓存空值
- 布隆过滤器
- 参数合法性校验
示例:
@Cacheable(value = "userCache", key = "#id", unless = "#result == null")
public User getUser(Long id) {
return userMapper.selectById(id);
}
如果你希望把空结果也缓存,需要自己设计空对象或手工处理。
3. 缓存击穿
某个热点 key 在失效瞬间,大量请求同时打到数据库。
解决方式:
- 热点数据永不过期
- 互斥锁
- 逻辑过期 + 后台刷新
4. 缓存雪崩
大量 key 同时过期,数据库瞬间被压垮。
解决方式:
- 过期时间加随机值
- 多级缓存
- 熔断降级
例如:
int ttl = 600 + new Random().nextInt(300);
redisTemplate.opsForValue().set("product:1001", product, ttl, TimeUnit.SECONDS);
5. key 设计混乱
项目越大,越容易出现:
- key 冲突
- 难以排查
- 删除困难
建议统一规范:
业务:模块:主键
user:info:1001
order:detail:202604170001
sms:code:13800000000
lock:pay:order123
6. 大 key 问题
一个 key 里塞太多内容,比如:
- 一个 Hash 里几十万 field
- 一个 List 存几百万条
- 一个 String 存超大 JSON
这会影响:
- 网络传输
- Redis 阻塞
- 删除性能
建议拆分数据,避免超大对象。
RedisTemplate 和 Spring Cache 怎么选
适合 RedisTemplate 的场景
- 需要操作 Redis 多种数据结构
- 需要精确控制过期时间
- 需要做分布式锁、排行榜、限流
- 需要自定义复杂缓存逻辑
适合 Spring Cache 的场景
- 主要目的是“给方法结果做缓存”
- 业务模型简单
- 希望少写模板代码
- 重点是提升查询性能而不是玩 Redis 数据结构
可以这么理解:
- Spring Cache 更偏业务缓存抽象
- RedisTemplate 更偏 Redis 原生能力操作
一个更合理的项目实践建议
在实际项目里,通常不是二选一,而是组合使用:
- 查询缓存:用
Spring Cache - 验证码、分布式锁、排行榜、限流:用
RedisTemplate - 复杂分布式能力:考虑
Redisson
这种分层方式最常见,也最清晰。
推荐的工程化写法
为了避免在业务代码里到处写 Redis 细节,建议做一层封装。
例如:
package com.example.demo.component;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
private final RedisTemplate<String, Object> redisTemplate;
public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void set(String key, Object value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public Boolean setIfAbsent(String key, Object value, long timeout) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
}
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}
这样业务层只关心:
- 存什么
- 取什么
- 过期多久
而不是到处写重复的模板代码。
总结
Spring Boot 使用 Redis,本质上有两条主线:
- 把 Redis 当缓存用:优先考虑 Spring Cache
- 把 Redis 当数据结构服务用:优先考虑 RedisTemplate
真正决定实现方式的,不是“能不能连上 Redis”,而是你要解决什么问题:
- 只是缓存查询结果:用缓存注解最快
- 需要验证码、锁、限流、排行榜:直接上
RedisTemplate - 需要稳定的分布式锁和高级特性:考虑 Redisson
写 Spring Boot + Redis 代码时,最该关注的不是 API 会不会调,而是这几件事:
- 序列化是否合理
- key 设计是否规范
- 过期策略是否清晰
- 是否考虑缓存穿透、击穿、雪崩
- 是否避免大 key 和混乱数据结构
把这些问题处理好,Redis 才会真正成为性能工具,而不是故障来源。