Java高级全套教程(十三)------ 分布式锁超详细实战详解(原理+三种方案企业级落地)
一、分布式锁核心基础认知
1.1 分布式锁的诞生背景
在传统单体架构中,所有业务代码运行在单个JVM进程 内,针对多线程并发争抢共享资源的问题,可依靠JVM原生锁机制解决,例如 synchronized 重量级锁、ReentrantLock 可重入锁。这类锁的核心生效范围是当前JVM进程内,仅能管控本机多线程的并发竞争。
随着互联网业务迭代,系统逐步升级为微服务集群部署架构 。为了实现高可用、负载均衡,同一业务服务会部署多台实例,每个实例独立运行在不同JVM进程、不同服务器节点中。此时JVM原生锁完全失效:多节点的多个进程会同时争抢同一共享资源(如商品库存、订单编号),进程间无法感知彼此的锁状态,最终引发超卖、数据覆盖、数据不一致等严重问题。
为解决集群环境下跨进程、跨节点的资源竞争问题,分布式锁应运而生。
1.2 分布式锁核心定义
分布式锁是一种跨进程、跨服务器的互斥锁机制,核心作用是管控分布式系统中多个独立进程对同一临界共享资源的访问权限。通过统一的锁规则,保证同一时刻仅有一个进程能操作临界资源,规避并发冲突,保障分布式场景下的数据一致性。
简单来说:本地锁锁线程,分布式锁锁进程。
1.3 分布式锁必备核心特性
企业级分布式锁必须满足四大刚性特性,缺一不可:
-
互斥性(核心):同一时间点,仅允许一个客户端/进程获取锁,杜绝并发争抢。
-
可重入性:同一客户端持有锁后,可重复加锁,避免自身死锁(适配嵌套业务场景)。
-
锁超时释放:防止客户端宕机、网络异常导致锁无法释放,引发全局死锁。
-
高可用、高性能:锁的获取与释放开销极低,且锁服务集群容错,不会因单点故障导致锁机制失效。
1.4 分布式系统CAP理论(锁方案选型依据)
分布式系统三大核心特性:一致性(C)、可用性(A)、分区容错性(P),三者无法同时满足,最多兼容两项。
互联网绝大多数业务场景(订单、库存、支付)均舍弃强一致性 ,优先保障高可用与分区容错性,采用最终一致性方案。所有分布式锁的实现方案,均是基于CAP理论做的取舍适配。
二、业务场景复现:分布式并发超卖问题
2.1 业务场景说明
搭建微服务集群订单系统,模拟高并发秒杀下单场景:多节点服务同时接收用户下单请求,并发扣减商品库存。无分布式锁时,会出现库存超卖、订单数据错乱问题。
2.2 技术环境选型
| 技术栈 | 版本 | 用途 |
|---|---|---|
| JDK | 1.8 | 运行环境 |
| SpringBoot | 2.6.3 | 项目框架 |
| MySQL | 5.7 | 业务数据存储 |
| Mybatis Plus | 3.5.2 | 数据库ORM操作 |
| Redis | 3.0+ | 高性能分布式锁实现 |
| Zookeeper | 3.6.3 | 高可靠分布式锁实现 |
| Nginx | 最新稳定版 | 请求负载均衡 |
| JMeter | 最新稳定版 | 高并发压测 |
2.3 业务数据库表设计
2.3.1 商品库存表
存储商品基础信息与库存数据,新增version字段用于数据库乐观锁实现
sql
CREATE TABLE `product` (
`id` int(11) NOT NULL COMMENT '商品主键ID',
`product_name` varchar(255) DEFAULT NULL COMMENT '商品名称',
`price` decimal(10,2) DEFAULT NULL COMMENT '商品单价',
`count` bigint(50) UNSIGNED DEFAULT NULL COMMENT '剩余库存数量',
`product_desc` varchar(255) DEFAULT NULL COMMENT '商品描述',
`version` int(11) NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品库存表';
-- 初始化测试数据(库存5件)
INSERT INTO `product` VALUES (1001,'联想拯救者笔记本',100.00,5,'高性能游戏本',0);
2.3.2 订单主表
sql
CREATE TABLE `t_order` (
`id` varchar(255) NOT NULL COMMENT '订单唯一ID',
`order_status` int(1) DEFAULT NULL COMMENT '订单状态 1-待支付 2-已支付 3-已取消',
`receiver_name` varchar(255) DEFAULT NULL COMMENT '收货人姓名',
`receiver_mobile` varchar(255) DEFAULT NULL COMMENT '收货人手机号',
`order_amount` decimal(10,2) DEFAULT NULL COMMENT '订单总金额',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '订单创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单主表';
2.3.3 订单商品关联表
sql
CREATE TABLE `order_item` (
`id` varchar(255) NOT NULL COMMENT '关联主键ID',
`order_id` varchar(36) DEFAULT NULL COMMENT '关联订单ID',
`produce_id` int(11) DEFAULT NULL COMMENT '关联商品ID',
`purchase_price` decimal(10,2) DEFAULT NULL COMMENT '下单单价',
`purchase_num` int(11) DEFAULT NULL COMMENT '下单数量',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单商品关联表';
2.4 项目初始化搭建
2.4.1 核心依赖引入
xml
<?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.6.3</version>
<relativePath/>
</parent>
<groupId>com.distribute.lock</groupId>
<artifactId>distribute-lock-demo</artifactId>
<version>1.0.0</version>
<name>distribute-lock-demo</name>
<description>分布式锁实战项目</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<mybatis.plus.version>3.5.2</mybatis.plus.version>
<redisson.version>3.17.2</redisson.version>
<curator.version>5.2.0</curator.version>
</properties>
<dependencies>
<!-- SpringBoot Web核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<!-- Zookeeper Curator客户端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</project>
2.4.2 项目配置文件(application.yml)
yaml
server:
port: 9091
spring:
application:
name: distribute-lock-service
# 数据库配置
datasource:
url: jdbc:mysql://192.168.66.100:3306/distribute?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Redis配置
redis:
host: 127.0.0.1
port: 6379
timeout: 10000ms
# Mybatis Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: auto
update-strategy: not_null
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.4.3 项目启动类
java
package com.distribute.lock;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
@MapperScan("com.distribute.lock.mapper")
public class LockApplication {
public static void main(String[] args) {
SpringApplication.run(LockApplication.class, args);
log.info("===== 分布式锁实战项目启动成功 =====");
}
}
2.4.4 代码生成工具类
java
package com.distribute.lock.util;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
public static void main(String[] args) {
// 数据源配置
FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
// 全局配置
.globalConfig(builder -> {
builder.author("distribute-lock")
.commentDate("yyyy-MM-dd")
.outputDir(System.getProperty("user.dir") + "/src/main/java/")
.fileOverride();
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.distribute.lock")
.entity("entity")
.mapper("mapper")
.service("service")
.controller("controller");
})
// 策略配置
.strategyConfig(builder -> {
List<String> tableList = Arrays.asList("product", "t_order", "order_item");
builder.addInclude(tableList)
.entityBuilder()
.enableLombok()
.naming(NamingStrategy.underline_to_camel)
.columnNaming(NamingStrategy.underline_to_camel);
})
.execute();
}
}
2.5 基础下单业务实现(无锁版,存在超卖问题)
2.5.1 订单业务接口
java
package com.distribute.lock.service;
import com.distribute.lock.entity.TOrder;
import com.baomidou.mybatisplus.extension.service.IService;
public interface OrderService extends IService<TOrder> {
/**
* 无锁下单接口
* @param productId 商品ID
* @param buyNum 购买数量
* @return 订单ID
*/
String createOrderNoLock(Integer productId, Integer buyNum);
}
2.5.2 业务实现类(核心超卖复现代码)
java
package com.distribute.lock.service.impl;
import com.distribute.lock.entity.OrderItem;
import com.distribute.lock.entity.Product;
import com.distribute.lock.entity.TOrder;
import com.distribute.lock.mapper.OrderItemMapper;
import com.distribute.lock.mapper.ProductMapper;
import com.distribute.lock.mapper.TOrderMapper;
import com.distribute.lock.service.OrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.UUID;
@Service
public class OrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements OrderService {
@Resource
private ProductMapper productMapper;
@Resource
private OrderItemMapper orderItemMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrderNoLock(Integer productId, Integer buyNum) {
// 1. 查询商品库存信息
Product product = productMapper.selectById(productId);
// 2. 校验商品是否存在
if (null == product) {
throw new RuntimeException("当前购买商品不存在");
}
// 3. 校验库存是否充足(并发漏洞:多线程同时通过校验)
if (buyNum > product.getCount()) {
throw new RuntimeException("商品库存不足,当前剩余库存:" + product.getCount());
}
// 4. 扣减库存
product.setCount(product.getCount() - buyNum);
productMapper.updateById(product);
// 5. 创建订单数据
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("测试用户");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
// 6. 创建订单商品关联数据
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
}
}
2.5.3 订单控制层接口
java
package com.distribute.lock.controller;
import com.distribute.lock.service.OrderService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderService orderService;
@PostMapping("/create/noLock")
public String createOrderNoLock(@RequestParam Integer productId, @RequestParam Integer buyNum) {
return "订单创建成功,订单ID:" + orderService.createOrderNoLock(productId, buyNum);
}
}
2.6 集群压测复现超卖问题
-
服务集群部署:修改启动端口,分别启动9090、9091两个服务实例
-
Nginx负载均衡配置:统一入口转发请求
nginx
upstream order_server {
server localhost:9090;
server localhost:9091;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://order_server;
}
}
-
JMeter压测配置:设置100个并发线程,循环请求下单接口
-
问题结果:初始库存5件,压测后库存为负数,出现严重超卖,证明单机锁无法解决集群并发问题
三、三大分布式锁方案企业级落地实现
3.1 方案一:数据库分布式锁(悲观锁+乐观锁)
3.1.1 数据库悲观锁(FOR UPDATE)
实现原理
基于数据库行级排他锁实现,通过 SELECT ... FOR UPDATE 语句对查询数据加锁。事务未提交前,其他所有事务无法查询、修改该行数据,强制实现进程互斥,适配分布式并发场景。
核心代码实现
1、新增Mapper查询方法(加行锁)
java
package com.distribute.lock.mapper;
import com.distribute.lock.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface ProductMapper extends BaseMapper<Product> {
/**
* 悲观锁查询商品数据(行级锁)
*/
@Select("select * from product where id = #{productId} for update")
Product selectProductByLock(@Param("productId") Integer productId);
}
2、悲观锁下单业务实现
java
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrderPessimisticLock(Integer productId, Integer buyNum) {
// 悲观锁查询,锁定该行数据
Product product = productMapper.selectProductByLock(productId);
if (null == product) {
throw new RuntimeException("商品不存在");
}
if (buyNum > product.getCount()) {
throw new RuntimeException("商品库存不足");
}
// 扣减库存
product.setCount(product.getCount() - buyNum);
productMapper.updateById(product);
// 创建订单、关联数据(逻辑同上)
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("悲观锁测试");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
}
优缺点分析
优点:无需引入中间件、实现简单、数据一致性强
缺点:数据库IO开销大、并发性能极低、容易产生锁等待、死锁风险,仅适用于低并发场景
3.1.2 数据库乐观锁(版本号机制)
实现原理
无锁设计,通过版本号字段实现并发控制。查询数据时获取version版本号,更新数据时校验version是否一致,一致则更新并版本号+1,不一致则说明数据已被修改,更新失败,支持重试机制。
核心代码实现
1、新增库存更新SQL(版本号校验)
xml
<update id="updateStockByVersion">
UPDATE product
SET count = count - #{buyNum}, version = version + 1
WHERE id = #{productId} AND version = #{version} AND count >= #{buyNum}
</update>
2、乐观锁重试下单逻辑
java
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrderOptimisticLock(Integer productId, Integer buyNum) {
// 最大重试次数3次
int maxRetry = 3;
int result = 0;
while (maxRetry > 0 && result == 0) {
// 查询商品数据及版本号
Product product = productMapper.selectById(productId);
if (null == product) {
throw new RuntimeException("商品不存在");
}
if (buyNum > product.getCount()) {
throw new RuntimeException("库存不足,无法下单");
}
// 带版本号更新库存
result = productMapper.updateStockByVersion(productId, buyNum, product.getVersion());
maxRetry--;
}
if (result == 0) {
throw new RuntimeException("下单失败,当前商品并发抢购过多,请重试");
}
// 后续订单创建逻辑
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("乐观锁测试");
order.setReceiverMobile("18587781068");
order.setOrderAmount(productMapper.selectById(productId).getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(productMapper.selectById(productId).getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
}
优缺点分析
优点:无锁阻塞、并发吞吐量高、性能优于悲观锁
缺点:高并发下大量请求重试、浪费系统资源、仅适用于多读少写场景
3.2 方案二:Redis分布式锁(原生+Redisson企业级方案)
3.2.1 原生Redis分布式锁实现原理
基于Redis SET NX EX 原子命令实现:NX保证key不存在才创建(互斥),EX设置key过期时间(防死锁)。相比数据库锁,纯内存操作,并发性能极强。
3.2.2 原生Redis锁完整实现(解决误删、超时问题)
java
package com.distribute.lock.util;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
@Resource
private StringRedisTemplate stringRedisTemplate;
// 锁key前缀
private static final String LOCK_PREFIX = "product:lock:";
// 锁超时时间
private static final long LOCK_EXPIRE_TIME = 30;
/**
* 获取分布式锁
* @param lockKey 锁唯一标识
* @param lockValue 线程唯一标识(防止误删)
* @return 是否加锁成功
*/
public boolean tryLock(String lockKey, String lockValue) {
return stringRedisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
}
/**
* 释放锁(校验当前线程持有锁,避免误删)
* @param lockKey 锁标识
* @param lockValue 线程标识
* @return 是否释放成功
*/
public boolean unLock(String lockKey, String lockValue) {
String key = LOCK_PREFIX + lockKey;
// 原子校验+删除
if (lockValue.equals(stringRedisTemplate.opsForValue().get(key))) {
stringRedisTemplate.delete(key);
return true;
}
return false;
}
}
Redis锁下单业务实现
java
@Override
public String createOrderRedisLock(Integer productId, Integer buyNum) {
String lockValue = UUID.randomUUID().toString().replace("-", "");
boolean lock = redisLockUtil.tryLock(productId.toString(), lockValue);
// 加锁失败直接返回
if (!lock) {
return "系统繁忙,商品抢购中,请稍后重试";
}
try {
// 执行业务逻辑
Product product = productMapper.selectById(productId);
if (null == product || buyNum > product.getCount()) {
throw new RuntimeException("商品库存不足");
}
product.setCount(product.getCount() - buyNum);
productMapper.updateById(product);
// 创建订单数据
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("Redis锁测试");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
} finally {
// 释放锁
redisLockUtil.unLock(productId.toString(), lockValue);
}
}
3.2.3 原生Redis锁存在的核心问题
-
不可重入:同一线程无法重复加锁,嵌套业务会死锁
-
锁超时失效:业务执行超时,锁自动释放,引发并发问题
-
无重试机制:加锁失败直接返回,用户体验差
3.2.4 Redisson企业级分布式锁(完美解决原生问题)
Redisson核心优势
Redisson是Redis官方推荐的分布式锁客户端,底层封装了可重入锁、锁续命、自动重试、公平锁、读写锁等企业级特性,适配生产环境高并发场景。
Redisson配置类
java
package com.distribute.lock.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单机Redis配置
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
Redisson可重入锁实战代码
java
@Override
public String createOrderRedissonLock(Integer productId, Integer buyNum) {
// 获取可重入锁
String lockKey = "product:redisson:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,等待3秒,自动释放15秒
boolean tryLock = lock.tryLock(3, 15, TimeUnit.SECONDS);
if (!tryLock) {
return "商品抢购繁忙,请稍后重试";
}
// 核心业务逻辑
Product product = productMapper.selectById(productId);
if (null == product || buyNum > product.getCount()) {
throw new RuntimeException("商品库存不足");
}
product.setCount(product.getCount() - buyNum);
productMapper.updateById(product);
// 创建订单
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("Redisson锁测试");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("加锁失败");
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
3.3 方案三:Zookeeper分布式锁(高可靠方案)
3.3.1 实现核心原理
利用Zookeeper临时顺序节点实现分布式锁:
-
所有客户端在统一锁路径下创建临时顺序节点
-
序号最小的节点获取锁,其余节点监听前序节点
-
锁释放/客户端宕机,临时节点自动删除,下一个节点收到通知并获取锁
核心特性:无死锁、公平锁机制、可靠性极高
3.3.2 Curator客户端配置
java
package com.distribute.lock.config;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZookeeperConfig {
@Bean
public CuratorFramework curatorFramework() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
return client;
}
}
3.3.3 Zookeeper分布式锁实战代码
java
@Override
public String createOrderZkLock(Integer productId, Integer buyNum) throws Exception {
// 定义锁路径
String lockPath = "/distribute/lock/product/" + productId;
// 创建可重入分布式锁
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, lockPath);
try {
// 尝试获取锁,超时3秒
boolean acquire = mutex.acquire(3, TimeUnit.SECONDS);
if (!acquire) {
return "系统繁忙,请稍后重试";
}
// 执行业务逻辑
Product product = productMapper.selectById(productId);
if (null == product || buyNum > product.getCount()) {
throw new RuntimeException("商品库存不足");
}
product.setCount(product.getCount() - buyNum);
productMapper.updateById(product);
// 订单创建逻辑
TOrder order = new TOrder();
order.setId(UUID.randomUUID().replace("-", ""));
order.setOrderStatus(1);
order.setReceiverName("Zookeeper锁测试");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(buyNum)));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setId(UUID.randomUUID().replace("-", ""));
orderItem.setOrderId(order.getId());
orderItem.setProduceId(productId);
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(buyNum);
orderItemMapper.insert(orderItem);
return order.getId();
} finally {
// 释放锁
if (mutex.isAcquiredInThisProcess()) {
mutex.release();
}
}
}
四、三大分布式锁方案全方位对比 & 选型策略
4.1 方案优缺点汇总
4.1.1 数据库锁
优点:零中间件依赖、接入简单、数据一致性强、无需维护额外组件
缺点:并发性能极差、存在锁阻塞、数据库压力大、高并发场景不适用
4.1.2 Redis锁(Redisson)
优点:纯内存操作、性能最优、支持可重入、锁续命、自动重试、适配绝大多数高并发业务
缺点:极端场景存在短暂数据不一致风险、需要维护Redis中间件
4.1.3 Zookeeper锁
优点:可靠性最高、无死锁、公平锁机制、适配金融、支付等极致一致性场景
缺点:性能低于Redis、节点监听开销大、组件较重、维护成本高
4.2 核心维度选型排序
-
并发性能:Redis > Zookeeper > 数据库
-
数据可靠性:Zookeeper > Redis > 数据库
-
实现复杂度:数据库 < Redis < Zookeeper
-
运维成本:数据库 < Redis < Zookeeper
4.3 企业级场景选型建议
-
普通电商、秒杀、高并发业务 :优先选择 Redisson分布式锁(性能与可靠性平衡最佳)
-
金融、支付、清算等极致一致性业务 :优先选择 Zookeeper分布式锁
-
低并发、内部系统、无中间件场景 :选择 数据库乐观锁