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. 问题排查:针对缓存穿透、击穿、雪崩等常见问题,采用对应的解决方案,保障系统稳定运行。
相关推荐
廋到被风吹走2 小时前
【Spring】核心类研究价值排行榜
java·后端·spring
老华带你飞2 小时前
农产品销售管理|基于springboot农产品销售管理系统(源码+数据库+文档)
数据库·vue.js·spring boot
wanghowie2 小时前
01.05 Java基础篇|I/O、NIO 与序列化实战
java·开发语言·nio
孔明兴汉2 小时前
springboot4 项目从零搭建
java·java-ee·springboot
APIshop2 小时前
Java 爬虫 1688 评论 API 接口实战解析
java·开发语言·爬虫
编程乐学(Arfan开发工程师)2 小时前
信息收集与分析详解:渗透测试的侦察兵 (CISP-PTE 核心技能)
java·开发语言·javascript·python
Filotimo_2 小时前
在java开发中:JSON序列化和JSON反序列化
java·microsoft·json
czlczl200209252 小时前
SpringBoot实践:从验证码到业务接口的完整交互生命周期
java·spring boot·redis·后端·mysql·spring
Han_coding12082 小时前
从原理到实战:基于游标分页解决深分页问题(附源码方案)
java·服务器·数据库·spring boot·spring cloud·oracle