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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,经济版 1GB 1个月
简介: 【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件

分布式锁的前提介绍

因为分布式系统之间是不同进程的,单机版的锁无法满足要求。所以我们可以借助中间件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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
消息中间件 Java 开发者
Spring Cloud微服务框架:构建高可用、分布式系统的现代架构
Spring Cloud是一个开源的微服务框架,旨在帮助开发者快速构建在分布式系统环境中运行的服务。它提供了一系列工具,用于在分布式系统中配置、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等领域的支持。
18 5
|
5天前
|
消息中间件 监控 Java
使用Kafka实现分布式事件驱动架构
使用Kafka实现分布式事件驱动架构
|
10天前
|
存储 关系型数据库 分布式数据库
PolarDB,阿里云的云原生分布式数据库,以其存储计算分离架构为核心,解决传统数据库的扩展性问题
【7月更文挑战第3天】PolarDB,阿里云的云原生分布式数据库,以其存储计算分离架构为核心,解决传统数据库的扩展性问题。此架构让存储层专注数据可靠性,计算层专注处理SQL,提升性能并降低运维复杂度。通过RDMA加速通信,多副本确保高可用性。资源可独立扩展,便于成本控制。动态添加计算节点以应对流量高峰,展示了其灵活性。PolarDB的开源促进了数据库技术的持续创新和发展。
153 2
|
12天前
|
弹性计算 运维 负载均衡
构建高可用性的分布式系统:技术与策略
【7月更文挑战第1天】构建高可用分布式系统涉及负载均衡、容错处理和数据一致性等关键技术,遵循冗余、模块化及异步设计原则,并通过监控告警、自动化运维和弹性伸缩策略确保稳定性。
|
1天前
|
NoSQL 安全 Java
技术好文:Redis分布式锁的正确实现方式
技术好文:Redis分布式锁的正确实现方式
|
9天前
|
负载均衡 Java Linux
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,
|
9天前
|
缓存 Devops 微服务
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
|
5天前
|
NoSQL Java Redis
实现基于Redis的分布式锁机制
实现基于Redis的分布式锁机制
|
17天前
|
NoSQL Redis
redis分布式锁redisson
底层会尝试去加锁,如果加锁失败,会睡眠,自旋加锁,直到获取到锁为止。
18 1
|
15天前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
40 0