当前位置: 首页 > news >正文

SpringBoot 教程(十四) SpringBoot之集成 Redis(优化版)

SpringBoot 教程(十四)| SpringBoot 之集成 Redis

  • 一、开篇:Redis 集成核心认知与前置条件
    • 1.1 为什么要集成 Redis?
    • 1.2 主流 Redis 客户端选型指南
    • 1.3 集成流程总览
    • 1.4 前置条件
  • 二、第一步:添加依赖(按客户端选型)
    • 2.1 选型 1:默认集成 Lettuce(推荐基础场景)
    • 2.2 选型 2:切换集成 Jedis(需排除 Lettuce)
    • 2.3 选型 3:集成 Redisson(分布式场景推荐)
  • 三、第二步:配置 Redis 连接(application.yml)
    • 3.1 基础配置:单机模式(Lettuce/Jedis 通用)
      • 连接池生效验证方法(新手友好版)
    • 3.2 进阶配置:集群模式(Lettuce/Jedis 通用)
    • 3.3 特殊配置:Redisson 独立配置
  • 四、第三步:序列化配置(按客户端分别实现)
    • 4.1 核心问题:为什么要处理序列化?
    • 4.2 场景 1:Lettuce/Jedis 序列化配置(必须自定义)
    • 4.3 场景 2:Redisson 序列化配置(按需补充)
    • 4.4 三种客户端序列化效果对比
  • 五、第四步:基础操作:RedisTemplate 与 RedissonClient 上手
    • 5.1 实体类准备(示例用)
    • 5.2 统一返回结果类(Result.java)
    • 5.3 RedisTemplate 基础操作(Lettuce/Jedis 通用)
    • 5.4 RedissonClient 基础操作(分布式场景)
  • 六、第五步:进阶场景:分布式锁实现
    • 6.1 方案 1:基于 RedisTemplate 实现分布式锁
      • 6.1.1 分布式锁工具类(RedisTemplate 版)
      • 6.1.2 业务示例:库存扣减(RedisTemplate 锁)
    • 6.2 方案 2:基于 Redisson 实现分布式锁
      • 6.2.1 分布式锁工具类(Redisson 版,可选)
      • 6.2.2 业务示例:库存扣减(Redisson 锁)
  • 七、总结:选型指南与生产避坑
    • 7.1 客户端选型终极建议
    • 7.2 生产环境避坑 5 大要点

一、开篇:Redis 集成核心认知与前置条件

1.1 为什么要集成 Redis?

Redis 是 Java 开发中高频 NoSQL 内存数据库,以 Key-Value 键值对存储为核心,解决三大核心业务场景:

  • 缓存:缓存商品详情、用户信息等高频访问数据,减轻 MySQL 数据库压力;

  • 分布式锁:解决微服务多实例并发冲突(如库存扣减、订单防重提交);

  • 计数器 / 自增序列:生成唯一订单号、统计文章阅读量、接口限流等。

1.2 主流 Redis 客户端选型指南

Spring Boot 生态中三类核心 Redis 客户端,按业务场景精准匹配:

客户端核心特性线程安全序列化默认行为适用场景
LettuceSpring Boot 2.x + 默认客户端、基于 Netty 异步非阻塞、原生支持哨兵 / 集群默认 JDK 序列化(Key 乱码,需自定义 Jackson 序列化)绝大多数基础场景(单体 / 微服务基础缓存)
Jedis同步 API 直观易懂、调试简单、需通过连接池管理实例需连接池管理默认 JDK 序列化(Key 乱码,需自定义 Jackson 序列化)小型单体项目、简单同步操作场景
RedissonRedis 官方推荐分布式客户端、封装自动锁续期(看门狗)、支持读写锁 / 红锁默认 Jackson 序列化(Key 无乱码,特殊场景需补充配置)微服务分布式场景(秒杀、跨服务并发控制)

1.3 集成流程总览

部署 Redis 服务端 → 选择客户端并添加依赖 → 配置连接信息(含客户端专属配置) → 处理序列化(按需,不同客户端差异直接说明) → 基础操作开发 → 进阶场景实现

其中序列化处理差异:

  • Lettuce/Jedis:默认 JDK 序列化会导致 Key 乱码、Value 二进制不可读,必须自定义 Jackson 序列化(解决乱码 + 支持对象 / 时间类型存储);
  • Redisson:默认已用 Jackson 序列化(Key 清晰、Value 为 JSON),大部分场景无需额外配置,仅特殊场景(如 JDK8 时间类型、跨语言兼容)需补充配置。

1.4 前置条件

已部署 Redis 服务端(本地或云服务器,推荐 Redis 6.x+),确保:

  1. Redis 服务正常运行,IP、端口(默认 6379)、密码正确;
  2. 服务器防火墙开放 6379 端口(云服务器需配置安全组);
  3. 客户端与 Redis 版本兼容(Jedis 3.8.0 兼容 Redis 6.x+,Redisson 3.25.2 兼容 Redis 6.x+)。

二、第一步:添加依赖(按客户端选型)

2.1 选型 1:默认集成 Lettuce(推荐基础场景)

Spring Boot 2.x + 默认内置 Lettuce,无需额外排除依赖,完整 POM 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lsqingfeng.springboot</groupId><artifactId>springboot-learning</artifactId><version>1.0.0</version><!-- 依赖管理:统一Spring Boot版本 --><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.6.2</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- 1. Spring Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 2. 数据库相关 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 3. 工具类 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><!-- 4. Redis核心依赖(默认集成Lettuce) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 连接池依赖:必须添加!否则 lettuce.pool 配置不生效 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency></dependencies>
</project>

2.2 选型 2:切换集成 Jedis(需排除 Lettuce)

若业务需使用 Jedis 客户端,需先排除默认 Lettuce 依赖,再引入 Jedis 和连接池依赖:

<!-- 1. 排除默认Lettuce,保留Spring Data Redis基础能力 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency><!-- 2. 引入Jedis客户端(版本3.8.0,兼容Redis 6.x+) -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.8.0</version>
</dependency><!-- 3. 连接池依赖:Jedis必须添加,否则 jedis.pool 配置不生效 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

2.3 选型 3:集成 Redisson(分布式场景推荐)

Redisson 可与 Lettuce/Jedis 共存,直接引入 Starter 实现自动配置,依赖如下:

<!-- 1. Redisson Starter(自动配置RedissonClient,兼容Spring Boot 2.x/3.x) -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.25.2</version> <!-- 兼容Redis 6.x+,建议使用最新稳定版 -->
</dependency><!-- 2. 若需同时使用RedisTemplate,补充以下依赖(仅用Redisson可省略) -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

三、第二步:配置 Redis 连接(application.yml)

3.1 基础配置:单机模式(Lettuce/Jedis 通用)

保留数据库、热部署、第三方配置,补充 Redis 核心连接配置:

server:port: 19191 # 项目端口spring:# 1. 数据库配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springboot_learning?serverTimezone=Asia/Shanghai&characterEncoding=utf-8username: rootpassword: root# 2. 热部署配置devtools:restart:enable: true# 3. Redis核心配置(Lettuce示例,切换Jedis则将lettuce改为jedis)redis:host: localhost          # Redis服务地址(本地填127.0.0.1,服务器填公网IP)port: 6379               # Redis默认端口password: 123456         # Redis密码(无密码则删除此配置)database: 0              # 数据库索引(0-15,默认0)timeout: 3000ms          # 连接超时时间(单位:ms,避免无限等待)lettuce:                 # 客户端专属连接池配置(Jedis用jedis: {})pool:max-active: 32       # 最大活跃连接数(根据QPS调整,默认8)max-idle: 16         # 最大空闲连接数(建议≤max-active)min-idle: 8          # 最小空闲连接数(避免频繁创建连接)max-wait: -1ms       # 最大等待时间(-1表示无限制,单位:ms)

连接池生效验证方法(新手友好版)

  1. 在 Controller 中注入 RedisTemplate,在任意接口方法内打 Debug 断点(如后续 5.2 中的 saveUser 方法);
  2. 启动项目,访问接口触发断点(如访问 http://localhost:19191/redis/template/saveUser);
  3. 查看 Debug 面板:redisTemplate → connectionFactory(Lettuce 对应 LettuceConnectionFactory,Jedis 对应 JedisConnectionFactory)→ poolConfig;
  4. 若 poolConfig 显示 maxTotal=32、maxIdle=16 等配置,说明连接池生效;若为 null,检查是否遗漏 commons-pool2 依赖。

3.2 进阶配置:集群模式(Lettuce/Jedis 通用)

适用于 Redis 集群部署场景(至少 3 主 3 从),配置需包含所有主从节点:

spring:redis:password: 123456         # 集群统一密码(无密码则删除)cluster:nodes:                 # 集群节点列表(格式:IP:端口,需包含所有主从节点)- 192.168.1.10:7001- 192.168.1.10:7002- 192.168.1.10:7003- 192.168.1.10:7004- 192.168.1.10:7005- 192.168.1.10:7006max-redirects: 3       # 最大重定向次数(默认 3,避免循环跳转)# 连接池配置(Lettuce 示例,Jedis 替换为 jedis: {})lettuce:pool:max-active: 64       # 集群场景建议增大连接数(默认 8)max-idle: 32min-idle: 16max-wait: -1ms

3.3 特殊配置:Redisson 独立配置

Redisson 需单独配置连接信息,支持单机 / 集群模式,且默认已集成 Jackson 序列化(无需额外处理 Key 乱码),(与 Lettuce/Jedis 配置不冲突):

spring:redis:redisson:# 1. 单机模式(常用场景)singleServerConfig:address: redis://localhost:6379  # 格式:redis://IP:端口(必填)password: 123456                 # 与 Redis 服务密码一致database: 0                      # 数据库索引(默认 0)connectionPoolSize: 64           # 连接池大小(默认 64)lockWatchdogTimeout: 30000       # 锁自动续期时间(默认 30s,看门狗机制核心参数)# 2. 集群模式(替换单机配置,适用于 Redis 集群)# clusterServersConfig:#   nodeAddresses:#     - redis://192.168.1.10:7001#     - redis://192.168.1.10:7002#   password: 123456#   scanInterval: 2000ms            # 集群节点扫描间隔(默认 2000ms)

四、第三步:序列化配置(按客户端分别实现)

4.1 核心问题:为什么要处理序列化?

  • Lettuce/Jedis:默认 JDK 序列化(JdkSerializationRedisSerializer)会导致 Key 乱码(如 user:1 变成 \xAC\xED\x00\x05t\x00\x03user:1)、Value 为二进制不可读,且仅支持实现 Serializable 的对象;
  • Redisson:默认 Jackson 序列化(JacksonJsonRedisSerializer),Key 清晰、Value 为 JSON,但特殊场景(如 JDK8 时间类型、自定义序列化规则)需补充配置。

4.2 场景 1:Lettuce/Jedis 序列化配置(必须自定义)

需通过配置类替换默认序列化器,支持对象、JDK8 时间类型存储,代码如下:

RedisConfig.java(Lettuce/Jedis 通用序列化配置)

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 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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 序列化配置类:解决 Lettuce/Jedis 的 Key 乱码+对象序列化问题(生产环境必用)*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 1. 创建 RedisTemplate 实例RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 2. 配置 Key 序列化器:StringRedisSerializer(避免 Key 乱码)StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringRedisSerializer);       // 普通 Key 序列化redisTemplate.setHashKeySerializer(stringRedisSerializer);   // Hash 结构的 Key 序列化// 3. 配置 Value 序列化器:Jackson2JsonRedisSerializer(支持对象存储)Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();// 3.1 解决类型推断问题:替代过时的 enableDefaultTyping(旧方法存在反序列化漏洞)objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,  // 安全的类型校验器ObjectMapper.DefaultTyping.NON_FINAL,   // 非 final 类支持类型推断(如 User)com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY  // 类型信息嵌入 JSON(生成 @class 字段));// 3.2 配置字段可见性:所有字段(包括 private)均可序列化objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 3.3 解决 JDK8 时间类型(LocalDateTime/LocalDate)序列化问题objectMapper.registerModule(new JavaTimeModule());  // 显式注册时间模块(兼容旧版本 Jackson)jacksonSerializer.setObjectMapper(objectMapper);redisTemplate.setValueSerializer(jacksonSerializer);     // 普通 Value 序列化redisTemplate.setHashValueSerializer(jacksonSerializer); // Hash 结构的 Value 序列化// 4. 初始化模板配置(必须调用,否则序列化配置不生效)redisTemplate.afterPropertiesSet();return redisTemplate;}
}

4.3 场景 2:Redisson 序列化配置(按需补充)

Redisson 默认已实现 Jackson 序列化,大部分场景无需额外配置,仅以下场景需自定义:

  1. 需支持 JDK8 时间类型(LocalDateTime/LocalDate);
  2. 需自定义 JSON 序列化规则(如字段命名策略);
  3. 跨语言场景(需统一序列化格式)。

RedissonConfig.java(Redisson 自定义序列化配置)

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.redisson.codec.Jackson2JsonCodec;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Redisson 序列化配置类:仅特殊场景需配置(如支持 LocalDateTime)*/
@Configuration
public class RedissonConfig {@Beanpublic RedissonAutoConfigurationCustomizer redissonAutoConfigurationCustomizer() {// 1. 自定义 ObjectMapper,支持 JDK8 时间类型ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(new JavaTimeModule());  // 注册时间模块// 2. 使用自定义 ObjectMapper 初始化 Jackson 序列化器Jackson2JsonCodec jacksonCodec = new Jackson2JsonCodec(objectMapper);// 3. 将自定义序列化器注入 Redisson 配置return config -> config.setCodec(jacksonCodec);}
}

4.4 三种客户端序列化效果对比

对比维度Lettuce/Jedis(默认 JDK 序列化)Lettuce/Jedis(自定义 Jackson)Redisson(默认 / 自定义 Jackson)
Key 显示\xAC\xED\x00\x05t\x00\x03user:1user:1(清晰可读)user:1(清晰可读)
Value 存储二进制数据(不可直接查看)JSON 字符串(如 {“id”:1,“name”:“张三”})JSON 字符串(如 {“id”:1,“name”:“张三”})
支持类型仅实现 Serializable 的对象常用类型(String、自定义对象、JDK8 时间)常用类型(String、自定义对象、JDK8 时间)
跨语言兼容性差(仅 Java 可解析)好(JSON 格式)好(JSON 格式)
配置必要性必须配置(否则乱码)必须配置(否则乱码)默认无需配置,特殊场景按需配置

五、第四步:基础操作:RedisTemplate 与 RedissonClient 上手

解释说明:RedisTemplate工具类 是 Spring框架对Jedis和Lettuce客户端的封装,用它可以更方便的在Spring项目里面操作Redis,引入 “spring-boot-starter-data-redis” 依赖,就可以用这个RedisTemplate工具类了。

5.1 实体类准备(示例用)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;/*** 用户实体类(无需实现 Serializable,Jackson 序列化已兼容)*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Long id;private String name;private Integer age;private String email;private LocalDateTime createTime; // JDK8 时间类型(验证序列化兼容性)
}

5.2 统一返回结果类(Result.java)

接口返回统一格式,避免零散返回值,示例中已依赖此类:

import lombok.Data;/*** 统一接口返回结果类*/
@Data
public class Result<T> {// 状态码:200=成功,500=失败private Integer code;// 提示消息private String msg;// 返回数据private T data;// 成功响应(带数据)public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setCode(200);result.setMsg("操作成功");result.setData(data);return result;}// 成功响应(无数据)public static <T> Result<T> success() {return success(null);}// 失败响应public static <T> Result<T> error(String msg) {Result<T> result = new Result<>();result.setCode(500);result.setMsg(msg);result.setData(null);return result;}
}

5.3 RedisTemplate 基础操作(Lettuce/Jedis 通用)

通过 RedisTemplate<String, Object> 操作 String、Hash、List 等常见数据结构,示例在 Controller 层实现:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** RedisTemplate 基础操作示例(Lettuce/Jedis 通用)*/
@RestController
@RequestMapping("/redis/template")
public class RedisTemplateDemoController {// 注入自定义配置的 RedisTemplate(泛型 <String, Object>,避免强转)private final RedisTemplate<String, Object> redisTemplate;// 构造器注入(推荐,避免字段注入的循环依赖风险)public RedisTemplateDemoController(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// ------------------- String 类型示例(缓存单个对象) -------------------@GetMapping("/saveUser")public Result<User> saveUser() {// 1. 创建 User 对象(含 LocalDateTime,验证时间序列化)User user = new User(1L, "张三", 25, "zhangsan@test.com", LocalDateTime.now());// 2. 存储 String 类型:key=user:{id},value=User 对象,过期时间 1 小时String userKey = "user:" + user.getId();redisTemplate.opsForValue().set(userKey, user, 3600, TimeUnit.SECONDS);// 3. 读取 String 类型:根据 key 获取 User 对象User cachedUser = (User) redisTemplate.opsForValue().get(userKey);return Result.success(cachedUser);}// ------------------- Hash 类型示例(缓存对象的多个字段) -------------------@GetMapping("/saveUserHash")public Result<Map<Object, Object>> saveUserHash() {// 1. 创建 Hash 数据:key=userHash:{id},field=属性名,value=属性值Long userId = 2L;String hashKey = "userHash:" + userId;Map<String, Object> userMap = new HashMap<>();userMap.put("id", userId);userMap.put("name", "李四");userMap.put("age", 28);userMap.put("createTime", LocalDateTime.now());// 2. 存储 Hash 类型:批量添加 field-valueredisTemplate.opsForHash().putAll(hashKey, userMap);// 设置 Hash 过期时间(30 分钟)redisTemplate.expire(hashKey, 1800, TimeUnit.SECONDS);// 3. 读取 Hash 类型:获取所有 field-valueMap<Object, Object> cachedUserMap = redisTemplate.opsForHash().entries(hashKey);return Result.success(cachedUserMap);}
}

5.4 RedissonClient 基础操作(分布式场景)

Redisson 封装了更简洁的 API,且默认支持分布式锁,基础缓存操作示例:

import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;/*** RedissonClient 基础操作示例(分布式场景推荐)*/
@RestController
@RequestMapping("/redis/redisson")
public class RedissonDemoController {// 注入 Redisson 自动配置的 RedissonClientprivate final RedissonClient redissonClient;public RedissonDemoController(RedissonClient redissonClient) {this.redissonClient = redissonClient;}// ------------------- String 类型示例 -------------------@GetMapping("/saveUser")public Result<User> saveUser() {User user = new User(3L, "王五", 30, "wangwu@test.com", LocalDateTime.now());String userKey = "user:" + user.getId();// Redisson 操作 String:直接获取 Bucket(桶)对象,支持泛型redissonClient.getBucket(userKey).set(user, 3600, TimeUnit.SECONDS); // 存储并设置过期时间// 读取 String:直接获取泛型对象,无需强转User cachedUser = redissonClient.getBucket(userKey).get();return Result.success(cachedUser);}
}

六、第五步:进阶场景:分布式锁实现

6.1 方案 1:基于 RedisTemplate 实现分布式锁

6.1.1 分布式锁工具类(RedisTemplate 版)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;/*** RedisTemplate 分布式锁工具类(基础场景通用)*/
@Component
public class RedisTemplateLockUtil {private final RedisTemplate<String, Object> redisTemplate;public RedisTemplateLockUtil(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 加锁:原子操作,避免并发问题* @param lockKey 锁的 Key(如 "stock:lock:1001")* @param lockValue 锁的 Value(用 UUID 区分线程,避免误删)* @param expireTime 锁过期时间(秒),防止死锁* @return true=加锁成功,false=加锁失败*/public boolean lock(String lockKey, String lockValue, long expireTime) {Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);// 避免 Boolean 空指针(自动拆箱风险)return Boolean.TRUE.equals(isLocked);}/*** 解锁:验证 Value 一致性,避免误删其他线程的锁* @param lockKey 锁的 Key* @param lockValue 锁的 Value(需与加锁时的 Value 一致)*/public void unlock(String lockKey, String lockValue) {String storedValue = (String) redisTemplate.opsForValue().get(lockKey);// 只有当存储的 Value 与当前线程的 Value 一致时,才删除锁if (lockValue != null && lockValue.equals(storedValue)) {redisTemplate.delete(lockKey);}}
}

6.1.2 业务示例:库存扣减(RedisTemplate 锁)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;/*** 库存扣减业务示例(基于 RedisTemplate 分布式锁)*/
@RestController
@RequestMapping("/business/stock")
public class StockDeductWithTemplateLockController {private final RedisTemplateLockUtil redisTemplateLockUtil;private final RedisTemplate<String, Object> redisTemplate;public StockDeductWithTemplateLockController(RedisTemplateLockUtil redisTemplateLockUtil, RedisTemplate<String, Object> redisTemplate) {this.redisTemplateLockUtil = redisTemplateLockUtil;this.redisTemplate = redisTemplate;}/*** 库存扣减接口* @param productId 商品 ID* @return 扣减结果*/@GetMapping("/deduct/{productId}")public Result<String> deductStock(@PathVariable Long productId) {// 1. 定义锁 Key(按商品 ID 区分,避免全局锁导致性能问题)String lockKey = "stock:lock:" + productId;// 2. 定义锁 Value(UUID 区分线程,防止误删其他线程的锁)String lockValue = UUID.randomUUID().toString();// 3. 锁过期时间(10 秒,需大于业务执行时间)long expireTime = 10;try {// 4. 尝试加锁boolean isLocked = redisTemplateLockUtil.lock(lockKey, lockValue, expireTime);if (!isLocked) {return Result.error("当前请求过多,请稍后重试");}// 5. 核心业务:库存扣减(模拟从 Redis 读取库存并扣减)String stockKey = "stock:" + productId;// 初始化库存(首次调用时设置,实际项目从 DB 加载)redisTemplate.opsForValue().setIfAbsent(stockKey, 100);// 读取当前库存Integer currentStock = (Integer) redisTemplate.opsForValue().get(stockKey);// 6. 库存校验if (currentStock == null || currentStock <= 0) {return Result.error("商品 ID:" + productId + ",库存不足");}// 7. 扣减库存redisTemplate.opsForValue().set(stockKey, currentStock - 1);return Result.success("商品 ID:" + productId + ",库存扣减成功,剩余库存:" + (currentStock - 1));} finally {// 8. 解锁(必须在 finally 中,确保无论业务成功/失败都释放锁)redisTemplateLockUtil.unlock(lockKey, lockValue);}}
}

6.2 方案 2:基于 Redisson 实现分布式锁

6.2.1 分布式锁工具类(Redisson 版,可选)

Redisson 已封装完善的锁 API,工具类可按需封装(简化重复调用):

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;/*** Redisson 分布式锁工具类(生产环境推荐)*/
@Component
public class RedissonLockUtil {private final RedissonClient redissonClient;public RedissonLockUtil(RedissonClient redissonClient) {this.redissonClient = redissonClient;}/*** 获取重入锁并尝试加锁* @param lockKey 锁的 Key* @param waitTime 最大等待时间(秒):获取不到锁时最多等多久* @param leaseTime 锁持有时间(秒):-1 表示启用看门狗自动续期* @return RLock 锁对象(需手动解锁)* @throws InterruptedException 线程中断异常*/public RLock tryLock(String lockKey, long waitTime, long leaseTime) throws InterruptedException {RLock lock = redissonClient.getLock(lockKey);// 尝试加锁:waitTime 内获取不到则返回 nullboolean isLocked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);return isLocked ? lock : null;}/*** 解锁:验证当前线程是否持有锁,避免误删* @param lock 锁对象*/public void unlock(RLock lock) {if (lock != null && lock.isHeldByCurrentThread()) {lock.unlock();}}
}

6.2.2 业务示例:库存扣减(Redisson 锁)

import org.redisson.api.RLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 库存扣减业务示例(基于 Redisson 分布式锁)*/
@RestController
@RequestMapping("/business/stock")
public class StockDeductWithRedissonLockController {private final RedissonLockUtil redissonLockUtil;private final RedissonClient redissonClient;public StockDeductWithRedissonLockController(RedissonLockUtil redissonLockUtil, RedissonClient redissonClient) {this.redissonLockUtil = redissonLockUtil;this.redissonClient = redissonClient;}/*** 库存扣减接口(Redisson 锁,支持自动续期)* @param productId 商品 ID* @return 扣减结果*/@GetMapping("/deduct/redisson/{productId}")public Result<String> deductStock(@PathVariable Long productId) {// 1. 定义锁 Key(按商品 ID 区分)String lockKey = "stock:lock:" + productId;RLock lock = null;try {// 2. 尝试加锁:waitTime=5 秒(最多等 5 秒),leaseTime=-1(启用看门狗自动续期)lock = redissonLockUtil.tryLock(lockKey, 5, -1);if (lock == null) {return Result.error("当前请求过多,请稍后重试");}// 3. 核心业务:库存扣减(Redisson 操作 Redis 数据)String stockKey = "stock:" + productId;// 初始化库存(首次调用时设置)redissonClient.getBucket(stockKey).setIfAbsent(100);// 读取当前库存Integer currentStock = (Integer) redissonClient.getBucket(stockKey).get();// 4. 库存校验if (currentStock == null || currentStock <= 0) {return Result.error("商品 ID:" + productId + ",库存不足");}// 5. 扣减库存redissonClient.getBucket(stockKey).set(currentStock - 1);return Result.success("商品 ID:" + productId + ",库存扣减成功,剩余库存:" + (currentStock - 1));} catch (InterruptedException e) {Thread.currentThread().interrupt();return Result.error("请求被中断,请重试");} finally {// 6. 解锁(工具类已验证线程持有权,避免误删)redissonLockUtil.unlock(lock);}}
}

七、总结:选型指南与生产避坑

7.1 客户端选型终极建议

业务场景推荐客户端核心理由
单体项目 / 基础缓存LettuceSpring Boot 默认集成,异步非阻塞,无需额外配置,性能优于 Jedis
小型同步操作项目JedisAPI 直观,调试简单,适合新手入门(需手动管理连接池)
微服务 / 分布式锁 / 秒杀Redisson官方推荐,封装看门狗续期、红锁等高级特性,避免手动处理分布式锁细节

7.2 生产环境避坑 5 大要点

  1. 连接池参数不合理(导致 Redis 连接耗尽)
  • 问题:max-active 过大(超过 Redis 服务端 maxclients 配置),或 min-idle 过小(频繁创建连接);
  • 解决方案:单机场景 max-active=32~64,集群场景 64~128,min-idle 设为 max-active/2,避免极端值。
  1. 未设置 Key 过期时间(导致内存泄漏)
  • 问题:缓存类 Key 无过期时间,永久占用 Redis 内存,最终导致内存溢出;
  • 解决方案:所有缓存 Key 必须通过 expire() 设置过期时间(如 1~24 小时),热点数据可配合缓存更新策略(如 Caffeine 本地缓存)。
  1. 分布式锁未处理 “锁过期”(业务未完成锁释放)
  • 问题:RedisTemplate 实现中,锁过期时间短于业务执行时间,导致其他线程提前获锁;
  • 解决方案:RedisTemplate 需自定义 “续命线程”,Redisson 直接用 leaseTime=-1 启用看门狗(默认 30s 续期)。
  1. Redis 集群未配置主从 + 哨兵(单点故障风险)
  • 问题:单节点 Redis 宕机后,整个缓存 / 分布式锁服务不可用;
  • 解决方案:生产环境部署 3 主 3 从集群 + 哨兵,确保主节点故障时自动切换从节点。
  1. 序列化遗漏特殊类型(如 LocalDateTime 反序列化失败)
  • 问题:Lettuce/Jedis 未注册 JavaTimeModule,或 Redisson 未自定义序列化器,导致 JDK8 时间类型反序列化报错;
  • 解决方案:Lettuce/Jedis 在 RedisConfig 中显式注册 JavaTimeModule,Redisson 按需配置 RedissonConfig。
http://www.dtcms.com/a/464749.html

相关文章:

  • 【Linux】线程同步与互斥(上)
  • 图观 模型编辑器
  • Win11 输入延迟与鼠标卡顿:系统化排查与优化指南
  • 【开题答辩全过程】以 爱运动健身小程序的设计与实现为例,包含答辩的问题和答案
  • Linux 内核IIO sensor驱动
  • 《Linux系统编程之入门基础》【Linux的前世今生】
  • 活动汪活动策划网站龙岗建设网站
  • Apache IoTDB 架构特性与 Prometheus+Grafana 监控体系部署实践
  • LLM时代基于unstructured解析非结构化pdf
  • uniapp tab切换及tab锚点效果(wx小程序及H5端)
  • Hadoop面试题及详细答案 110题 (71-85)-- 集群部署与运维
  • 5-1〔OSCP ◈ 研记〕❘ SQL注入攻击▸SQL注入理论基础
  • 南充市企业网站建设wordpress极客主题
  • 企业做小红书关键词搜索排名推广时,怎么找到小红书上有一定搜索量但竞争度低的蓝海词?
  • 数据仓库与数据挖掘基础知识
  • 鸿蒙:使用Rating组件实现五角星打分评价
  • 外国人可以在中国做网站吗做个网站得花多少钱
  • 双均线策略
  • 【vLLM 学习】Neuron
  • 网站做行业认证好处施工企业在施工过程中发现工程设计图纸存在差错的
  • 迅为RK3576开发板挂载Windows以及虚拟机Ubuntu测试
  • 第1篇:创建基础电商AI客服
  • 【MyBatis从入门到入土】告别JDBC原始时代:零基础MyBatis极速上手指南
  • MaxScript 科研绘图教程:从数据到精确的可视化
  • org.apache.http.conn.HttpHostConnectException: Connect to localhost:8086
  • 深度学习入门(一)——从神经元到损失函数,一步步理解前向传播(上)
  • 沧州网站制作公司宁波网站的优化
  • 工程承包去哪个网站做网站的步骤 优帮云
  • 网站建设课程wordpress 去掉左上角
  • 怎么创建一个博客网站wordpress电影下载