SpringBoot与Redis实战:企业级缓存进阶指南

目录

[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:注解式缓存开发)

(1)开启缓存注解支持

(2)注解式缓存实战

[2. Redis 分布式锁:解决分布式并发问题](#2. Redis 分布式锁:解决分布式并发问题)

(1)分布式锁核心实现

[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 :通过 RedisTemplateStringRedisTemplate 封装了 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-activemax-wait);④ 增加 spring.redis.timeout 配置。

七、总结

Spring Boot 与 Redis 的整合是企业级分布式项目的必备技能,本文从基础环境搭建、核心数据结构操作,到进阶的注解式缓存、分布式锁、管道技术,再到实战案例和问题排查,全面覆盖了开发中的核心需求。

核心要点回顾:

  1. 依赖配置:引入 spring-boot-starter-data-rediscommons-pool2,简化 Redis 集成;
  2. 序列化配置:自定义 RedisTemplate,解决 key/value 序列化乱码问题;
  3. 基础操作:掌握 String、Hash、List、Set、ZSet 五种核心数据结构的用法;
  4. 进阶功能:通过 Spring Cache 实现注解式缓存,通过 Redis 实现分布式锁,提升系统性能和并发安全性;
  5. 问题排查:针对缓存穿透、击穿、雪崩等常见问题,采用对应的解决方案,保障系统稳定运行。
相关推荐
juniperhan1 小时前
Flink 系列第21篇:Flink SQL 函数与 UDF 全解读:类型推导、开发要点与 Module 扩展
java·大数据·数据仓库·分布式·sql·flink
ID_180079054731 小时前
Python 实现亚马逊商品详情 API 数据准确性校验(极简可用 + JSON 参考)
java·python·json
devpotato1 小时前
Spring Boot mTLS 报 `keystore password was incorrect`:不一定是密码错了
spring boot·tls·pkcs12·mtls
c++之路1 小时前
C++23概述
java·c++·c++23
专注API从业者2 小时前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠3 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY3 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克34 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠4 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌4 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包