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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
相关文章
|
1天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
16天前
|
存储 缓存 负载均衡
从零到一:分布式缓存技术初探
分布式缓存通过将数据存储在多个节点上,利用负载均衡算法提高访问速度、降低数据库负载并增强系统可用性。常见产品有Redis、Memcached等。其优势包括性能扩展、高可用性、负载均衡和容错性,适用于页面缓存、应用对象缓存、状态缓存、并行处理、事件处理及极限事务处理等多种场景。
44 1
|
20天前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
57 2
|
21天前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
67 3
|
19天前
|
前端开发 搜索推荐 安全
陪玩系统架构设计陪玩系统前后端开发,陪玩前端设计是如何让人眼前一亮的?
陪玩系统的架构设计、前后端开发及前端设计是构建吸引用户、功能完善的平台关键。架构需考虑用户需求、技术选型、安全性等,确保稳定性和扩展性。前端可选用React、Vue或Uniapp,后端用Spring Boot或Django,数据库结合MySQL和MongoDB。功能涵盖用户管理、陪玩者管理、订单处理、智能匹配与通讯。安全性方面采用SSL加密和定期漏洞扫描。前端设计注重美观、易用及个性化推荐,提升用户体验和平台粘性。
52 0
|
1月前
|
运维 监控 Java
后端开发中的微服务架构实践与挑战####
在数字化转型加速的今天,微服务架构凭借其高度的灵活性、可扩展性和可维护性,成为众多企业后端系统构建的首选方案。本文深入探讨了微服务架构的核心概念、实施步骤、关键技术考量以及面临的主要挑战,旨在为开发者提供一份实用的实践指南。通过案例分析,揭示微服务在实际项目中的应用效果,并针对常见问题提出解决策略,帮助读者更好地理解和应对微服务架构带来的复杂性与机遇。 ####
|
1月前
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
43 1
|
1月前
|
消息中间件 运维 API
后端开发中的微服务架构实践####
本文深入探讨了微服务架构在后端开发中的应用,从其定义、优势到实际案例分析,全面解析了如何有效实施微服务以提升系统的可维护性、扩展性和灵活性。不同于传统摘要的概述性质,本摘要旨在激发读者对微服务架构深度探索的兴趣,通过提出问题而非直接给出答案的方式,引导读者深入
46 1
|
1月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
2月前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
53 3

热门文章

最新文章

下一篇
开通oss服务