首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >分布式锁:底层原理全拆解 + 高可用方案深度对比,生产落地全指南

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

作者头像
果酱带你啃java
发布2026-04-30 18:25:30
发布2026-04-30 18:25:30
1890
举报

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

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

在单体应用中,我们通过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)
代码语言:javascript
复制
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='分布式锁表';
2. 核心代码实现

Maven核心依赖

代码语言:javascript
复制
<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>

实体类

代码语言:javascript
复制
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接口

代码语言:javascript
复制
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> {
}

核心锁服务类

代码语言:javascript
复制
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;
            }
        });
    }
}

业务使用示例

代码语言:javascript
复制
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);
        }
    }
}
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的过期时间,保证客户端宕机后锁能自动释放,防止死锁。
  • 唯一值:每个锁设置唯一的持有者标识,保证只有锁的持有者才能释放锁,避免误释放。

核心原子命令

代码语言:javascript
复制
SET lock_key unique_value NX PX 30000

释放锁的原子Lua脚本释放锁必须通过Lua脚本实现原子操作,避免出现“判断value相等后、删除key前,锁过期被其他客户端获取,导致误释放”的问题。

代码语言:javascript
复制
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
3.2.2 代码实现

Maven核心依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>3.2.4</version>
</dependency>

核心锁服务类

代码语言:javascript
复制
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);
    }
}
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核心依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.0</version>
</dependency>

核心锁服务类

代码语言:javascript
复制
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();
        }
    }
}

红锁实现示例

代码语言:javascript
复制
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();
        }
    }
}
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核心依赖

代码语言:javascript
复制
<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>

核心锁服务类

代码语言:javascript
复制
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);
        }
    }
}
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核心依赖

代码语言:javascript
复制
<dependency>
    <groupId>io.etcd</groupId>
    <artifactId>jetcd-core</artifactId>
    <version>0.7.7</version>
</dependency>

核心锁服务类

代码语言:javascript
复制
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);
        }
    }
}
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场景中,网络抖动导致客户端与服务端会话断开,会话超时后,锁被自动释放,客户端仍在执行业务,导致并发问题。解决方案:调整会话超时时间,设置合理的心跳间隔;添加会话状态监听,会话断开时立即中断业务执行;配置客户端重连与重试机制。

七、总结

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为什么需要分布式锁?
  • 二、分布式锁的核心设计准则
  • 三、主流分布式锁方案全拆解
    • 3.1 基于MySQL的分布式锁
      • 3.1.1 基于唯一索引的排他锁(生产推荐)
      • 3.1.2 其他MySQL锁实现
      • 3.1.3 高可用与优缺点
    • 3.2 基于Redis原生SETNX的分布式锁
      • 3.2.1 底层核心原理
      • 3.2.2 代码实现
      • 3.2.3 高可用与优缺点
    • 3.3 基于Redisson的分布式锁
      • 3.3.1 底层核心原理
      • 3.3.2 代码实现
      • 3.3.3 高可用与优缺点
    • 3.4 基于ZooKeeper的分布式锁
      • 3.4.1 底层核心原理
      • 3.4.2 代码实现
      • 3.4.3 高可用与优缺点
    • 3.5 基于Etcd的分布式锁
      • 3.5.1 底层核心原理
      • 3.5.2 代码实现
      • 3.5.3 高可用与优缺点
  • 四、全方案核心维度横向对比
  • 五、生产环境选型指南
  • 六、分布式锁生产踩坑避坑指南
    • 6.1 锁超时与业务执行时间不匹配
    • 6.2 锁误释放
    • 6.3 事务与锁的时序问题
    • 6.4 集群脑裂导致锁丢失
    • 6.5 锁粒度不合理
    • 6.6 网络抖动导致的锁提前释放
  • 七、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档