news 2026/9/3 18:05:55

Spring Boot集成Redisson实现高可靠分布式锁:从原理到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot集成Redisson实现高可靠分布式锁:从原理到实战

最近在开发一个分布式任务调度系统时,遇到了一个非常棘手的问题:多个服务节点同时处理任务,由于缺乏有效的分布式锁机制,导致同一个任务被重复执行,造成了数据不一致和资源浪费。排查后发现,问题的核心在于对分布式锁的理解不够深入,尤其是在高并发场景下的选型与实现。本文将围绕分布式锁这一核心技术,从概念、选型到实战,为你完整拆解一套在 Spring Boot 项目中集成 Redisson 实现高可靠分布式锁的闭环方案。无论你是正在学习分布式系统的学生,还是需要在生产环境中解决并发问题的后端开发者,都能从本文中找到可直接复用的代码和清晰的避坑指南。

1. 背景与核心概念:为什么需要分布式锁?

在单机多线程环境下,我们可以使用synchronized关键字或ReentrantLock来保证同一时刻只有一个线程能访问共享资源,这是进程内锁线程锁

然而,在微服务或分布式架构中,应用被部署在多个独立的 JVM 进程甚至多台物理服务器上。这些进程之间的内存是隔离的,单机的锁机制(如synchronized)完全失效。此时,我们需要一个所有服务节点都能共同访问和认可的“协调者”来充当锁的仲裁者,这就是分布式锁

分布式锁的核心目标:在分布式系统或集群环境中,控制对共享资源(如数据库某一行、一个文件、一个业务ID)的访问,确保在同一时间,只有一个客户端(或线程)能执行特定的代码逻辑。

常见应用场景

  1. 防止重复提交:用户快速点击提交订单按钮,后端需要确保同一订单号只被处理一次。
  2. 秒杀库存扣减:成千上万的请求同时抢购一件商品,必须保证库存扣减的原子性,不能超卖。
  3. 定时任务调度:在集群中部署了多个相同的定时任务实例,需要保证同一时刻只有一个实例执行任务,避免重复执行。
  4. 分布式全局序列号生成:生成全局唯一的订单号、流水号,需要保证递增序列的原子性。

为什么需要深入掌握?简单地使用一个setnx命令并不足以应对生产环境的复杂性。你需要考虑锁的可重入性(同一个线程可多次获取锁)、锁超时(防止死锁)、锁续期(看门狗机制)、锁释放的原子性(避免误删他人锁)以及高可用性(锁服务本身不能是单点)。接下来,我们将从主流实现方案对比开始。

2. 环境准备与版本说明

在开始编码之前,请确保你的开发环境满足以下要求。本文的示例将基于最常用的技术栈进行演示。

基础运行环境:

  • 操作系统:Windows 10/11, macOS 或 Linux(如 Ubuntu 20.04+)均可。本文命令以 Linux/macOS 的 Bash 为例,Windows 用户可使用 Git Bash 或 WSL。
  • Java 开发套件 (JDK):版本 8 或 11(推荐 11)。本文使用 OpenJDK 11。
    java -version # 输出应类似:openjdk version "11.0.15" 2022-04-19
  • 项目管理与构建工具:Apache Maven 3.6+ 或 Gradle 6.8+。本文使用 Maven。
    mvn -v # 输出应包含 Apache Maven 3.8.6
  • 集成开发环境 (IDE):IntelliJ IDEA(推荐)、Eclipse 或 VS Code。

核心依赖与中间件:

  • Spring Boot:2.7.x 版本(本文使用 2.7.18)。这是当前长期支持版本之一,稳定且生态丰富。
  • Redis:5.0+ 版本(本文使用 Redis 7.0)。Redis 将作为分布式锁的存储和协调中心。你需要确保有一个可访问的 Redis 服务。
    • 安装与启动参考(使用 Docker 最便捷):
    # 拉取 Redis 7.0 镜像 docker pull redis:7.0 # 运行 Redis 容器,暴露 6379 端口 docker run -d --name my-redis -p 6379:6379 redis:7.0 # 测试连接 docker exec -it my-redis redis-cli ping # 应返回 PONG
  • Redisson:3.17.x 版本(本文使用 3.17.7)。Redisson 是一个在 Redis 基础上实现的 Java 驻内存数据网格客户端,它提供了丰富易用的分布式对象和服务,其中就包括我们需要的分布式锁。

示例项目结构:我们将创建一个标准的 Spring Boot 项目。初始化后的目录结构大致如下:

distributed-lock-demo/ ├── pom.xml (Maven 配置文件) ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── demo/ │ │ │ ├── DistributedLockDemoApplication.java (启动类) │ │ │ ├── config/ (配置类目录) │ │ │ ├── service/ (业务服务目录) │ │ │ └── controller/ (Web控制器目录) │ │ └── resources/ │ │ ├── application.yml (或 application.properties) │ │ └── ... │ └── test/ (测试目录) └── ...

如果你的具体环境版本与上述不同(例如使用 Spring Boot 3.x 或 Gradle),不必担心,核心配置思路和 API 用法是相通的,只需在依赖版本上做相应调整即可。

3. 分布式锁方案选型与 Redisson 原理

在 Java 领域,实现分布式锁主要有以下几种方案,各有优劣:

方案实现方式优点缺点适用场景
基于数据库利用数据库的唯一约束或乐观锁版本号。实现简单,依赖少。性能差,对数据库压力大,锁无失效时间容易死锁。并发量极低,且不允许引入额外中间件的场景。
基于 ZooKeeper创建临时有序节点,序号最小的节点获取锁。高可靠性,原生支持 Watcher 机制,锁释放及时。性能比 Redis 差,需要维护 ZooKeeper 集群,客户端需处理会话过期。对一致性要求极高,且并发量不是首要考量的场景(如配置中心、选主)。
基于 Redis使用SET key value NX PX timeout命令。性能极高,实现相对简单。需要自己处理锁续期、原子性释放等问题,单节点 Redis 有宕机风险。高并发、对性能要求苛刻,且允许极低概率锁失效的场景。
基于 Redisson在 Redis 基础上封装,提供看门狗、可重入锁等高级特性。功能完善(可重入、公平锁、联锁等),高可用(支持主从、哨兵、集群),简单易用引入额外客户端依赖。生产环境推荐方案,适用于绝大多数需要强一致性和高可用分布式锁的场景。

为什么选择 Redisson?因为它完美解决了原生 Redis 实现分布式锁的诸多痛点:

  1. 可重入锁(Reentrant Lock):同一个线程可以多次获取同一把锁,锁内部有一个计数器,获取时+1,释放时-1,计数器为0时才真正释放。这对于递归调用或嵌套方法非常必要。
  2. 锁续期(Watchdog):当你的业务逻辑执行时间超过了锁的初始超时时间,Redisson 的“看门狗”机制会自动为你续期,防止业务未执行完锁就过期,导致其他线程误入。默认看门狗检查锁的超时时间是30秒,每10秒续期一次。
  3. 锁释放的原子性:Redisson 使用 Lua 脚本在 Redis 端原子性地判断锁标识和释放锁,避免了“判断锁归属”和“释放锁”两个操作之间的竞态条件,彻底解决了误删他人锁的问题。
  4. 丰富的锁类型:除了基本的可重入锁,还提供了公平锁、联锁、红锁、读写锁等,满足复杂业务场景。

Redisson 分布式锁核心原理简析: 当你调用lock.lock()时,Redisson 会执行一个 Lua 脚本到 Redis。这个脚本主要做两件事:

  • 判断key是否存在。如果不存在,则用HSET命令设置一个 Hash 结构,字段为客户端唯一标识(UUID+线程ID),值为1(重入次数),并设置一个过期时间。
  • 如果key已存在,则检查 Hash 中的客户端标识是否与当前请求一致。如果一致,则将重入次数+1,并重新设置过期时间。

锁的释放lock.unlock()同样通过 Lua 脚本实现原子操作:检查客户端标识,匹配则重入次数-1,如果减到0则删除key

4. 完整实战:在 Spring Boot 中集成 Redisson

接下来,我们一步步构建一个完整的示例。假设我们有一个“商品库存扣减”的场景。

4.1 创建 Spring Boot 项目并添加依赖

使用 Spring Initializr 或 IDE 创建一个新的 Spring Boot 项目,选择WebLombok依赖(Lombok 用于简化代码)。

pom.xml中,手动添加redisson-spring-boot-starter依赖。注意版本匹配,Spring Boot 2.7.x 通常对应 Redisson 3.17.x。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <!-- 使用稳定的 2.7.x 版本 --> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>distributed-lock-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>distributed-lock-demo</name> <description>Demo project for distributed lock with Redisson</description> <properties> <java.version>11</java.version> <redisson.version>3.17.7</redisson.version> <!-- 指定 Redisson 版本 --> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Redisson Starter --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>${redisson.version}</version> </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> </project>

4.2 配置 Redisson 连接

src/main/resources/application.yml中配置 Redis 连接信息。这里我们连接本地 Docker 启动的 Redis。

spring: redis: # Redis 服务器地址,如果是远程或云服务,请修改此处 host: localhost port: 6379 # 如果有密码,配置 password # password: yourpassword database: 0 # 默认使用 0 号数据库 # Redisson 特定配置 (可选,大部分情况使用默认即可) redisson: # 单节点模式配置 single-server-config: address: "redis://${spring.redis.host}:${spring.redis.port}" # 连接池大小 connectionPoolSize: 64 connectionMinimumIdleSize: 24 # 如果 spring.redis.password 设置了,这里会自动继承,无需重复配置

Redisson Spring Boot Starter 会自动读取spring.redis.*的配置并创建RedissonClient实例注入到 Spring 容器中。你也可以通过@Configuration类进行更复杂的配置(如集群模式、哨兵模式),但单机模式上述配置足够。

4.3 编写库存服务与锁应用

首先,我们模拟一个商品库存表。在实际项目中,这对应数据库的一张表。这里我们用一个ConcurrentHashMap在内存中模拟。

// 文件路径:src/main/java/com/example/demo/service/InventoryService.java package com.example.demo.service; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 模拟库存服务 */ @Service @Slf4j public class InventoryService { // 模拟数据库中的库存表,key: 商品ID, value: 库存数量 private final Map<Long, Integer> inventoryMap = new ConcurrentHashMap<>(); /** * 初始化一些测试数据 */ @PostConstruct public void init() { inventoryMap.put(1001L, 10); // 商品ID 1001,初始库存 10 inventoryMap.put(1002L, 5); // 商品ID 1002,初始库存 5 log.info("库存初始化完成: {}", inventoryMap); } /** * 查询库存(无需加锁) */ public Integer getStock(Long productId) { return inventoryMap.getOrDefault(productId, 0); } /** * 扣减库存 - 不安全版本(用于对比) * 存在并发超卖问题! */ public boolean deductStockUnsafe(Long productId, Integer quantity) { Integer currentStock = inventoryMap.get(productId); if (currentStock == null || currentStock < quantity) { log.warn("商品 {} 库存不足,当前库存: {}, 需要: {}", productId, currentStock, quantity); return false; } // 模拟一个耗时操作,放大并发问题 try { Thread.sleep(10); // 10毫秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } int newStock = currentStock - quantity; inventoryMap.put(productId, newStock); log.info("商品 {} 扣减 {} 成功,剩余库存: {}", productId, quantity, newStock); return true; } /** * 扣减库存 - 安全版本(使用 Redisson 分布式锁) */ public boolean deductStockWithLock(Long productId, Integer quantity) { // 锁的 key 应该与业务资源强相关,这里使用商品ID String lockKey = "lock:inventory:" + productId; // 获取锁对象 RLock lock = redissonClient.getLock(lockKey); boolean isLocked = false; try { // 尝试加锁,最多等待5秒,锁持有时间10秒(看门狗会自动续期) isLocked = lock.tryLock(5, 10, java.util.concurrent.TimeUnit.SECONDS); if (!isLocked) { log.error("获取商品 {} 的分布式锁失败,可能系统繁忙", productId); return false; } // 成功获取锁,执行核心业务逻辑 return doDeductStock(productId, quantity); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("获取锁时被中断", e); return false; } finally { // 必须在 finally 块中释放锁,确保锁一定会被释放 if (isLocked && lock.isHeldByCurrentThread()) { lock.unlock(); log.debug("商品 {} 的分布式锁已释放", productId); } } } /** * 实际执行库存扣减的逻辑(抽离出来,方便加锁) */ private boolean doDeductStock(Long productId, Integer quantity) { Integer currentStock = inventoryMap.get(productId); if (currentStock == null || currentStock < quantity) { log.warn("商品 {} 库存不足,当前库存: {}, 需要: {}", productId, currentStock, quantity); return false; } // 模拟业务处理耗时 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } int newStock = currentStock - quantity; inventoryMap.put(productId, newStock); log.info("【安全】商品 {} 扣减 {} 成功,剩余库存: {}", productId, quantity, newStock); return true; } // 需要注入 RedissonClient private final org.redisson.api.RedissonClient redissonClient; public InventoryService(org.redisson.api.RedissonClient redissonClient) { this.redissonClient = redissonClient; } }

关键代码解释:

  1. redissonClient.getLock(lockKey):获取一个RLock(可重入锁)对象。lockKey是锁在 Redis 中的键,必须具有业务含义,通常格式为lock:业务前缀:资源标识
  2. lock.tryLock(waitTime, leaseTime, unit):这是推荐的使用方式
    • waitTime:获取锁的最大等待时间。如果设置为0,则获取不到锁立即返回。这里设置5秒,意味着线程会尝试5秒去获取锁。
    • leaseTime:锁的持有时间。超过这个时间,锁会自动失效。即使你的业务没有执行完。但 Redisson 的看门狗机制(默认开启)会在业务执行期间自动续期,除非你显式指定leaseTime并关闭看门狗。
    • 最佳实践:通常使用tryLock并设置一个合理的等待时间,避免线程无限期阻塞。leaseTime 可以不指定(或设为 -1),使用看门狗默认机制。
  3. lock.unlock():释放锁。务必在finally块中执行,以确保异常情况下锁也能被释放。释放前通过lock.isHeldByCurrentThread()检查当前线程是否持有该锁,避免误释放。

4.4 创建控制器进行测试

创建一个简单的 HTTP 接口,方便我们通过压测工具(如 JMeter、Postman)或浏览器进行并发测试。

// 文件路径:src/main/java/com/example/demo/controller/InventoryController.java package com.example.demo.controller; import com.example.demo.service.InventoryService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @RestController @RequestMapping("/inventory") @Slf4j public class InventoryController { private final InventoryService inventoryService; public InventoryController(InventoryService inventoryService) { this.inventoryService = inventoryService; } /** * 查询库存 */ @GetMapping("/{productId}") public String getStock(@PathVariable Long productId) { Integer stock = inventoryService.getStock(productId); return String.format("商品 %d 的当前库存为: %d", productId, stock); } /** * 测试不安全扣减 (模拟并发超卖) */ @PostMapping("/deduct/unsafe/{productId}") public String deductUnsafe(@PathVariable Long productId, @RequestParam(defaultValue = "1") Integer quantity, @RequestParam(defaultValue = "50") Integer threadCount) throws InterruptedException { int totalRequests = threadCount; CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(totalRequests); ExecutorService executorService = Executors.newFixedThreadPool(10); int initialStock = inventoryService.getStock(productId); log.info("商品 {} 初始库存: {}", productId, initialStock); for (int i = 0; i < totalRequests; i++) { executorService.submit(() -> { try { startLatch.await(); // 等待所有线程就绪 inventoryService.deductStockUnsafe(productId, quantity); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endLatch.countDown(); } }); } startLatch.countDown(); // 同时放行所有线程 endLatch.await(); // 等待所有线程执行完毕 executorService.shutdown(); int finalStock = inventoryService.getStock(productId); log.info("商品 {} 最终库存: {}", productId, finalStock); int sold = initialStock - finalStock; int expectedSold = Math.min(totalRequests * quantity, initialStock); // 理论最大售出量 return String.format("测试完成。初始库存: %d, 最终库存: %d, 实际售出: %d, 理论最大售出: %d。可能存在超卖。", initialStock, finalStock, sold, expectedSold); } /** * 测试安全扣减 (使用分布式锁) */ @PostMapping("/deduct/safe/{productId}") public String deductSafe(@PathVariable Long productId, @RequestParam(defaultValue = "1") Integer quantity, @RequestParam(defaultValue = "50") Integer threadCount) throws InterruptedException { // 为了公平对比,先重置库存(实际生产环境不要这样做) // 这里简单重新初始化,仅用于演示 // 实际测试前请重启应用或调用重置接口 int totalRequests = threadCount; CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(totalRequests); ExecutorService executorService = Executors.newFixedThreadPool(10); int initialStock = inventoryService.getStock(productId); log.info("商品 {} 初始库存: {}", productId, initialStock); for (int i = 0; i < totalRequests; i++) { executorService.submit(() -> { try { startLatch.await(); inventoryService.deductStockWithLock(productId, quantity); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endLatch.countDown(); } }); } startLatch.countDown(); endLatch.await(); executorService.shutdown(); int finalStock = inventoryService.getStock(productId); log.info("商品 {} 最终库存: {}", productId, finalStock); int sold = initialStock - finalStock; int expectedSold = Math.min(totalRequests * quantity, initialStock); return String.format("测试完成。初始库存: %d, 最终库存: %d, 实际售出: %d, 理论最大售出: %d。库存正确。", initialStock, finalStock, sold, expectedSold); } }

4.5 运行与验证

  1. 启动 Redis:确保你的 Redis 服务正在运行(例如通过前面提到的 Docker 命令)。
  2. 启动 Spring Boot 应用:运行DistributedLockDemoApplicationmain方法。
  3. 测试查询接口:打开浏览器或使用curl访问http://localhost:8080/inventory/1001,应该看到库存为10。
  4. 测试不安全扣减(模拟超卖)
    • 使用工具(如 Postman)并发调用POST http://localhost:8080/inventory/deduct/unsafe/1001?quantity=1&threadCount=20
    • 观察控制台日志,你会发现大量的“扣减成功”日志,但最终库存很可能为负数。因为20个线程几乎同时读到库存为10,都判断为充足,然后依次扣减,导致超卖。
  5. 测试安全扣减(使用分布式锁)
    • 重要:由于我们是在内存中模拟库存,为了再次测试,你需要重启应用来重置库存为10。
    • 调用POST http://localhost:8080/inventory/deduct/safe/1001?quantity=1&threadCount=20
    • 观察控制台日志。这次,日志会显示线程在“获取锁”和“释放锁”,并且“扣减成功”的日志是顺序出现的。最终库存一定不会为负,最多扣减到0。实际售出数量等于初始库存(10),证明了分布式锁有效防止了超卖。

预期结果对比:

  • 无锁情况:20个请求,可能成功扣减20次,库存变为-10(超卖)。
  • 有锁情况:20个请求,只有前10个请求能成功获取锁并扣减库存,后10个请求会因库存不足或获取锁超时而失败。库存最终为0,符合预期。

5. 常见问题与排查思路

在实际使用 Redisson 分布式锁时,你可能会遇到以下问题:

问题现象可能原因排查思路与解决方案
获取锁一直失败,返回 false1. 锁被其他客户端长期占用。
2.waitTime设置太短,在竞争激烈时来不及获取。
3. Redis 连接异常或网络超时。
1. 检查持有锁的客户端业务逻辑是否执行时间过长或发生死锁。可以通过 Redis 命令TTL lock_key查看锁剩余时间。
2. 适当增加tryLockwaitTime,或检查业务设计,看是否锁粒度太粗导致竞争激烈。
3. 检查 Redis 服务状态、网络连通性以及 Redisson 配置。
抛出IllegalMonitorStateException当前线程尝试释放一个它并未持有的锁。通常发生在错误地释放了其他线程的锁,或锁已因超时自动释放。务必finally块中释放锁前,使用lock.isHeldByCurrentThread()进行判断。确保释放锁的线程就是获取锁的线程。
业务执行时间超过锁过期时间,锁提前释放未使用看门狗机制,且leaseTime设置过短。业务未执行完,锁已过期,被其他线程获取,导致数据不一致。1.推荐:使用lock.tryLock(waitTime, -1, unit)lock.lock(),不指定leaseTime,启用看门狗(默认开启)。
2. 如果必须指定leaseTime,请确保其值远大于业务最大可能执行时间,并考虑在业务代码中实现续期逻辑(Redisson 看门狗已封装)。
Redis 集群模式下,主节点宕机可能导致锁丢失在 Redis 异步复制的模式下,锁信息可能还未同步到从节点,主节点就宕机了,此时从节点升级为主,锁状态丢失。对于要求绝对强一致性的场景,可以考虑使用 Redisson 的红锁(RedLock)。但请注意,红锁性能较低,且仍有争议。对于绝大多数业务场景,主从+哨兵模式已足够可靠,需权衡一致性与性能。
性能瓶颈高并发下,大量线程在竞争同一把锁,导致大部分线程处于等待状态,系统吞吐量下降。1.细化锁粒度:不要用一把大锁锁住整个方法或资源。例如,库存扣减锁的 key 精确到商品ID,而不是所有商品共用一把锁。
2.考虑乐观锁:如果冲突概率不高,可以使用数据库的乐观锁(版本号)或 CAS 操作,避免互斥等待。
3.业务优化:检查是否可以通过队列、批量处理等方式减少对锁的竞争。

基础排查命令(Redis CLI):

# 连接到 Redis redis-cli # 查看所有以 ‘lock:‘ 开头的键 KEYS lock:* # 查看特定锁的详细信息 (Redisson 锁是 Hash 类型) HGETALL lock:inventory:1001 # 查看锁的剩余生存时间 (TTL) TTL lock:inventory:1001

6. 最佳实践与工程建议

将分布式锁应用到生产环境,除了正确使用 API,还需要遵循以下工程实践:

  1. 锁的粒度要尽可能细

    • 坏实践String lockKey = "global_inventory_lock";(所有商品共用一把锁)
    • 好实践String lockKey = "lock:inventory:" + productId;(每个商品ID一把锁)
    • 细粒度锁能极大减少竞争,提升系统并发能力。
  2. 锁的命名要有业务意义

    • 使用统一的命名规范,如lock:业务模块:资源标识[:子资源]
    • 例如:lock:order:create:${userId}(用户订单创建锁),lock:coupon:grant:${batchId}(优惠券发放批次锁)。
    • 清晰的命名便于监控、排查和清理僵尸锁。
  3. 总是使用 tryLock 并设置超时时间

    • lock()方法会一直阻塞直到获取锁,这在网络分区或客户端宕机时可能导致灾难。
    • tryLock(timeout)允许你在指定时间内获取锁,超时则快速失败,进行降级处理(如返回“系统繁忙,请重试”)。
  4. 锁必须被释放,且由加锁线程释放

    • 使用try-finally块确保锁释放。
    • finally中释放前,用isHeldByCurrentThread()判断。
    • 避免在try块内使用return语句而跳过unlock
  5. 谨慎处理锁的自动续期(看门狗)

    • 默认情况下,不指定leaseTime或设置为-1,看门狗会工作。
    • 如果你显式指定了leaseTime,看门狗机制将不会生效。此时你必须确保业务能在leaseTime内完成。
    • 对于执行时间不确定的长时间任务(如文件处理、复杂计算),建议使用默认看门狗。
  6. 做好降级和熔断

    • 分布式锁依赖外部存储(Redis),它本身可能故障。
    • tryLock失败或获取锁超时时,要有合理的业务降级策略,而不是让流程彻底失败。例如,可以记录日志、返回排队中状态、或使用本地限流做兜底。
  7. 监控与告警

    • 监控 Redis 的内存、连接数、QPS。
    • 监控应用中对特定锁的等待时间、获取失败率。如果某个锁的竞争异常激烈(高等待时间、高失败率),可能意味着业务热点或锁粒度过粗,需要优化。
    • 可以记录锁的获取和释放日志(Debug级别),便于线上问题追踪。
  8. 在测试环境充分验证

    • 模拟网络延迟、Redis 宕机、应用实例重启等异常情况,验证锁的行为是否符合预期。
    • 进行高并发压测,验证锁的性能和正确性。

掌握分布式锁的原理和 Redisson 的最佳实践,能让你在构建高并发、高可用的分布式系统时,更加从容地应对数据一致性的挑战。从理解问题本质开始,选择合适的工具,再到严谨的编码和全面的测试,每一步都至关重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 18:05:03

Rustore应用商店开发指南:从GMS迁移到支付集成的完整实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 18:04:01

从源码到二进制:编译器优化、TOCTOU与构建一致性风险解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 18:02:30

AI内容生成边界:技术博客助手为何拒绝娱乐类请求?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 18:00:25

STM32F103无刷电机六步换相驱动:低成本三极管方案实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 17:59:24

Codex与Claude Code实战对比:多文件项目下谁更适合作工程助手?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华