
分布式锁就像共享办公区的唯一会议室门禁:同一时间只能有一个团队持有门禁权限使用会议室;使用完必须归还权限;如果持有门禁的人突然离职,门禁必须能自动重置,不会一直被占用;同时要保证,只有持有门禁的人才能开门,别人不能随便打开。
在单体应用中,我们通过JVM自带的synchronized或ReentrantLock就能实现线程间的并发控制,保证同一时间只有一个线程操作共享资源。但在分布式架构下,应用会被部署到多个服务器节点,形成多个独立的JVM进程,本地锁只能控制本节点内的线程,无法跨节点、跨进程实现互斥。
最典型的场景就是电商商品库存扣减:假设商品库存仅剩10件,同时有100个用户发起下单请求,订单服务集群部署了3个节点。如果仅使用本地锁,3个节点的线程可以同时进入库存扣减的临界区,最终会导致超卖,造成资损。
分布式锁的本质,就是实现跨进程、跨机器、跨网络的互斥控制,保证同一时间只有一个客户端可以持有锁,操作共享资源。
评判一个分布式锁方案是否合格、是否适合生产环境,核心看以下8个核心准则,这也是所有分布式锁实现的底层逻辑根基:

MySQL分布式锁是实现成本最低的方案,无需额外部署中间件,适合低并发、已有MySQL基础设施的项目,核心有三种实现方式。
这是MySQL分布式锁最成熟的实现方式,核心依靠InnoDB的唯一索引约束保证互斥性,通过过期时间防止死锁,通过持有者标识保证锁释放安全,支持可重入性。
CREATE TABLE`distributed_lock` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`lock_key`varchar(128) NOTNULLCOMMENT'锁的唯一标识',
`lock_holder`varchar(64) NOTNULLCOMMENT'锁持有者唯一标识',
`reentrant_count`intNOTNULLDEFAULT'1'COMMENT'重入次数',
`expire_time` datetime NOTNULLCOMMENT'锁过期时间',
`create_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
PRIMARY KEY (`id`),
UNIQUEKEY`uk_lock_key` (`lock_key`) COMMENT'锁key唯一索引,保证互斥性'
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='分布式锁表';
Maven核心依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.52</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
实体类
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("distributed_lock")
@Schema(description = "分布式锁实体")
publicclass DistributedLock {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
@TableField("lock_key")
@Schema(description = "锁的唯一标识")
private String lockKey;
@TableField("lock_holder")
@Schema(description = "锁持有者唯一标识")
private String lockHolder;
@TableField("reentrant_count")
@Schema(description = "重入次数")
private Integer reentrantCount;
@TableField("expire_time")
@Schema(description = "锁过期时间")
private LocalDateTime expireTime;
@TableField("create_time")
@Schema(description = "创建时间")
private LocalDateTime createTime;
@TableField("update_time")
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
Mapper接口
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.DistributedLock;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DistributedLockMapper extends BaseMapper<DistributedLock> {
}
核心锁服务类
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jam.demo.entity.DistributedLock;
import com.jam.demo.mapper.DistributedLockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* MySQL分布式锁服务
*
* @author ken
*/
@Slf4j
@Service
publicclass MysqlDistributedLockService {
privatefinal DistributedLockMapper lockMapper;
privatefinal TransactionTemplate transactionTemplate;
privatestaticfinallong DEFAULT_EXPIRE_SECONDS = 30L;
privatestaticfinal ThreadLocal<String> HOLDER_ID_LOCAL = new ThreadLocal<>();
public MysqlDistributedLockService(DistributedLockMapper lockMapper, TransactionTemplate transactionTemplate) {
this.lockMapper = lockMapper;
this.transactionTemplate = transactionTemplate;
}
/**
* 获取锁持有者唯一标识
*
* @return 持有者标识
*/
private String getHolderId() {
String holderId = HOLDER_ID_LOCAL.get();
if (!StringUtils.hasText(holderId)) {
holderId = UUID.randomUUID() + "-" + Thread.currentThread().getId();
HOLDER_ID_LOCAL.set(holderId);
}
return holderId;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryLock(String lockKey) {
return tryLock(lockKey, DEFAULT_EXPIRE_SECONDS);
}
/**
* 尝试获取分布式锁(指定过期时间)
*
* @param lockKey 锁的唯一标识
* @param expireSeconds 锁过期时间(秒)
* @return 获取结果
*/
public boolean tryLock(String lockKey, long expireSeconds) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String holderId = getHolderId();
LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireSeconds);
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
LambdaQueryWrapper<DistributedLock> queryWrapper = new LambdaQueryWrapper<DistributedLock>()
.eq(DistributedLock::getLockKey, lockKey);
DistributedLock existLock = lockMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(existLock)) {
DistributedLock newLock = new DistributedLock();
newLock.setLockKey(lockKey);
newLock.setLockHolder(holderId);
newLock.setExpireTime(expireTime);
newLock.setReentrantCount(1);
try {
lockMapper.insert(newLock);
returntrue;
} catch (Exception e) {
log.debug("锁插入冲突,获取失败: {}", lockKey);
returnfalse;
}
}
if (holderId.equals(existLock.getLockHolder())) {
LambdaUpdateWrapper<DistributedLock> updateWrapper = new LambdaUpdateWrapper<DistributedLock>()
.eq(DistributedLock::getLockKey, lockKey)
.set(DistributedLock::getReentrantCount, existLock.getReentrantCount() + 1)
.set(DistributedLock::getExpireTime, expireTime);
lockMapper.update(null, updateWrapper);
returntrue;
}
if (existLock.getExpireTime().isBefore(LocalDateTime.now())) {
LambdaUpdateWrapper<DistributedLock> updateWrapper = new LambdaUpdateWrapper<DistributedLock>()
.eq(DistributedLock::getLockKey, lockKey)
.eq(DistributedLock::getLockHolder, existLock.getLockHolder())
.set(DistributedLock::getLockHolder, holderId)
.set(DistributedLock::getReentrantCount, 1)
.set(DistributedLock::getExpireTime, expireTime);
int updateCount = lockMapper.update(null, updateWrapper);
return updateCount > 0;
}
returnfalse;
}
});
}
/**
* 释放分布式锁
*
* @param lockKey 锁的唯一标识
* @return 释放结果
*/
public boolean releaseLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String holderId = getHolderId();
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
LambdaQueryWrapper<DistributedLock> queryWrapper = new LambdaQueryWrapper<DistributedLock>()
.eq(DistributedLock::getLockKey, lockKey);
DistributedLock existLock = lockMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(existLock)) {
HOLDER_ID_LOCAL.remove();
returntrue;
}
if (!holderId.equals(existLock.getLockHolder())) {
log.error("非锁持有者,无法释放锁: {}", lockKey);
returnfalse;
}
int currentCount = existLock.getReentrantCount() - 1;
if (currentCount > 0) {
LambdaUpdateWrapper<DistributedLock> updateWrapper = new LambdaUpdateWrapper<DistributedLock>()
.eq(DistributedLock::getLockKey, lockKey)
.set(DistributedLock::getReentrantCount, currentCount);
lockMapper.update(null, updateWrapper);
returntrue;
}
lockMapper.delete(queryWrapper);
HOLDER_ID_LOCAL.remove();
returntrue;
}
});
}
}
业务使用示例
package com.jam.demo.controller;
import com.jam.demo.service.MysqlDistributedLockService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@RestController
@RequestMapping("/order")
@Tag(name = "订单管理", description = "订单相关接口")
publicclass OrderController {
privatefinal MysqlDistributedLockService distributedLockService;
public OrderController(MysqlDistributedLockService distributedLockService) {
this.distributedLockService = distributedLockService;
}
@PostMapping("/create")
@Operation(summary = "创建订单", description = "分布式锁控制库存扣减")
public String createOrder(
@Parameter(description = "商品ID", required = true) @RequestParam String productId,
@Parameter(description = "购买数量", required = true) @RequestParam Integer num) {
String lockKey = "order:stock:" + productId;
boolean lockSuccess = distributedLockService.tryLock(lockKey, 30);
if (!lockSuccess) {
return"系统繁忙,请稍后重试";
}
try {
log.info("获取锁成功,开始执行业务逻辑,商品ID:{}", productId);
// 库存扣减、订单创建等核心业务逻辑
return"订单创建成功";
} finally {
distributedLockService.releaseLock(lockKey);
}
}
}
select * from distributed_lock where lock_key = ? for update;加锁,事务提交后释放锁。缺点是长事务会占用数据库连接,高并发下性能极差,容易出现死锁。update table set stock = stock -1, version = version +1 where id = ? and version = ?实现,适合单表更新操作,不适合作为通用分布式锁,需要修改业务表,重试成本高。Redis分布式锁是互联网项目最常用的方案,依托Redis单线程模型的原子性命令,实现高性能的互斥控制,单机QPS可达10万以上。
Redis的单线程模型保证所有命令都是原子执行的,核心依靠SET命令的扩展参数实现互斥与防死锁:
NX:Only set the key if it does not already exist,只有key不存在时才设置成功,保证互斥性。PX milliseconds:Set the specified expire time, in milliseconds,设置key的过期时间,保证客户端宕机后锁能自动释放,防止死锁。核心原子命令
SET lock_key unique_value NX PX 30000
释放锁的原子Lua脚本释放锁必须通过Lua脚本实现原子操作,避免出现“判断value相等后、删除key前,锁过期被其他客户端获取,导致误释放”的问题。
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
Maven核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.2.4</version>
</dependency>
核心锁服务类
package com.jam.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Redis原生SETNX分布式锁服务
*
* @author ken
*/
@Slf4j
@Service
publicclass RedisNativeLockService {
privatefinal StringRedisTemplate stringRedisTemplate;
privatestaticfinallong DEFAULT_EXPIRE_MS = 30000L;
privatestaticfinal ThreadLocal<String> HOLDER_ID_LOCAL = new ThreadLocal<>();
privatestaticfinal DefaultRedisScript<Long> RELEASE_LOCK_SCRIPT;
static {
RELEASE_LOCK_SCRIPT = new DefaultRedisScript<>();
RELEASE_LOCK_SCRIPT.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end");
RELEASE_LOCK_SCRIPT.setResultType(Long.class);
}
public RedisNativeLockService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
private String getHolderId() {
String holderId = HOLDER_ID_LOCAL.get();
if (!StringUtils.hasText(holderId)) {
holderId = UUID.randomUUID() + "-" + Thread.currentThread().getId();
HOLDER_ID_LOCAL.set(holderId);
}
return holderId;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryLock(String lockKey) {
return tryLock(lockKey, DEFAULT_EXPIRE_MS, TimeUnit.MILLISECONDS);
}
/**
* 尝试获取分布式锁(指定过期时间)
*
* @param lockKey 锁的唯一标识
* @param expire 过期时间
* @param timeUnit 时间单位
* @return 获取结果
*/
public boolean tryLock(String lockKey, long expire, TimeUnit timeUnit) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String holderId = getHolderId();
Boolean result = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, holderId, expire, timeUnit);
return Boolean.TRUE.equals(result);
}
/**
* 释放分布式锁
*
* @param lockKey 锁的唯一标识
* @return 释放结果
*/
public boolean releaseLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String holderId = getHolderId();
Long result = stringRedisTemplate.execute(
RELEASE_LOCK_SCRIPT,
Collections.singletonList(lockKey),
holderId
);
HOLDER_ID_LOCAL.remove();
return Long.valueOf(1L).equals(result);
}
}
Redisson是Redis官方推荐的Java客户端,封装了生产级的分布式锁实现,解决了原生SETNX方案的所有缺陷,是互联网高并发场景的首选方案。

Maven核心依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.0</version>
</dependency>
核心锁服务类
package com.jam.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* Redisson分布式锁服务
*
* @author ken
*/
@Slf4j
@Service
publicclass RedissonLockService {
privatefinal RedissonClient redissonClient;
privatestaticfinallong DEFAULT_WAIT_TIME = 3L;
privatestaticfinallong DEFAULT_LEASE_TIME = 30L;
public RedissonLockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
/**
* 尝试获取可重入锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁中断: {}", lockKey, e);
returnfalse;
}
}
/**
* 尝试获取可重入锁(自定义超时时间)
*
* @param lockKey 锁的唯一标识
* @param waitTime 最大等待时间
* @param leaseTime 锁持有时间
* @param timeUnit 时间单位
* @return 获取结果
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, timeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁中断: {}", lockKey, e);
returnfalse;
}
}
/**
* 获取公平锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryFairLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
RLock fairLock = redissonClient.getFairLock(lockKey);
try {
return fairLock.tryLock(DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取公平锁中断: {}", lockKey, e);
returnfalse;
}
}
/**
* 释放锁
*
* @param lockKey 锁的唯一标识
*/
public void releaseLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
RLock lock = redissonClient.getLock(lockKey);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
红锁实现示例
package com.jam.demo.service;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* Redisson红锁服务
*
* @author ken
*/
@Service
publicclass RedissonRedLockService {
privatefinal RedissonClient redissonClient1;
privatefinal RedissonClient redissonClient2;
privatefinal RedissonClient redissonClient3;
privatefinal RedissonClient redissonClient4;
privatefinal RedissonClient redissonClient5;
public RedissonRedLockService(RedissonClient redissonClient1, RedissonClient redissonClient2, RedissonClient redissonClient3, RedissonClient redissonClient4, RedissonClient redissonClient5) {
this.redissonClient1 = redissonClient1;
this.redissonClient2 = redissonClient2;
this.redissonClient3 = redissonClient3;
this.redissonClient4 = redissonClient4;
this.redissonClient5 = redissonClient5;
}
public boolean tryRedLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) {
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
RLock lock4 = redissonClient4.getLock(lockKey);
RLock lock5 = redissonClient5.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);
try {
return redLock.tryLock(waitTime, leaseTime, timeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
returnfalse;
}
}
public void releaseRedLock(String lockKey) {
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
RLock lock4 = redissonClient4.getLock(lockKey);
RLock lock5 = redissonClient5.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);
if (redLock.isHeldByCurrentThread()) {
redLock.unlock();
}
}
}
ZooKeeper分布式锁依托ZAB一致性协议,实现强一致性的互斥控制,适合金融级、对锁的可靠性有极致要求的场景。
生产环境推荐使用Apache Curator框架,它封装了成熟的分布式锁实现,处理了会话中断、重连、Watcher重复注册等所有边界问题。
Maven核心依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.6.0</version>
</dependency>
核心锁服务类
package com.jam.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* ZooKeeper分布式锁服务
*
* @author ken
*/
@Slf4j
@Service
publicclass ZookeeperLockService {
privatefinal CuratorFramework curatorFramework;
privatestaticfinal String LOCK_ROOT_PATH = "/distributed_lock";
privatestaticfinallong DEFAULT_WAIT_TIME = 3L;
public ZookeeperLockService(CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String lockPath = LOCK_ROOT_PATH + "/" + lockKey;
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, lockPath);
try {
return mutex.acquire(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("获取ZK锁异常: {}", lockKey, e);
returnfalse;
}
}
/**
* 尝试获取分布式锁(自定义等待时间)
*
* @param lockKey 锁的唯一标识
* @param waitTime 最大等待时间
* @param timeUnit 时间单位
* @return 获取结果
*/
public boolean tryLock(String lockKey, long waitTime, TimeUnit timeUnit) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String lockPath = LOCK_ROOT_PATH + "/" + lockKey;
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, lockPath);
try {
return mutex.acquire(waitTime, timeUnit);
} catch (Exception e) {
log.error("获取ZK锁异常: {}", lockKey, e);
returnfalse;
}
}
/**
* 释放分布式锁
*
* @param lockKey 锁的唯一标识
*/
public void releaseLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
String lockPath = LOCK_ROOT_PATH + "/" + lockKey;
InterProcessMutex mutex = new InterProcessMutex(curatorFramework, lockPath);
try {
if (mutex.isAcquiredInThisProcess()) {
mutex.release();
}
} catch (Exception e) {
log.error("释放ZK锁异常: {}", lockKey, e);
}
}
}
Etcd是基于Raft一致性协议的分布式键值存储,是云原生场景的标准组件,分布式锁实现兼顾强一致性与性能,适合K8s生态下的项目。
Maven核心依赖
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>0.7.7</version>
</dependency>
核心锁服务类
package com.jam.demo.service;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* Etcd分布式锁服务
*
* @author ken
*/
@Slf4j
@Service
publicclass EtcdLockService {
privatefinal Client etcdClient;
privatestaticfinallong DEFAULT_LEASE_TTL = 30L;
privatestaticfinallong DEFAULT_WAIT_TIME = 3L;
public EtcdLockService(Client etcdClient) {
this.etcdClient = etcdClient;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的唯一标识
* @return 获取结果
*/
public boolean tryLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
Lock lockClient = etcdClient.getLockClient();
Lease leaseClient = etcdClient.getLeaseClient();
try {
long leaseId = leaseClient.grant(DEFAULT_LEASE_TTL).get().getID();
lockClient.lock(lockKey.getBytes(), leaseId).get(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
returntrue;
} catch (Exception e) {
log.error("获取Etcd锁异常: {}", lockKey, e);
returnfalse;
}
}
/**
* 释放分布式锁
*
* @param lockKey 锁的唯一标识
*/
public void releaseLock(String lockKey) {
if (!StringUtils.hasText(lockKey)) {
thrownew IllegalArgumentException("锁key不能为空");
}
Lock lockClient = etcdClient.getLockClient();
try {
lockClient.unlock(lockKey.getBytes()).get();
} catch (Exception e) {
log.error("释放Etcd锁异常: {}", lockKey, e);
}
}
}
对比维度 | MySQL悲观锁 | MySQL唯一索引锁 | Redis原生SETNX | Redisson分布式锁 | ZooKeeper(Curator) | Etcd |
|---|---|---|---|---|---|---|
核心互斥性保障 | InnoDB行锁 | 唯一索引约束 | Redis单线程原子命令 | Lua脚本+Hash结构 | 临时顺序节点+Watcher | Raft+Revision+Lease |
强一致性 | 是 | 是 | 否(异步复制) | 否(普通集群)/是(红锁) | 是(ZAB协议) | 是(Raft协议) |
可重入性 | 不支持 | 手动实现 | 不支持 | 原生支持 | 原生支持 | 原生支持 |
防死锁能力 | 弱(长事务) | 中(过期时间) | 中(过期时间) | 强(看门狗+过期时间) | 强(临时节点自动删除) | 强(租约自动过期) |
锁释放安全性 | 中(事务提交释放) | 中(手动判断持有者) | 中(Lua脚本) | 强(Lua脚本校验持有者) | 强(只能删除自己的节点) | 强(只能操作自己的租约key) |
锁续期能力 | 不支持 | 手动实现 | 手动实现 | 原生支持(看门狗) | 无需(会话保活) | 原生支持(租约续期) |
公平性 | 不支持 | 不支持 | 不支持 | 原生支持 | 原生支持(顺序节点) | 原生支持(Revision顺序) |
阻塞获取能力 | 不支持(轮询) | 不支持(轮询) | 不支持(轮询) | 原生支持(发布订阅) | 原生支持(Watcher) | 原生支持(Watch) |
单机QPS性能 | 低(<1000) | 低(<2000) | 高(>10W) | 高(>8W) | 中(<1W) | 中高(<5W) |
高可用保障 | 主从/MGR | 主从/MGR | 哨兵/Cluster | 哨兵/Cluster/红锁 | 集群过半机制 | 集群过半机制 |
部署成本 | 低(已有MySQL) | 低(已有MySQL) | 中(需部署Redis) | 中(需部署Redis) | 高(需部署ZK集群) | 高(需部署Etcd集群) |
适用场景 | 低并发、短事务 | 低并发、无额外中间件 | 简单场景、低并发 | 高并发、互联网通用场景 | 强一致性、金融级场景 | 云原生、K8s生态场景 |

问题:业务执行时间超过锁的预设过期时间,锁被提前释放,其他客户端获取到锁,导致并发问题与数据不一致。解决方案:使用Redisson看门狗机制实现自动续期;评估业务最长执行时间,设置合理的过期时间,预留2-3倍的冗余;禁止使用无过期时间的锁。
问题:客户端A释放了客户端B持有的锁,导致互斥性失效。解决方案:必须为每个锁设置唯一的持有者标识,释放锁时必须校验持有者身份;释放锁必须通过原子操作(Lua脚本、事务)实现,禁止直接删除锁;只能由持有锁的线程释放锁。
问题:锁释放后,事务还未提交,其他客户端获取到锁,读取到未提交的数据,出现脏读与更新丢失。解决方案:严格控制时序,必须在事务提交之后再释放锁;优先使用编程式事务,手动控制事务提交与锁释放的顺序。
问题:Redis主从集群中,主节点宕机,异步复制导致锁数据未同步到从节点,从节点升级为主后,原锁数据丢失,多个客户端同时获取到锁。解决方案:强一致性场景使用ZooKeeper/Etcd;Redis场景配置min-replicas-to-write,保证写操作至少同步到1个从节点才返回成功;极端场景使用Redisson红锁方案。
问题:锁粒度太大,导致并发度低,系统性能差;锁粒度太小,导致锁覆盖不全,出现并发漏洞。解决方案:锁的粒度必须与共享资源一一对应,比如商品库存锁使用商品ID作为锁key,禁止使用全商品的全局锁;避免在锁内执行非共享资源的操作,尽量缩短锁的持有时间。
问题:ZooKeeper/Etcd场景中,网络抖动导致客户端与服务端会话断开,会话超时后,锁被自动释放,客户端仍在执行业务,导致并发问题。解决方案:调整会话超时时间,设置合理的心跳间隔;添加会话状态监听,会话断开时立即中断业务执行;配置客户端重连与重试机制。
分布式锁没有银弹,不存在完美的方案,只有最适合业务场景的方案。
所有分布式锁的实现,本质上都是围绕核心设计准则做的取舍:追求高性能,就要牺牲一定的一致性;追求强一致性,就要接受性能的损耗;追求简单低成本,就要接受功能的局限性。
只有真正理解分布式锁的底层原理,才能结合业务场景做出正确的选型,在生产环境中正确使用,避免踩坑,真正解决分布式系统的并发互斥问题。