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

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

分布式锁的前提介绍

因为分布式系统之间是不同进程的,单机版的锁无法满足要求。所以我们可以借助中间件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
目录
打赏
0
0
0
0
379
分享
相关文章
十大主流联邦学习框架:技术特性、架构分析与对比研究
联邦学习(FL)是保障数据隐私的分布式模型训练关键技术。业界开发了多种开源和商业框架,如TensorFlow Federated、PySyft、NVFlare、FATE、Flower等,支持模型训练、数据安全、通信协议等功能。这些框架在灵活性、易用性、安全性和扩展性方面各有特色,适用于不同应用场景。选择合适的框架需综合考虑开源与商业、数据分区支持、安全性、易用性和技术生态集成等因素。联邦学习已在医疗、金融等领域广泛应用,选择适配具体需求的框架对实现最优模型性能至关重要。
623 79
十大主流联邦学习框架:技术特性、架构分析与对比研究
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
66 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
DeepSeek背后的技术基石:DeepSeekMoE基于专家混合系统的大规模语言模型架构
DeepSeekMoE是一种创新的大规模语言模型架构,融合了专家混合系统(MoE)、多头潜在注意力机制(MLA)和RMSNorm归一化。通过专家共享、动态路由和潜在变量缓存技术,DeepSeekMoE在保持性能的同时,将计算开销降低了40%,显著提升了训练和推理效率。该模型在语言建模、机器翻译和长文本处理等任务中表现出色,具备广泛的应用前景,特别是在计算资源受限的场景下。
590 29
DeepSeek背后的技术基石:DeepSeekMoE基于专家混合系统的大规模语言模型架构
YOLOv11改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
YOLOv11改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
182 10
YOLOv11改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
RT-DETR改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
RT-DETR改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
51 4
RT-DETR改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型
体育赛事即时比分 分析页面的开发技术架构与实现细节
本文基于“体育即时比分系统”开发经验总结,分享技术实现细节。系统通过后端(ThinkPHP)、前端(Vue.js)、移动端(Android/iOS)协同工作,解决实时比分更新、赔率同步及赛事分析展示等问题。前端采用 Vue.js 结合 WebSocket 实现数据推送,提升用户体验;后端提供 API 支持比赛数据调用;移动端分别使用 Java 和 Objective-C 实现跨平台功能。代码示例涵盖比赛分析页面、API 接口及移动端数据加载逻辑,为同类项目开发提供参考。
布谷直播系统源码开发实战:从架构设计到性能优化
作为山东布谷科技的一名技术研发人员,我参与了多个直播系统平台从0到1的开发和搭建,也见证了直播行业从萌芽到爆发的全过程。今天,我想从研发角度,分享一些直播系统软件开发的经验和心得,希望能对大家有所帮助。
DeepSeek进阶开发与应用4:DeepSeek中的分布式训练技术
随着深度学习模型和数据集规模的扩大,单机训练已无法满足需求,分布式训练技术应运而生。DeepSeek框架支持数据并行和模型并行两种模式,通过将计算任务分配到多个节点上并行执行,显著提高训练效率。本文介绍DeepSeek中的分布式训练技术,包括配置与启动方法,帮助用户轻松实现大规模模型训练。数据并行通过`MirroredStrategy`同步梯度,适用于大多数模型;模型并行则通过`ParameterServerStrategy`异步处理大模型。DeepSeek简化了分布式环境配置,支持单机多卡和多机多卡等场景。
Java高级应用开发:基于AI的微服务架构优化与性能调优
在现代企业级应用开发中,微服务架构虽带来灵活性和可扩展性,但也增加了系统复杂性和性能瓶颈。本文探讨如何利用AI技术,特别是像DeepSeek这样的智能工具,优化Java微服务架构。AI通过智能分析系统运行数据,自动识别并解决性能瓶颈,优化服务拆分、通信方式及资源管理,实现高效性能调优,助力开发者设计更合理的微服务架构,迎接未来智能化开发的新时代。
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
分布式爬虫框架Scrapy-Redis实战指南

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等