原创

Spring Boot 使用 Redis:从入门配置到缓存实战

Redis 在 Spring Boot 里到底解决什么问题

在 Spring Boot 项目里接入 Redis,通常不是为了“把数据换个地方存”,而是为了处理三类非常具体的问题:

  1. 降低数据库压力:把热点数据放进缓存,减少重复查库。Spring Boot 可以直接把 Redis 作为 Spring Cache 的底层实现。(Home)
  2. 提升访问速度:用户资料、配置、会话、验证码、短期统计这类读多写少或允许短暂过期的数据,适合放 Redis。Redis 本身是高性能 key-value 存储,同时也常被用作缓存和消息中间层。(Home)
  3. 利用 Redis 数据结构做业务:除了简单 key-value,Redis 还支持 List、Set、ZSet、Hash 等结构,Spring Data Redis 对这些结构提供了直接操作封装。(Home)

如果你的业务核心是强事务、复杂关联查询、长期持久化,主存储仍然应该是 MySQL / PostgreSQL 一类关系型数据库;Redis 更适合做缓存、临时状态、分布式协调和高频读写场景的辅助存储

Spring Boot 集成 Redis 的核心组件

Spring Boot 使用 Redis,底层通常是这套关系:

  • Spring Boot 自动配置:负责根据配置文件创建连接工厂、模板类等。Spring Boot 官方文档明确提供了对 Redis 的自动配置支持。(Home)
  • Spring Data Redis:提供统一的 Redis API、Template、Repository、缓存支持。(Home)
  • Redis 驱动:Spring Data Redis 支持 LettuceJedis 两种 Java 客户端。(Home)

实际开发里,Spring Boot 3.x 项目一般直接使用 spring-boot-starter-data-redis,默认场景下最常见的是 Lettuce

先说结论:大多数项目该怎么选

1. 连接客户端选 Lettuce 还是 Jedis

Spring Data Redis 当前同时支持 LettuceJedis。(Home)

一般建议:

  • 默认选 Lettuce
  • 只有在你团队明确依赖 Jedis 生态、历史项目已经统一使用 Jedis 时,再继续用 Jedis

原因不是“谁绝对更先进”,而是 Spring Boot 现在对 Lettuce 的默认集成更顺手,常规场景配置更省心。

2. 业务对象怎么序列化

不要直接使用默认 JDK 序列化把 Java 对象丢进 Redis。 RedisTemplate 默认会使用 Java 序列化;官方 API 文档也明确说明了这一点。(Home)

生产环境更常见的做法是:

  • key 用 String
  • value 用 JSON 序列化
  • 明确控制缓存前缀、过期时间、空值缓存策略

这样做的直接好处:

  • Redis 里的值更容易排查
  • 跨服务更友好
  • 兼容性比 JDK 序列化更好
  • 避免类结构变更导致反序列化问题

3. 优先用哪种接入方式

Redis 在 Spring Boot 里常见有三种用法:

  • StringRedisTemplate:适合纯字符串、计数器、验证码、简单键值
  • RedisTemplate<K,V>:适合你要自己控制对象读写
  • @Cacheable / @CacheEvict / @CachePut:适合“方法结果缓存”

结论很简单:

  • 简单 key-value:用 StringRedisTemplate
  • 明确操作 Redis 数据结构:用 RedisTemplate
  • 只是想给查询结果加缓存:优先用 Spring Cache

Maven 依赖

<dependencies>
    <!-- Web,仅示例需要 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 可选:如果你要自己定义 JSON 序列化,一般项目本来就会有 Jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

最基本的配置

application.yml

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0
      timeout: 3s
      lettuce:
        pool:
          max-active: 16
          max-idle: 8
          min-idle: 0
          max-wait: 3s

server:
  port: 8080

Spring Boot 3.3 的 RedisProperties 官方 API 中可以看到,配置项包含客户端类型、集群、哨兵、Jedis、Lettuce 连接池等内容。(Home)

一个能直接落地的配置类

下面这份配置做了三件事:

  • 自定义 RedisTemplate<String, Object>
  • key 使用字符串序列化
  • value / hash value 使用 JSON 序列化
  • 配置统一的 RedisCacheManager
package com.example.demo.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // key 序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // value 序列化
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
        );
        GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                new GenericJackson2JsonRedisSerializer(objectMapper);

        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisSerializationContext.SerializationPair<String> keyPair =
                RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());

        RedisSerializationContext.SerializationPair<Object> valuePair =
                RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(keyPair)
                .serializeValuesWith(valuePair)
                .entryTtl(Duration.ofMinutes(30))
                .disableCachingNullValues();

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
}

StringRedisTemplate 做最简单的读写

这个场景适合验证码、token、短信限流、计数器。

package com.example.demo.service;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
public class VerifyCodeService {

    private final StringRedisTemplate stringRedisTemplate;

    public VerifyCodeService(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void saveCode(String phone, String code) {
        String key = "verify:code:" + phone;
        stringRedisTemplate.opsForValue().set(key, code, Duration.ofMinutes(5));
    }

    public String getCode(String phone) {
        String key = "verify:code:" + phone;
        return stringRedisTemplate.opsForValue().get(key);
    }

    public void deleteCode(String phone) {
        String key = "verify:code:" + phone;
        stringRedisTemplate.delete(key);
    }
}

对应接口

package com.example.demo.controller;

import com.example.demo.service.VerifyCodeService;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/code")
public class VerifyCodeController {

    private final VerifyCodeService verifyCodeService;

    public VerifyCodeController(VerifyCodeService verifyCodeService) {
        this.verifyCodeService = verifyCodeService;
    }

    @PostMapping("/send")
    public String send(@RequestParam String phone, @RequestParam String code) {
        verifyCodeService.saveCode(phone, code);
        return "ok";
    }

    @GetMapping("/get")
    public String get(@RequestParam String phone) {
        return verifyCodeService.getCode(phone);
    }

    @DeleteMapping("/delete")
    public String delete(@RequestParam String phone) {
        verifyCodeService.deleteCode(phone);
        return "deleted";
    }
}

RedisTemplate 保存对象

实体类

package com.example.demo.model;

import java.io.Serializable;

public class UserCacheDTO implements Serializable {

    private Long id;
    private String username;
    private String email;

    public UserCacheDTO() {
    }

    public UserCacheDTO(Long id, String username, String email) {
        this.id = id;
        this.username = username;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public UserCacheDTO setId(Long id) {
        this.id = id;
        return this;
    }

    public String getUsername() {
        return username;
    }

    public UserCacheDTO setUsername(String username) {
        this.username = username;
        return this;
    }

    public String getEmail() {
        return email;
    }

    public UserCacheDTO setEmail(String email) {
        this.email = email;
        return this;
    }
}

Service

package com.example.demo.service;

import com.example.demo.model.UserCacheDTO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
public class UserCacheService {

    private final RedisTemplate<String, Object> redisTemplate;

    public UserCacheService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void save(UserCacheDTO user) {
        String key = "user:profile:" + user.getId();
        redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(30));
    }

    public UserCacheDTO get(Long userId) {
        String key = "user:profile:" + userId;
        Object value = redisTemplate.opsForValue().get(key);
        return value == null ? null : (UserCacheDTO) value;
    }

    public void delete(Long userId) {
        String key = "user:profile:" + userId;
        redisTemplate.delete(key);
    }
}

这种写法的本质很直接: 你自己决定 key 命名、过期时间、读写逻辑和数据结构,适合业务有明显 Redis 设计诉求的场景。

更推荐的缓存写法:Spring Cache

如果你的目标只是“给查询结果加缓存”,不要一上来就自己写 redisTemplate.opsForValue()。 Spring Data Redis 提供了对 Spring Cache 抽象的实现,可以通过 RedisCacheManager 作为底层缓存管理器。(Home)

开启缓存后,业务代码可以非常直接

package com.example.demo.service;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cacheable(cacheNames = "user", key = "#userId")
    public String getUserById(Long userId) {
        System.out.println("query db...");
        return "user-" + userId;
    }

    @CachePut(cacheNames = "user", key = "#userId")
    public String updateUser(Long userId, String username) {
        System.out.println("update db...");
        return username;
    }

    @CacheEvict(cacheNames = "user", key = "#userId")
    public void deleteUser(Long userId) {
        System.out.println("delete db...");
    }
}

三个注解的职责要分清

  • @Cacheable:先查缓存,没有才执行方法,并把结果放入缓存
  • @CachePut:一定执行方法,并把返回值更新到缓存
  • @CacheEvict:删除缓存

这是 Redis 在 Spring Boot 里最常见、也是最适合业务开发团队协作的接入方式。

实战里最容易踩的坑

1. 过期时间不设,缓存迟早失控

Redis 不是无限空间。 如果你把对象长期塞进去却不设置 TTL,结果通常只有两个:

  • 内存不断上涨
  • 线上排查时根本分不清哪些 key 还有效

结论很明确:绝大多数业务缓存都应该设置过期时间

2. key 命名混乱,后期运维很痛苦

推荐统一使用带业务前缀的命名方式,例如:

user:profile:1001
order:detail:20260001
verify:code:13800000000

不要写成:

1001
user1001
cache_key_1

好的 key 设计至少要满足:

  • 一眼看出业务含义
  • 支持按前缀筛查
  • 支持分环境、分模块隔离

3. 默认序列化不适合长期维护

RedisTemplate 默认使用 JDK 序列化。官方 API 文档已经明确说明默认行为,并建议字符串密集场景使用 StringRedisTemplate。(Home)

默认 JDK 序列化的问题不是“不能用”,而是:

  • Redis 可读性差
  • 类变更后兼容性差
  • 不利于跨服务共享
  • 排查问题时不直观

所以生产环境里通常会主动替换为 JSON 序列化。

4. 把 Redis 当数据库硬用

Redis 很适合缓存、计数、排行榜、会话、分布式锁等场景,但它不是关系型数据库替代品。 尤其是下面这些需求,不应该强行往 Redis 上套:

  • 多表关联
  • 复杂筛选和分页
  • 强事务一致性
  • 长期核心业务数据存储

5. 缓存更新策略没设计好,会读到脏数据

只接入 Redis 很简单,真正麻烦的是缓存和数据库的一致性策略。 最常见的两种方式:

Cache Aside(旁路缓存)

流程是:

  • 读:先查缓存,没命中再查数据库,并回填缓存
  • 写:先更新数据库,再删除缓存

这是最常用、最容易理解的一种策略。

Write Through / Write Behind

这类策略由缓存层参与写入路径,复杂度更高,普通 Spring Boot 业务系统里并不常作为第一选择。

大部分中后台项目,优先把 Cache Aside 用稳。

Redis 常见业务场景

1. 验证码

特点:

  • 生命周期短
  • 高并发读取
  • 必须带 TTL

适合用 StringRedisTemplate

2. 用户信息缓存

特点:

  • 热点读
  • 数据有明确主键
  • 可容忍短时间缓存延迟

适合用 @CacheableRedisTemplate

3. 购物车

特点:

  • 频繁更新
  • 可按用户隔离
  • 常需要 Hash 结构存储多个字段

适合用 RedisTemplate.opsForHash()

4. 排行榜

特点:

  • 需要排序
  • 需要按分值范围查询

适合用 Redis 的 ZSet

5. 分布式计数与限流

特点:

  • 原子递增
  • 通常要配合过期时间窗口

适合用 INCR 相关能力,通过 Spring Data Redis 也可以完成。

一个稍微完整一点的用户缓存示例

下面给一个典型模式:数据库查用户信息,Redis 只做缓存层。

模拟 DAO

package com.example.demo.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    public String findUsernameById(Long userId) {
        // 这里仅做演示,真实项目里应查询 MySQL
        return "db-user-" + userId;
    }
}

Service

package com.example.demo.service;

import com.example.demo.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserQueryService {

    private final UserRepository userRepository;

    public UserQueryService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Cacheable(cacheNames = "userName", key = "#userId")
    public String getUsername(Long userId) {
        return userRepository.findUsernameById(userId);
    }
}

Controller

package com.example.demo.controller;

import com.example.demo.service.UserQueryService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserQueryController {

    private final UserQueryService userQueryService;

    public UserQueryController(UserQueryService userQueryService) {
        this.userQueryService = userQueryService;
    }

    @GetMapping("/user/name")
    public String getUsername(@RequestParam Long userId) {
        return userQueryService.getUsername(userId);
    }
}

第一次访问会执行数据库查询,第二次开始会直接命中 Redis。 这类模式就是 Spring Boot 使用 Redis 最典型的落地方式。

版本区别要说清楚

Spring Boot 2.x 与 Spring Boot 3.x 的使用差异

1. JDK 基线不同

Spring Data Redis 3.1 文档明确要求 JDK 17+ 和 Spring Framework 6.x。(Home) 这意味着在 Spring Boot 3.x 体系中,你至少应以 JDK 17 为基础来使用当前主流版本。

2. 配置前缀要注意

在 Spring Boot 3.x 里,Redis 常见配置路径是:

spring:
  data:
    redis:

如果你项目还停留在部分旧版写法或旧博客示例,看到的是别的配置层级,就要按你实际 Spring Boot 版本核对,不要直接照抄。

3. 依赖兼容不要手工乱配

Spring Data 官方文档明确建议通过 Spring Data Release Train BOM 管理兼容版本。对于 Spring Boot 项目,更简单的做法是:直接跟随 Spring Boot 自带依赖管理,不要单独硬改 Spring Data Redis 版本。(Home)

4. 老博客里的序列化代码不一定还能直接照搬

很多旧文章使用的序列化配置、ObjectMapper 写法、甚至缓存配置 API,放到 Spring Boot 3.x / Spring Framework 6.x 环境里可能需要调整。 因此看示例时一定先确认文章对应版本。

连接模式不止单机

Spring Data Redis 官方文档明确给出了多种连接模式,包括:

  • Standalone(单机)
  • Master/Replica
  • Sentinel
  • Cluster

不同模式对应不同配置。(Home)

中小项目开发环境通常先用单机; 生产环境是否用 Sentinel 或 Cluster,要看你的高可用和容量要求,而不是“Redis 上线就必须集群”。

一套更靠谱的落地建议

如果你在做一个普通 Spring Boot 后端项目,比较稳妥的实践是:

  1. 先把 Redis 用在缓存,不要一开始就塞太多复杂业务状态
  2. 查询缓存优先用 Spring Cache
  3. 明确设置 TTL
  4. key 命名带业务前缀
  5. value 使用 JSON 序列化
  6. 热点接口要考虑缓存击穿、穿透、雪崩
  7. 更新数据库时配合删除缓存,避免脏数据
  8. 不要把 Redis 当关系型数据库替代品

小结

Spring Boot 使用 Redis,重点从来不在“怎么连上 Redis”,而在三件事:

  • 为什么要用 Redis
  • 用 Redis 解决哪类问题
  • 怎样把序列化、TTL、key 设计、缓存一致性这些基础问题一次做对

真正的实战经验差异,通常也不体现在 set/get 代码上,而体现在:

  • 你有没有明确缓存边界
  • 你有没有设计好失效策略
  • 你有没有控制住数据结构和 key 命名
  • 你有没有避免把 Redis 用成一个难以维护的“第二数据库”

把这些问题处理好,Spring Boot + Redis 才会真正成为项目性能优化工具,而不是后续故障源头。

正文到此结束
评论插件初始化中...
Loading...