分布式锁的演进和最佳实践,含springBoot 实现(Java版本)

好的,我将以资深程序设计专家的角度,为你系统地讲解分布式锁的演进过程与最佳实践,涵盖设计思想、常见技术方案、优劣对比与生产经验。


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

在分布式系统中,多个节点(如多个服务实例)可能会并发地访问共享资源(如数据库、缓存、文件、队列等),这就可能引发 数据不一致、重复执行、脏写等并发问题,需要一种机制来实现 互斥与同步控制 —— 这就是分布式锁的作用。


二、分布式锁的演进路线

我们按技术实现方式和复杂性来梳理一下主流分布式锁的演进过程:


1. 基于数据库的分布式锁(最早期)

实现方式:
  • 使用数据库中的一行记录(如 LOCK_TABLE)进行加锁。
  • 利用数据库的唯一约束、事务与悲观/乐观锁控制。
示例 SQL:
INSERT INTO lock_table (lock_key, owner, expire_time) VALUES ('job_1', 'node_1', NOW() + INTERVAL 30 SECOND);
优点:
  • 易于实现,不引入新组件;
  • 利用数据库事务天然支持锁粒度控制。
缺点:
  • 性能差、开销大
  • 无法很好处理死锁、异常宕机时锁释放问题;
  • 不适合高并发、高性能场景。

2. 基于 Redis 的分布式锁(主流方案)

实现方式:

使用 Redis 的原子操作实现,如 SET key value NX PX 30000

RedLock(Redisson / 官方推荐方案):

由 Redis 作者提出,适用于多主架构,使用 多个 Redis 实例 进行加锁确认。

优点:
  • 高性能、低延迟;
  • 基于内存操作,吞吐大;
  • 开源客户端多(Redisson、Lettuce、Jedis);
缺点:
  • 单节点 Redis 容错性差;
  • RedLock 多节点需要网络同步,复杂;
  • 客户端必须定期续租,防止锁失效。

3. 基于 ZooKeeper 的分布式锁

实现方式:

利用 ZooKeeper 的临时顺序节点 + Watch 机制,谁节点最小谁获得锁。

优点:
  • 强一致性;
  • 自动过期释放(临时节点);
  • 适用于需要顺序、公平锁的场景。
缺点:
  • 性能一般(ZooKeeper 本质是 CP 系统);
  • 实现复杂度高;
  • 容易导致“羊群效应”(过多 Watch)。

4. 基于 etcd / Consul 的分布式锁(云原生)

实现方式:

使用 etcd 的 Compare-And-Swap(CAS)机制与租约机制。

优点:
  • 强一致性,符合 Raft 协议;
  • 与 K8s 等云原生系统天然集成。
缺点:
  • 社区支持度一般;
  • 上手曲线较高。

三、分布式锁的最佳实践总结

1. 明确需求,选择合适方案

  • 如果高性能:优选 Redis + Redisson;
  • 如果需要强一致性或顺序执行:考虑 ZooKeeper;
  • 如果是K8s / 云原生环境:建议 etcd;
  • 如果仅仅是小系统:可以用数据库实现。

2. 锁设计注意点

  • 唯一标识(requestId / UUID):锁的拥有者标识,释放时需校验;
  • 超时机制:防止死锁,锁应有过期时间;
  • 续租机制:长任务需自动延长锁有效期;
  • 锁粒度控制:避免全局大锁;
  • 可重入性:根据业务选择是否支持;
  • 异常释放机制:如服务崩溃后,自动释放。

四、生产中的经验建议

项目建议做法
Redis 锁使用 SET NX PX,同时定期续约(Redisson 自动化)
RedLock仅适合极高一致性要求场景,否则单 Redis 足够
ZooKeeper 锁控制节点数,避免“惊群”
监控锁失败次数、锁持有时长、锁争用次数建议监控
日志锁获取/释放过程加详细日志,便于排查死锁问题

五、推荐组件与工具

工具特点
RedissonRedis 客户端,支持锁、限流、延迟队列等,强烈推荐
Apache CuratorZooKeeper 客户端封装,简化节点操作
Etcd Java Client云原生友好,适合 K8s/微服务架构

六、redisson最佳实践之代码实现

好的,我们以目前在生产中最常用的 Redis 分布式锁 + Redisson 为例,提供一个 完整可落地的 Java 示例。内容涵盖:

  1. Redisson 的引入和配置
  2. 获取和释放锁(可重入锁)
  3. 支持自动续约(watchdog)
  4. 注意事项与错误用法防范

🧱 一、Maven 引入 Redisson

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.28.0</version> <!-- 请使用最新版 -->
</dependency>


⚙️ 二、Redisson 配置

# 单节点
redisson:
  single-server:
    address: redis://127.0.0.1:6379
    password: your_password # 可选
    connection-minimum-idle-size: 5
    connection-pool-size: 10

 
# 集群模式(Redis Cluster)
redisson:
  cluster-servers:
    node-addresses:
      - redis://127.0.0.1:7001
      - redis://127.0.0.1:7002
      - redis://127.0.0.1:7003
    scan-interval: 2000
    password: your_password


---

### 🔒 三、分布式锁使用示例(可重入 + 自动续约)

```java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

public class DistributedLockExample {

    private static final RedissonClient redisson = RedissonManager.getClient();

    public void doBusinessWithLock() {
        String lockKey = "my-lock-key";
        RLock lock = redisson.getLock(lockKey);

        try {
            // 尝试获取锁,最多等 5 秒,锁定后 10 秒自动释放(如果不使用 watchdog)
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                try {
                    System.out.println("获取到锁,执行业务逻辑...");
                    Thread.sleep(8000); // 模拟业务处理
                } finally {
                    lock.unlock();
                    System.out.println("释放锁");
                }
            } else {
                System.out.println("未能获取到锁,稍后再试");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

⛑️ 四、Redisson Watchdog 自动续约机制(默认)

若使用 lock.lock() 而不传 leaseTime,Redisson 会启动看门狗机制自动续期,直到手动 unlock。

lock.lock(); // 会自动续约,避免执行太久锁过期
try {
    // 执行业务
} finally {
    lock.unlock();
}

Redisson 的 watchdog 默认续约时间是 30 秒,间隔 10 秒续一次,可通过配置修改:

config.setLockWatchdogTimeout(30000); // 30秒

🧷 五、常见错误示范(避免踩坑)

错误原因
多线程共享 RLock 实例RLock 是非线程安全的,必须每次通过 redisson.getLock() 获取
获取锁但未释放可能引发死锁,应使用 try-finally
自定义过短 leaseTime 未自动续期使用 tryLock(timeout, leaseTime) 时 Watchdog 不生效
Redis 崩溃后锁失效应配置 Redis 高可用(主从或哨兵)或 RedLock

✅ 六、可扩展建议

  • 封装通用的锁模板方法(AOP 或注解方式);
  • 锁状态落地持久化,用于监控或审计;
  • 设置 Prometheus 监控锁争用与异常;

📌 总结

场景推荐做法
普通业务互斥控制tryLock(timeout, lease)
长时间任务执行lock() + 自动续约(watchdog)
强一致场景可扩展至 RedLock 多节点锁
云原生环境可切换为 etcd / Consul 锁

七、redisson最佳实践之注解式的分布式锁封装,结合 Spring AOP + Redisson

好的,我们接下来实现一个注解式的分布式锁封装,结合 Spring AOP + Redisson,达到:

✅ 用注解就能加分布式锁
✅ 自动处理锁获取、释放、异常
✅ 支持锁的 key 表达式、重试机制、自动续约等


🔧 结构概览

com.example.lock
├── annotation
│   └── DistributedLock.java      <-- 自定义注解
├── aspect
│   └── DistributedLockAspect.java <-- AOP切面处理锁逻辑
├── service
│   └── SomeBusinessService.java   <-- 使用示例
└── RedissonConfig.java            <-- Redisson配置

🏷️ 一、自定义注解 @DistributedLock

package com.example.lock.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {

    /**
     * 锁的key支持Spring EL表达式,例如 "#userId"
     */
    String key();

    /**
     * 最大等待时间(秒)默认5秒
     */
    long waitTime() default 5;

    /**
     * 锁租期(秒)默认10秒
     */
    long leaseTime() default 10;

    /**
     * 获取锁失败是否抛异常
     */
    boolean throwOnFail() default false;
}

🧠 二、AOP 切面实现 DistributedLockAspect

package com.example.lock.aspect;

import com.example.lock.annotation.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class DistributedLockAspect {

    @Autowired
    private RedissonClient redisson;

    private final ExpressionParser parser = new SpelExpressionParser();
    private final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(com.example.lock.annotation.DistributedLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethod(joinPoint);
        DistributedLock lockAnnotation = method.getAnnotation(DistributedLock.class);
        String lockKey = parseKey(lockAnnotation.key(), method, joinPoint.getArgs());

        RLock lock = redisson.getLock(lockKey);
        boolean locked = false;

        try {
            locked = lock.tryLock(lockAnnotation.waitTime(), lockAnnotation.leaseTime(), TimeUnit.SECONDS);
            if (!locked) {
                log.warn("获取分布式锁失败: {}", lockKey);
                if (lockAnnotation.throwOnFail()) {
                    throw new RuntimeException("无法获取分布式锁");
                }
                return null;
            }

            log.info("获取锁成功: {}", lockKey);
            return joinPoint.proceed();

        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放锁: {}", lockKey);
            }
        }
    }

    private Method getMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        try {
            return AopUtils.getMostSpecificMethod(signature.getMethod(), targetClass);
        } catch (Exception e) {
            return signature.getMethod();
        }
    }

    private String parseKey(String keySpEL, Method method, Object[] args) {
        EvaluationContext context = new StandardEvaluationContext();
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
        }
        Expression expression = parser.parseExpression(keySpEL);
        return "lock:" + expression.getValue(context, String.class); // 加前缀避免冲突
    }
}

🔧 三、Redisson 配置类

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();
        config.useSingleServer()
              .setAddress("redis://127.0.0.1:6379")
              .setPassword(null)
              .setConnectionMinimumIdleSize(5);
        return Redisson.create(config);
    }
}

🚀 四、使用示例

import com.example.lock.annotation.DistributedLock;
import org.springframework.stereotype.Service;

@Service
public class SomeBusinessService {

    @DistributedLock(key = "#userId", waitTime = 3, leaseTime = 8, throwOnFail = true)
    public void doSomethingCritical(String userId) {
        System.out.println("正在执行关键业务逻辑,用户ID:" + userId);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ignored) {}
    }
}

✅ 五、优点

  • 业务代码无侵入,只用注解即可锁定方法
  • 支持 SpEL 表达式,可按业务动态生成 key
  • 防止锁不释放、异常抛出、并发冲突

📌 总结

特性说明
自动锁处理通过 AOP 自动加锁/释放
锁 key 支持 SpEL业务灵活控制锁粒度
可重入与续期Redisson 原生支持,无需手动处理
可统一监控日志支持打点锁日志,接入监控系统方便

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值