分布式锁:底层原理全拆解 + 高可用方案深度对比,生产落地全指南

简介: 本文系统解析分布式锁的必要性、八大核心设计准则,并对比MySQL、Redis原生、Redisson、ZooKeeper、Etcd五种主流方案,涵盖原理、代码实现、优缺点及生产选型指南,助你规避超时、误释放、脑裂等典型坑点。

一、为什么需要分布式锁?

分布式锁就像共享办公区的唯一会议室门禁:同一时间只能有一个团队持有门禁权限使用会议室;使用完必须归还权限;如果持有门禁的人突然离职,门禁必须能自动重置,不会一直被占用;同时要保证,只有持有门禁的人才能开门,别人不能随便打开。

在单体应用中,我们通过JVM自带的synchronizedReentrantLock就能实现线程间的并发控制,保证同一时间只有一个线程操作共享资源。但在分布式架构下,应用会被部署到多个服务器节点,形成多个独立的JVM进程,本地锁只能控制本节点内的线程,无法跨节点、跨进程实现互斥。

最典型的场景就是电商商品库存扣减:假设商品库存仅剩10件,同时有100个用户发起下单请求,订单服务集群部署了3个节点。如果仅使用本地锁,3个节点的线程可以同时进入库存扣减的临界区,最终会导致超卖,造成资损。

分布式锁的本质,就是实现跨进程、跨机器、跨网络的互斥控制,保证同一时间只有一个客户端可以持有锁,操作共享资源。

二、分布式锁的核心设计准则

评判一个分布式锁方案是否合格、是否适合生产环境,核心看以下8个核心准则,这也是所有分布式锁实现的底层逻辑根基:

  1. 互斥性:锁的核心本质,同一时间只能有一个客户端持有锁,绝对不能出现多个客户端同时持有锁的情况。
  2. 防死锁:即使持有锁的客户端崩溃、网络中断、服务宕机,锁最终一定能被释放,不会永久占用共享资源。
  3. 可重入性:同一个客户端的同一个线程,在已经持有锁的情况下,可以再次获取同一把锁,不会出现自己把自己锁死的情况。
  4. 高可用:锁服务需要支持集群部署,少数节点宕机不影响锁的正常获取与释放,不会出现锁服务整体不可用。
  5. 高性能:锁的获取与释放操作耗时低,能支撑高并发场景的请求量,不会成为系统的性能瓶颈。
  6. 锁释放安全性:只能由持有锁的客户端释放自己的锁,绝对不能出现客户端A释放了客户端B持有的锁的情况。
  7. 公平性(可选):按照客户端请求锁的顺序依次分配锁,避免部分线程长期抢不到锁,出现饥饿问题。
  8. 锁续期能力(可选):当业务执行时间超过锁的预设过期时间时,可以自动为锁续期,避免业务还没执行完,锁就被提前释放。

三、主流分布式锁方案全拆解

3.1 基于MySQL的分布式锁

MySQL分布式锁是实现成本最低的方案,无需额外部署中间件,适合低并发、已有MySQL基础设施的项目,核心有三种实现方式。

3.1.1 基于唯一索引的排他锁(生产推荐)

这是MySQL分布式锁最成熟的实现方式,核心依靠InnoDB的唯一索引约束保证互斥性,通过过期时间防止死锁,通过持有者标识保证锁释放安全,支持可重入性。

1. 表结构设计(MySQL 8.0)

CREATE TABLE `distributed_lock` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `lock_key` varchar(128) NOT NULL COMMENT '锁的唯一标识',
 `lock_holder` varchar(64) NOT NULL COMMENT '锁持有者唯一标识',
 `reentrant_count` int NOT NULL DEFAULT '1' COMMENT '重入次数',
 `expire_time` datetime NOT NULL COMMENT '锁过期时间',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_lock_key` (`lock_key`) COMMENT '锁key唯一索引,保证互斥性'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='分布式锁表';

2. 核心代码实现

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 = "分布式锁实体")
public class 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
public class MysqlDistributedLockService {
   private final DistributedLockMapper lockMapper;
   private final TransactionTemplate transactionTemplate;
   private static final long DEFAULT_EXPIRE_SECONDS = 30L;
   private static final 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)) {
           throw new 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);
                       return true;
                   } catch (Exception e) {
                       log.debug("锁插入冲突,获取失败: {}", lockKey);
                       return false;
                   }
               }

               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);
                   return true;
               }

               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;
               }

               return false;
           }
       });
   }

   /**
    * 释放分布式锁
    *
    * @param lockKey 锁的唯一标识
    * @return 释放结果
    */

   public boolean releaseLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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();
                   return true;
               }

               if (!holderId.equals(existLock.getLockHolder())) {
                   log.error("非锁持有者,无法释放锁: {}", lockKey);
                   return false;
               }

               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);
                   return true;
               }

               lockMapper.delete(queryWrapper);
               HOLDER_ID_LOCAL.remove();
               return true;
           }
       });
   }
}

业务使用示例

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 = "订单相关接口")
public class OrderController {
   private final 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);
       }
   }
}

3.1.2 其他MySQL锁实现

  1. 悲观锁(for update):基于InnoDB行锁实现,通过select * from distributed_lock where lock_key = ? for update;加锁,事务提交后释放锁。缺点是长事务会占用数据库连接,高并发下性能极差,容易出现死锁。
  2. 乐观锁(版本号机制):通过update table set stock = stock -1, version = version +1 where id = ? and version = ?实现,适合单表更新操作,不适合作为通用分布式锁,需要修改业务表,重试成本高。

3.1.3 高可用与优缺点

  • 高可用方案:采用MySQL MGR集群或主从半同步复制,避免单点故障,保证数据一致性。
  • 优点:实现简单,无需额外中间件,成本极低,天然支持强一致性。
  • 缺点:性能差,受限于数据库IO,高并发下压力大;锁过期依赖系统时间,存在时间同步风险;长连接占用会导致数据库性能下降。

3.2 基于Redis原生SETNX的分布式锁

Redis分布式锁是互联网项目最常用的方案,依托Redis单线程模型的原子性命令,实现高性能的互斥控制,单机QPS可达10万以上。

3.2.1 底层核心原理

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

3.2.2 代码实现

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
public class RedisNativeLockService {
   private final StringRedisTemplate stringRedisTemplate;
   private static final long DEFAULT_EXPIRE_MS = 30000L;
   private static final ThreadLocal<String> HOLDER_ID_LOCAL = new ThreadLocal<>();
   private static final 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)) {
           throw new 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)) {
           throw new 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);
   }
}

3.2.3 高可用与优缺点

  • 高可用方案:采用Redis哨兵集群或Redis Cluster集群,保证Redis服务的高可用。
  • 优点:性能极高,实现简单,轻量化,适合高并发场景。
  • 缺点:不支持可重入性;无锁续期机制,业务执行超时会导致锁提前释放;集群异步复制存在脑裂风险,可能导致锁丢失;不支持公平锁与阻塞获取。

3.3 基于Redisson的分布式锁

Redisson是Redis官方推荐的Java客户端,封装了生产级的分布式锁实现,解决了原生SETNX方案的所有缺陷,是互联网高并发场景的首选方案。

3.3.1 底层核心原理

  1. 可重入性实现:采用Redis Hash结构存储锁信息,key为锁名称,hash field为持有者唯一标识,value为重入次数,通过Lua脚本实现原子操作。
  2. 看门狗机制:客户端获取锁后,启动定时任务,每隔锁过期时间的1/3(默认30秒过期,每10秒执行一次),刷新锁的过期时间,保证业务未执行完时锁不会过期;客户端宕机后,定时任务停止,锁自动过期释放。
  3. 阻塞获取实现:基于Redis的发布订阅机制,锁释放时发布通知,等待的客户端收到通知后重新尝试获取锁,无需轮询,性能更高。
  4. 红锁(RedLock):解决集群脑裂问题,需要部署多个独立的Redis主节点,客户端必须在超过半数的节点上成功获取锁,且总耗时小于锁过期时间,才算获取成功,保证极端场景下的互斥性。

3.3.2 代码实现

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
public class RedissonLockService {
   private final RedissonClient redissonClient;
   private static final long DEFAULT_WAIT_TIME = 3L;
   private static final long DEFAULT_LEASE_TIME = 30L;

   public RedissonLockService(RedissonClient redissonClient) {
       this.redissonClient = redissonClient;
   }

   /**
    * 尝试获取可重入锁
    *
    * @param lockKey 锁的唯一标识
    * @return 获取结果
    */

   public boolean tryLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
           return false;
       }
   }

   /**
    * 尝试获取可重入锁(自定义超时时间)
    *
    * @param lockKey   锁的唯一标识
    * @param waitTime  最大等待时间
    * @param leaseTime 锁持有时间
    * @param timeUnit  时间单位
    * @return 获取结果
    */

   public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) {
       if (!StringUtils.hasText(lockKey)) {
           throw new IllegalArgumentException("锁key不能为空");
       }
       RLock lock = redissonClient.getLock(lockKey);
       try {
           return lock.tryLock(waitTime, leaseTime, timeUnit);
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           log.error("获取锁中断: {}", lockKey, e);
           return false;
       }
   }

   /**
    * 获取公平锁
    *
    * @param lockKey 锁的唯一标识
    * @return 获取结果
    */

   public boolean tryFairLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
           return false;
       }
   }

   /**
    * 释放锁
    *
    * @param lockKey 锁的唯一标识
    */

   public void releaseLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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
public class RedissonRedLockService {
   private final RedissonClient redissonClient1;
   private final RedissonClient redissonClient2;
   private final RedissonClient redissonClient3;
   private final RedissonClient redissonClient4;
   private final 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();
           return false;
       }
   }

   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();
       }
   }
}

3.3.3 高可用与优缺点

  • 高可用方案:普通场景采用Redis哨兵/Cluster集群;强一致性场景采用红锁方案,部署5个独立的Redis主节点,通过过半机制保证锁的可靠性。
  • 优点:性能优异,原生支持可重入、锁续期、公平锁、阻塞获取、读写锁等高级特性;API简单易用,生产级成熟方案,社区活跃,解决了原生Redis锁的几乎所有缺陷。
  • 缺点:红锁方案部署与运维成本高;普通集群模式仍存在极小概率的脑裂锁丢失风险。

3.4 基于ZooKeeper的分布式锁

ZooKeeper分布式锁依托ZAB一致性协议,实现强一致性的互斥控制,适合金融级、对锁的可靠性有极致要求的场景。

3.4.1 底层核心原理

  1. 临时顺序节点:每个锁对应一个持久化父节点,客户端获取锁时,在父节点下创建一个临时顺序节点;客户端会话失效后,临时节点会自动删除,天然解决死锁问题。
  2. 顺序性保证:客户端创建节点后,获取父节点下的所有子节点,按序号排序,判断自己创建的节点是否为序号最小的节点,是则获取锁成功。
  3. Watcher监听机制:如果当前节点不是最小的,客户端监听前一个序号节点的删除事件,进入等待状态;当前一个节点被删除(锁释放),Watcher触发,客户端再次判断自己是否为最小节点,是则获取锁成功,实现公平锁与阻塞获取。
  4. 可重入性:通过会话ID+线程ID标识持有者,同一持有者再次获取锁时,直接返回成功,更新重入次数。

生产环境推荐使用Apache Curator框架,它封装了成熟的分布式锁实现,处理了会话中断、重连、Watcher重复注册等所有边界问题。

3.4.2 代码实现

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
public class ZookeeperLockService {
   private final CuratorFramework curatorFramework;
   private static final String LOCK_ROOT_PATH = "/distributed_lock";
   private static final long DEFAULT_WAIT_TIME = 3L;

   public ZookeeperLockService(CuratorFramework curatorFramework) {
       this.curatorFramework = curatorFramework;
   }

   /**
    * 尝试获取分布式锁
    *
    * @param lockKey 锁的唯一标识
    * @return 获取结果
    */

   public boolean tryLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
           return false;
       }
   }

   /**
    * 尝试获取分布式锁(自定义等待时间)
    *
    * @param lockKey  锁的唯一标识
    * @param waitTime 最大等待时间
    * @param timeUnit 时间单位
    * @return 获取结果
    */

   public boolean tryLock(String lockKey, long waitTime, TimeUnit timeUnit) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
           return false;
       }
   }

   /**
    * 释放分布式锁
    *
    * @param lockKey 锁的唯一标识
    */

   public void releaseLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
       }
   }
}

3.4.3 高可用与优缺点

  • 高可用方案:部署5节点ZooKeeper集群,通过ZAB协议的过半机制保证集群可用性,只要半数以上节点存活,集群就能正常提供服务,无脑裂风险。
  • 优点:天然强一致性,无锁过期时间问题,自动释放死锁,原生支持公平锁、阻塞获取、可重入性,锁释放安全性极高,适合金融级强一致性场景。
  • 缺点:性能低于Redis,写操作需要过半节点同步,高并发下吞吐量有限;集群部署与运维成本高;网络抖动可能导致会话断开,临时节点被删除,锁被提前释放。

3.5 基于Etcd的分布式锁

Etcd是基于Raft一致性协议的分布式键值存储,是云原生场景的标准组件,分布式锁实现兼顾强一致性与性能,适合K8s生态下的项目。

3.5.1 底层核心原理

  1. Raft一致性协议:保证集群数据的强一致性,过半写入才返回成功,无脑裂风险。
  2. Revision全局版本号:Etcd的每个写操作都会生成一个全局唯一、递增的Revision号,通过Revision的顺序性实现公平锁,替代ZooKeeper的顺序节点。
  3. Lease租约机制:客户端获取锁时绑定一个租约,定期发送心跳续期;客户端宕机后,租约到期自动过期,key被删除,锁自动释放,防止死锁。
  4. Watch机制:客户端如果没有获取到锁,会Watch前一个Revision的key的删除事件,锁释放时收到通知,重新尝试获取锁,实现阻塞获取。

3.5.2 代码实现

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
public class EtcdLockService {
   private final Client etcdClient;
   private static final long DEFAULT_LEASE_TTL = 30L;
   private static final long DEFAULT_WAIT_TIME = 3L;

   public EtcdLockService(Client etcdClient) {
       this.etcdClient = etcdClient;
   }

   /**
    * 尝试获取分布式锁
    *
    * @param lockKey 锁的唯一标识
    * @return 获取结果
    */

   public boolean tryLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new 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);
           return true;
       } catch (Exception e) {
           log.error("获取Etcd锁异常: {}", lockKey, e);
           return false;
       }
   }

   /**
    * 释放分布式锁
    *
    * @param lockKey 锁的唯一标识
    */

   public void releaseLock(String lockKey) {
       if (!StringUtils.hasText(lockKey)) {
           throw new IllegalArgumentException("锁key不能为空");
       }
       Lock lockClient = etcdClient.getLockClient();
       try {
           lockClient.unlock(lockKey.getBytes()).get();
       } catch (Exception e) {
           log.error("释放Etcd锁异常: {}", lockKey, e);
       }
   }
}

3.5.3 高可用与优缺点

  • 高可用方案:部署3-5节点Etcd集群,通过Raft协议的过半机制保证集群可用性,半数以上节点存活即可正常服务,强一致性无脑裂风险。
  • 优点:强一致性,性能优于ZooKeeper,租约机制灵活,Watch机制高效,云原生生态适配性好,天然支持K8s环境。
  • 缺点:部署运维成本高,Java客户端生态不如Redisson成熟,国内使用场景少于Redis与ZooKeeper,性能略低于Redis。

四、全方案核心维度横向对比

对比维度 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生态场景

五、生产环境选型指南

  1. 初创项目、低并发场景:已有MySQL基础设施,无额外中间件预算,优先选择MySQL唯一索引分布式锁,实现简单,成本极低,满足基础互斥需求。
  2. 互联网高并发通用场景:电商、订单、库存、营销等90%以上的业务场景,优先选择Redisson分布式锁,性能优异,功能全面,成熟稳定,社区活跃,踩坑成本低。
  3. 金融级强一致性场景:支付、转账、清算等对锁的互斥性零容忍的场景,优先选择ZooKeeper或Etcd分布式锁,天然强一致性,无锁丢失风险,满足金融级可靠性要求。
  4. 云原生K8s环境:基于K8s部署的微服务项目,优先选择Etcd分布式锁,与云原生生态完美适配,无需额外部署一致性组件。
  5. 跨机房、超高可靠性场景:跨地域部署、对锁的可靠性有极致要求的场景,选择Redisson红锁方案,通过多独立节点过半机制,避免极端场景下的锁丢失问题。

六、分布式锁生产踩坑避坑指南

6.1 锁超时与业务执行时间不匹配

问题:业务执行时间超过锁的预设过期时间,锁被提前释放,其他客户端获取到锁,导致并发问题与数据不一致。解决方案:使用Redisson看门狗机制实现自动续期;评估业务最长执行时间,设置合理的过期时间,预留2-3倍的冗余;禁止使用无过期时间的锁。

6.2 锁误释放

问题:客户端A释放了客户端B持有的锁,导致互斥性失效。解决方案:必须为每个锁设置唯一的持有者标识,释放锁时必须校验持有者身份;释放锁必须通过原子操作(Lua脚本、事务)实现,禁止直接删除锁;只能由持有锁的线程释放锁。

6.3 事务与锁的时序问题

问题:锁释放后,事务还未提交,其他客户端获取到锁,读取到未提交的数据,出现脏读与更新丢失。解决方案:严格控制时序,必须在事务提交之后再释放锁;优先使用编程式事务,手动控制事务提交与锁释放的顺序。

6.4 集群脑裂导致锁丢失

问题:Redis主从集群中,主节点宕机,异步复制导致锁数据未同步到从节点,从节点升级为主后,原锁数据丢失,多个客户端同时获取到锁。解决方案:强一致性场景使用ZooKeeper/Etcd;Redis场景配置min-replicas-to-write,保证写操作至少同步到1个从节点才返回成功;极端场景使用Redisson红锁方案。

6.5 锁粒度不合理

问题:锁粒度太大,导致并发度低,系统性能差;锁粒度太小,导致锁覆盖不全,出现并发漏洞。解决方案:锁的粒度必须与共享资源一一对应,比如商品库存锁使用商品ID作为锁key,禁止使用全商品的全局锁;避免在锁内执行非共享资源的操作,尽量缩短锁的持有时间。

6.6 网络抖动导致的锁提前释放

问题:ZooKeeper/Etcd场景中,网络抖动导致客户端与服务端会话断开,会话超时后,锁被自动释放,客户端仍在执行业务,导致并发问题。解决方案:调整会话超时时间,设置合理的心跳间隔;添加会话状态监听,会话断开时立即中断业务执行;配置客户端重连与重试机制。

七、总结

分布式锁没有银弹,不存在完美的方案,只有最适合业务场景的方案。

所有分布式锁的实现,本质上都是围绕核心设计准则做的取舍:追求高性能,就要牺牲一定的一致性;追求强一致性,就要接受性能的损耗;追求简单低成本,就要接受功能的局限性。

只有真正理解分布式锁的底层原理,才能结合业务场景做出正确的选型,在生产环境中正确使用,避免踩坑,真正解决分布式系统的并发互斥问题。

目录
相关文章
|
9天前
|
人工智能 安全 Linux
【OpenClaw保姆级图文教程】阿里云/本地部署集成模型Ollama/Qwen3.5/百炼 API 步骤流程及避坑指南
2026年,AI代理工具的部署逻辑已从“单一云端依赖”转向“云端+本地双轨模式”。OpenClaw(曾用名Clawdbot)作为开源AI代理框架,既支持对接阿里云百炼等云端免费API,也能通过Ollama部署本地大模型,完美解决两类核心需求:一是担心云端API泄露核心数据的隐私安全诉求;二是频繁调用导致token消耗过高的成本控制需求。
5312 11
|
16天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
21436 116
|
13天前
|
人工智能 安全 前端开发
Team 版 OpenClaw:HiClaw 开源,5 分钟完成本地安装
HiClaw 基于 OpenClaw、Higress AI Gateway、Element IM 客户端+Tuwunel IM 服务器(均基于 Matrix 实时通信协议)、MinIO 共享文件系统打造。
8190 7

热门文章

最新文章