【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件(一)

简介: 【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件

分布式锁的前提介绍

因为分布式系统之间是不同进程的,单机版的锁无法满足要求。所以我们可以借助中间件Redis的setnx()命令实现分布式锁。setnx()命令只会对不存在的key设值,返回1代表获取锁成功。

分布式锁的基础要点

分布式锁的特性是排他、避免死锁、高可用。

分布式锁的实现原理

分布式锁的实现可以通过数据库的乐观锁(通过版本号)或者悲观锁(通过for update)、Redis的setnx()命令、Zookeeper(在某个持久节点添加临时有序节点,判断当前节点是否是序列中最小的节点,如果不是则监听比当前节点还要小的节点。如果是,获取锁成功。当被监听的节点释放了锁(也就是被删除),会通知当前节点。然后当前节点再尝试获取锁,如此反复)。

Zookeeper的分布式锁原理

  • Zookeeper(在某个持久节点添加临时有序节点,判断当前节点是否是序列中最小的节点,如果不是则监听比当前节点还要小的节点。如果是,获取锁成功。
  • 当被监听的节点释放了锁(也就是被删除),会通知当前节点。然后当前节点再尝试获取锁,如此反复)

数据库的分布式锁原理

如果获取锁的逻辑只有这三行代码的话,会造成死循环,明显不符合分布式锁的特性。

我们知道分布式锁的特性是排他、避免死锁、高可用。分布式锁的实现可以通过数据库的乐观锁(通过版本号)或者悲观锁(通过for update)。

Redis的分布式锁原理

  • Redis对存在的key设值,会返回0代表获取锁失败。这里的value是System.currentTimeMillis() (获取锁的时间)+锁持有的时间。
  • 这里设置锁持有的时间是200ms,实际业务执行的时间远比这200ms要多的多,持有锁的客户端应该检查锁是否过期,保证锁在释放之前不会过期。因为客户端故障的情况可能是很复杂的。
分布式案例分析
  • 比如现在有A,B俩个客户端。A客户端获取了锁,执行业务中做了骚操作导致阻塞了很久,时间应该远远超过200ms,当A客户端从阻塞状态下恢复继续执行业务代码时,A客户端持有的锁由于过期已经被其他客户端占有。这时候A客户端执行释放锁的操作,那么有可能释放掉其他客户端的锁。
  • 这里设置的客户端等待锁的时间是200ms。这里通过轮询的方式去让客户端获取锁。如果客户端在200ms之内没有锁的话,直接返回false。实际场景要设置合适的客户端等待锁的时间,避免消耗CPU资源。

接下来我们就要用redis去开发一个我们自己的一个常用的分布式锁的组件。

总体设计结构图


引用Maven配置

首先我们先进行配置相关的maven的依赖,这些依赖呢大家选择性进行使用即可。

xml

复制代码

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson-spring-boot-starter</artifactId>
      <version>3.13.3</version>
    </dependency>
   <dependency>
        <groupId>com.fengwenyi</groupId>
        <artifactId>JavaLib</artifactId>
        <version>2.1.6</version>
     </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <!--joda-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.1</version>
        </dependency>
         <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0-jre</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.8</version>
        </dependency>
  </dependencies>

建立分布式锁的参数模型

构建分布式锁的参数模型类:DistributeLockParam。

java

复制代码

@Data
public class DistributeLockParam {
    private String lockUUid;
    private String lockNamePrefix;
    
    private Long expireTime;
    private Long waitTime;
    private TimeUnit timeUnit;
    private String delimiter;
    private DistributeLockType lockType;
}

参数的一个大概的一个分析介绍:

  • lockUUid:分布式锁的唯一ID主键标识,作为主键操作。
  • lockNamePrefix:锁名称的前缀,用于作为查询锁状态的标准,
  • expireTime:为了防止死锁,我们需要加入一个参数作为过期时间,防止系统宕机后,或者长时间占用进行资源不释放的问题。
  • waitTime:与过期时间不同,等待时间作为锁需要占用或者其他线程会等待获取锁的时间。
  • timeUnit:等待时间和过期时间的时间单位
  • delimiter:锁标识key的分隔符,redis而言一般采用”:“的方式进行控制。
  • lockType:锁的类型。

所以还需要定义分布式锁类型:

java

复制代码

public enum DistributeLockType {
    /**
     * 重入锁
     */
    REENTRANT_LOCK,
    /**
     * 非公平锁
     */
    FAIR_LOCK,
    /**
     * 联和锁
     */
    MULTI_LOCK,
    /**
     * 红锁
     */
    RED_LOCK,
    /**
     * 读写锁
     */
    READ_WRITE_LOCK,
    ;
}

定义分布式锁的核心接口

接下来我们要定义一下分布式锁的核心逻辑接口DistributeLockSupport。

java

复制代码

public interface DistributeLockSupport<T> {
    /**
     * 默认的分隔符
     */
    String DEFAULT_DELIMTER = ":";
  
    String DEFAULT_KEY_PREFIX = "LOCK";
    Long DEFAULT_EXPIRE_TIME = 10L;
    Long DEFAULT_WAIT_TIME = 10L;
    Joiner DEFAULT_JOINER = Joiner.on(DistributeLockSupport.DEFAULT_DELIMTER).
            skipNulls();
        
    /**
     * 加锁
     * @param distributeLockParam
     * @return
     */
    T lock(DistributeLockParam distributeLockParam);
    /**
     * 解锁
     * @param distributeLockParam
     */
    void unlock(T param, DistributeLockParam distributeLockParam);
}

其中前四个属性静态常量值主要作用是给我们的分布式所提供默认值。

java

复制代码

/**
     * 默认的分隔符
     */
    String DEFAULT_DELIMTER = ":";
  
    String DEFAULT_KEY_PREFIX = "LOCK";
    Long DEFAULT_EXPIRE_TIME = 10L;
    Long DEFAULT_WAIT_TIME = 10L;

分别代表

  • 分布是所的键值的分割符。
  • 默认的key的前缀。
  • 还有就是锁的过期时间和等待时间。

这里我们采用了Guava的连接器,进行我们的特殊风格符的连接。

java

复制代码

Joiner DEFAULT_JOINER = Joiner.on(DistributeLockSupport.DEFAULT_DELIMTER).
            skipNulls();

业务加锁和解锁方法

主要用于枷锁和解锁我们的分布式锁。

java

复制代码

/**
     * 加锁
     * @param distributeLockParam
     * @return
     */
    T lock(DistributeLockParam distributeLockParam);
    /**
     * 解锁
     * @param distributeLockParam
     */
    void unlock(T param, DistributeLockParam distributeLockParam);

定义分布式锁的键Key生成接口

接下来主要去定一个接口,专门为我们生成不同样式,不同格式的键值,进行一个扩展的一个接口(LockKeyGenerator)。

java

复制代码

public interface LockKeyGenerator {
    String getLockKey(ProceedingJoinPoint pjp);
}

可以看到啊对应的参数是AOP的一个代理参数:ProceedingJoinPoint, 这也被我们后面进行批处理奠定一定的基础。

定义分布式锁的异常类

主要用于定义分布式锁的异常输出类:RedisDistributedLockException。

java

复制代码

public class RedisDistributedLockException extends RuntimeException {
    private String key;
    public RedisDistributedLockException (String key) {
        super("key [" + key + "] tryLock fail");
        this.key = key;
    }
    public RedisDistributedLockException (String key, String errorMessage) {
        super("key [" + key + "] tryLock fail error message :" + errorMessage);
        this.key = key;
    }
}

可以看到我们的该类是实现了RuntimeException的运行时异常类。


【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件(二)https://developer.aliyun.com/article/1471008

相关文章
|
6月前
|
存储 缓存 安全
某鱼电商接口架构深度剖析:从稳定性到高性能的技术密码
某鱼电商接口架构揭秘:分层解耦、安全加固、性能优化三维设计,实现200ms内响应、故障率低于0.1%。详解三层架构、多引擎存储、异步发布、WebSocket通信与全链路防护,助力开发者突破电商接口“三难”困境。
|
8月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
1258 3
|
7月前
|
数据采集 监控 JavaScript
移动端性能监控探索:鸿蒙 NEXT 探针架构与技术实现
阿里云 ARMS 团队倾力打造的鸿蒙 NEXT SDK,为鸿蒙应用提供了业界领先的全链路监控解决方案。这不仅仅是一个 SDK,更是您洞察用户体验、优化应用性能的智能伙伴。
829 65
|
6月前
|
缓存 Cloud Native 中间件
《聊聊分布式》从单体到分布式:电商系统架构演进之路
本文系统阐述了电商平台从单体到分布式架构的演进历程,剖析了单体架构的局限性与分布式架构的优势,结合淘宝、京东等真实案例,深入探讨了服务拆分、数据库分片、中间件体系等关键技术实践,并总结了渐进式迁移策略与核心经验,为大型应用架构升级提供了全面参考。
|
6月前
|
人工智能 自然语言处理 安全
AI助教系统:基于大模型与智能体架构的新一代教育技术引擎
AI助教系统融合大语言模型、教育知识图谱、多模态交互与智能体架构,实现精准学情诊断、个性化辅导与主动教学。支持图文语音输入,本地化部署保障隐私,重构“教、学、评、辅”全链路,推动因材施教落地,助力教育数字化转型。(238字)
1123 23
|
6月前
|
Java Linux 虚拟化
【Docker】(1)Docker的概述与架构,手把手带你安装Docker,云原生路上不可缺少的一门技术!
1. Docker简介 1.1 Docker是什么 为什么docker会出现? 假定您在开发一款平台项目,您的开发环境具有特定的配置。其他开发人员身处的环境配置也各有不同。 您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件。 您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。 **要求:**希望尽可能多在本地模拟这些环境而不产生重新创建服务器环境的开销 问题: 要如何确保应用能够在这些环境中运行和通过质量检测? 在部署过程中不出现令人头疼的版本、配置问题 无需重新编写代码和进行故障修复
575 2
|
7月前
|
Cloud Native API 开发者
Gemini 2.5 Flash 技术拆解:从 MoE 架构到阿里云生态落地指南
2025年9月,谷歌Gemini 2.5 Flash发布,性能提升5%、成本降24%,引发行业关注。其MoE架构、百万上下文与“思考”范式,助力阿里云开发者高效构建云原生应用。本文解析技术内核,结合汽车、物流等案例,提供落地指南与避坑建议,展望大模型与流计算融合前景。
842 6
|
6月前
|
存储 NoSQL 前端开发
【赵渝强老师】MongoDB的分布式存储架构
MongoDB分片通过将数据分布到多台服务器,实现海量数据的高效存储与读写。其架构包含路由、配置服务器和分片服务器,支持水平扩展,结合复制集保障高可用性,适用于大规模生产环境。
474 1

热门文章

最新文章

下一篇
开通oss服务