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

分布式锁详解及 Spring Boot 实战示例

在分布式系统中,多个服务实例可能同时操作共享资源(如数据库中的同一订单、库存记录),若缺乏协调机制,会导致数据不一致(如超卖、重复下单)。分布式锁正是解决这类问题的核心技术,它能保证同一时间只有一个服务实例执行特定临界区代码。

一、分布式锁的核心特性

一个可靠的分布式锁需满足以下特性:

  • 互斥性:任意时刻只有一个线程持有锁。
  • 安全性:锁只能被持有它的线程释放。
  • 可用性:即使部分节点故障,锁仍能正常获取和释放。
  • 防死锁:避免因线程崩溃导致锁永久无法释放。
  • 幂等性:重复获取 / 释放锁不会产生副作用。

二、分布式锁的实现方案及原理

常见实现方式包括基于数据库、Redis、ZooKeeper 等,不同方式的原理各有不同。

1. 基于数据库的分布式锁

原理:利用数据库的唯一索引或悲观锁来实现。例如,创建一张锁表,包含资源标识、持有线程标识、过期时间等字段,给资源标识字段创建唯一索引。当需要获取锁时,向表中插入一条记录,若插入成功则表示获取到锁;释放锁时,删除该记录。为防止死锁,可定期清理过期未释放的锁。

优缺点

  • 优点:实现简单,无需额外中间件。
  • 缺点:性能较差,数据库压力大;易出现锁表问题;不支持锁自动续期等高级特性。

2. 基于 Redis 的分布式锁

原理:利用 Redis 的SET命令的原子性。核心命令如下:

# 仅当key不存在时设置值,过期时间10秒,返回OK表示获取锁成功SET lock:resource true NX PX 10000
  • NX:仅在键不存在时才设置(保证互斥性)。
  • PX 10000:设置键的过期时间为 10 秒(防死锁)。

释放锁时,需通过 Lua 脚本保证原子性,先判断锁是否由当前线程持有,再删除锁:

if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
elsereturn 0
end

优缺点

  • 优点:性能高,操作简单;支持过期时间设置。
  • 缺点:在 Redis 集群环境下,可能存在主从同步延迟导致的锁丢失问题;需自行处理锁续期等问题。

由于 Redis 的高性能和易用性,它成为分布式锁的主流选择,本文后续重点介绍基于 Redis 的分布式锁实现。

3. 基于 ZooKeeper 的分布式锁

原理:利用 ZooKeeper 的节点特性和 Watcher 机制。ZooKeeper 的节点分为持久节点、临时节点、持久顺序节点、临时顺序节点。分布式锁通常使用临时顺序节点,当需要获取锁时,在指定节点下创建一个临时顺序节点,然后判断当前节点是否为序号最小的节点,若是则获取到锁;若不是,则监听序号比当前节点小的最后一个节点,当该节点被删除时,重新判断。释放锁时,删除创建的临时节点,由于是临时节点,当持有锁的线程崩溃时,节点会自动删除,避免死锁。

优缺点

  • 优点:可靠性高,不存在锁丢失问题;支持公平锁;自带 Watcher 机制,可实现锁的自动释放和唤醒。
  • 缺点:性能相对 Redis 较低;部署和维护成本高。

三、Spring Boot 集成分布式锁的相关依赖及对比

1. 基于 Redis 的依赖

  • spring-boot-starter-data-redis
    • 提供了 Redis 的基本操作模板(RedisTemplate),可用于手动实现分布式锁。
    • 需自行处理锁的获取、释放、续期等逻辑,实现相对复杂,但灵活性高。
  • redisson-spring-boot-starter
    • 是 Redis 官方推荐的 Java 客户端,内置了分布式锁的完整实现,支持自动续期、公平锁、可重入锁等高级特性。
    • 封装了复杂的底层逻辑,使用简单,适合生产环境。

2. 基于 ZooKeeper 的依赖

  • spring-cloud-starter-zookeeper-discovery
    • 主要用于服务发现,但也可借助 ZooKeeper 客户端操作 ZooKeeper 实现分布式锁。
    • 需要自行基于 ZooKeeper 的 API 实现锁的逻辑,较为繁琐。
  • curator-recipes
    • 是 ZooKeeper 的客户端框架,提供了分布式锁等常用功能的封装,如 InterProcessMutex 等类可直接用于实现分布式锁。
    • 简化了 ZooKeeper 分布式锁的实现,可靠性高。

3. 依赖对比

依赖

基于中间件

特点

适用场景

spring-boot-starter-data-redis

Redis

基础操作支持,需自行实现锁逻辑

简单场景,对灵活性要求高

redisson-spring-boot-starter

Redis

内置完整锁实现,支持高级特性

生产环境,复杂业务场景

spring-cloud-starter-zookeeper-discovery

ZooKeeper

主要用于服务发现,锁实现需自行开发

已使用 ZooKeeper 做服务发现,简单锁场景

curator-recipes

ZooKeeper

封装了分布式锁功能,可靠性高

对锁可靠性要求高的场景

四、Spring Boot 集成 Redis 分布式锁实战

1. 环境准备

pom.xml 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.redisson</groupId> <!-- 推荐使用Redisson简化锁操作 --><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.3</version>
</dependency>

Redis 配置(application.yml)

spring:redis:host: localhostport: 6379database: 0timeout: 3000ms

2. 基于 Redisson 的分布式锁实现

Redisson 是 Redis 官方推荐的 Java 客户端,内置了分布式锁的完整实现,支持自动续期、公平锁、可重入锁等高级特性。

分布式锁工具类

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;@Component
public class RedisDistributedLock {private final RedissonClient redissonClient;public RedisDistributedLock(RedissonClient redissonClient) {this.redissonClient = redissonClient;}/*** 获取分布式锁* @param lockKey 锁标识* @param waitTime 等待时间(获取锁的最大等待时长)* @param leaseTime 锁持有时间(自动释放时间)* @return 锁对象*/public RLock lock(String lockKey, long waitTime, long leaseTime) {RLock lock = redissonClient.getLock(lockKey);try {// 尝试获取锁,最多等待waitTime,持有leaseTime后自动释放boolean isLocked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);if (isLocked) {return lock;}} catch (InterruptedException e) {Thread.currentThread().interrupt();}return null;}/*** 释放锁* @param lock 锁对象*/public void unlock(RLock lock) {if (lock != null && lock.isHeldByCurrentThread()) {lock.unlock();}}
}

3. 业务场景示例:库存扣减

Service 层代码

import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;@Service
public class InventoryService {@Resourceprivate RedisDistributedLock distributedLock;@Resourceprivate InventoryMapper inventoryMapper;  // 假设已实现数据库操作/*** 扣减商品库存* @param productId 商品ID* @param quantity 扣减数量* @return 操作结果*/public boolean deductInventory(Long productId, int quantity) {// 锁标识:通常用业务资源唯一标识(如商品ID)String lockKey = "lock:inventory:" + productId;RLock lock = null;try {// 获取锁:最多等待3秒,持有10秒后自动释放lock = distributedLock.lock(lockKey, 3, 10);if (lock == null) {// 获取锁失败(如超时)return false;}// 临界区代码:查询库存并扣减int currentStock = inventoryMapper.selectStockByProductId(productId);if (currentStock >= quantity) {inventoryMapper.updateStock(productId, currentStock - quantity);return true;} else {// 库存不足return false;}} finally {// 确保锁释放distributedLock.unlock(lock);}}
}

五、关键注意事项

  • 锁的粒度:锁标识应精准到具体资源(如lock:order:123而非lock:order),避免锁范围过大导致性能瓶颈。
  • 过期时间设置:需大于业务执行时间,Redisson 的watch dog机制会自动续期(默认每 30 秒续期一次)。
  • 异常处理:必须在finally块中释放锁,避免因业务异常导致锁泄漏。
  • 重试机制:获取锁失败时可添加有限重试逻辑(如循环 3 次),提高成功率。

六、进阶优化方向

  • 公平锁:通过redissonClient.getFairLock(lockKey)实现,避免线程饥饿。
  • 红锁(RedLock):在多 Redis 节点环境中,通过多个实例获取锁提高可靠性(适合极高一致性场景)。
  • 缓存与数据库一致性:结合本地锁(synchronized)与分布式锁,减少分布式锁的使用频率。

通过不同的分布式锁实现方式和相关依赖,Spring Boot 应用可在分布式环境中安全地操作共享资源。在实际开发中,需根据业务场景和性能需求选择合适的实现方式和依赖,Redisson 等成熟工具可大幅降低实现复杂度,建议在生产环境中优先采用。

http://www.dtcms.com/a/323539.html

相关文章:

  • Redis 持久化策略深度剖析:从原理到实战,守护数据不丢失
  • 基于 InfluxDB 的服务器性能监控系统实战(二)
  • [论文阅读] 人工智能 + 软件工程 | Posterior-GRPO:优化代码生成推理过程的新框架
  • Solana上Launchpad混战:新颖性应被重视
  • 云服务器--阿里云OSS(1)【阿里云OSS简单介绍以及环境准备】
  • 论文学习21:Pyramid Scene Parsing Network
  • AG32cpld实现一个UartTx“外设”
  • 莫比乌斯反演学习笔记
  • Qt 元对象系统中的 QMetaObject 类和他的invokeMethod() 函数及其他常见函数应用详解​
  • MoVA:多模态视觉专家混合架构的创新设计与应用实践
  • 【能碳建设2】把“能碳计算”做成可配置、可演示的系统
  • codeforces 补题1
  • FAN5622SX 四通道六通道电流吸收线性LED驱动器,单线数字接口 数字式调光, 2.7 → 5.5 V 直流直流输入, 30mA输出FAN5622S
  • 现代数据加密技术:守护数字世界的无形之盾
  • 供应链需求预测项目如何设定合理的KPI、准确率指标(十四)
  • jxWebUI--输入框
  • M8-11读卡器如何通过RS485转Profinet网关在plc写入从站地址
  • 飞书多维表格搭建设备租赁系统-和保养提醒
  • C++ 虚函数、多重继承、虚基类与RTTI的实现成本剖析
  • 云闪付自动签到脚本
  • 线程池与反射
  • 动态规划(三维)直接按照题目条件
  • 基于STM32H5的循环GPDMA链表使用
  • Redis 事务机制
  • java基础(六)jvm
  • Vue3 路由
  • Chaos Monkey 故障注入工具使用介绍
  • Day37--动态规划--52. 携带研究材料(卡码网),518. 零钱兑换 II,377. 组合总和 Ⅳ,57. 爬楼梯(卡码网)
  • Web前端之 ECMAScript6
  • 【ros_humble】3.人脸检测python(服务通讯和参数通讯介绍)