分布式架构 --- 分布式锁

分布式锁

  • 1. 研究背景及其意义
  • 2. 分布式锁的介绍
    • 2.1 分布式锁
    • 2.2 为什么需要分布式锁
    • 2.3 分布式锁的基本要求
  • 3. 分布式锁的实现
    • 3.1 基于数据库的分布式锁
      • 3.1.1选用数据库实现分布式锁的原因
      • 3.1.2 基于数据库实现分布式锁的缺点
      • 3.1.3分布式锁的实现
    • 3.2 基于Redis的分布式锁
      • 3.2.1选用Redis实现分布式锁的原因
      • 3.2.2基于Redis实现分布式锁的缺点
      • 3.2.3 基于Redis的分布式锁的实现
    • 3.3 基于Zookeeper的分布式锁
      • 3.3.1选用Zookeeper实现分布式锁的原因
      • 3.3.2 Zookeeper实现分布式锁的缺点
      • 3.3.3基于Zookeeper的分布式锁的实现
  • 4. 三种分布式锁实现方式的比较
  • 5. 总结

摘要:分布式锁是用于控制分布式系统之间访问共享资源。在分布式系统中,为了保证数据的一致性,我们常常会用到分布式锁。当同一个系统的不同主机或者不同的系统之间之间共用一个或者一组资源时,当我们访问这些资源的时候,面对不同用户的操作,我们需要防止各个操作之间相互独立,相互不收影响,以此来保证数据的一致性,这时,我们就需要用到分布式锁。比如,我们出行需要抢购火车票时,火车票的总数是不变的,但是火车票备份发在多个平台售卖,火车票总数不能超过限定票数,更不允许同一个座位的火车票不能卖给多个人,这样的场景就需要分布式锁对共享资源进行保护。
首先,本文主要对分布式锁进行基本介绍;其次,对分别基于数据库、Redis、Zookeeper实现分布式锁的三种方式的优缺点以及实现方法进行简述;最后,对分布式锁进行总结,并从不同方面比较基于三种方式实现分布式锁的性能。
关键词:分布式;Zookeeper;数据库;Redis;锁

1. 研究背景及其意义

  • 随着我国网络时代的发展,电子商务也越来越发达,我们可以同一时间,在不同地点去访问同一个软件,去参与同一个活动。但,如果应用只部署在少数几台服务器中,那么,数以千万的访问并发量容易导致服务器压力过大,严重时会导致服务器瘫痪。在618,双十一,年货节等节假日优惠活动时,用户访问量以及购买量将远远大于平常,往往一台服务器是不能够去实现的,这时候,我们就需要多台服务器去同时处理这些任务。
  • 我们可以拿除夕夜晚上支付宝分发红包进行举例,假设有50台服务器去处理这些分红包的业务,红包总金额为1亿,2千万人去分,金额随机,那么在这个业务场景下,我们必须得保证这2千万人分得的总金额数为1亿,如果同一个时刻,多个用户进行操作,就会导致系统扣除金额混乱,从而导致金额总和远远大于1亿,这会给商家带来很严重的经济损失,通过分布式锁,我们就可以很好地处理这些问题;

2. 分布式锁的介绍

2.1 分布式锁

  • 分布式锁是指分布式环境下,系统集群部署,实现多进程分布式互斥的一种锁。
  • 为了保证多个进程能看到锁,锁被存在公共存储(比如 Redis、Memcache、数据库等三方存储中),以实现多个进程并发访问同一个临界资源,同一时刻只有一个进程可访问共享资源,确保数据的一致性。

2.2 为什么需要分布式锁

  • 在单机应用程序开发环境中,当线程需要并发同步时,对于多线程间涉及到的的代码同步问题,我们一般采用synchronized/Lock的方式来进行解决,同时,我们将基于锁来检查多个线程间对资源的安全访问。例如,在文件读写系统中,用户想要进行一个写操作,那么写进程就需要检查系统是否存在一个写线程锁。
  • 如果存在一个写线程锁,那么我们就需要等待直到锁释放后,才能去争夺资源,才有可能获取到属于该线程的锁并进行写操作,这样,通过锁就可以避免多个线程同时写操作造成的数据冲突。而对于读进程,往往是可以多个读进程一起执行的,但过大的访问量,也会给系统带来很大的压力。此时,单台服务器已经无法满足我们的需求。但当我们的应用处于分布式环境下工作时,基本锁已经不再适用,这时候,我们就需要一种更加高级的锁机制来处理这个进程级别的代码同步和并发问题。
  • 其实,操作系统中提供了许多内置的函数,以帮助程序员来实现并发控制。但是,对于运行分布在多台机器上的多线程的程序来说,我们无法再通过操作系统的内置函数来实现对资源的控制。如果借助关系数据库事务来实现锁,这种方法会有许多不足,例如性能差、稳定性不足。所以,我们必须引入分布式锁来约束并发操作。

2.3 分布式锁的基本要求

分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,分布式锁对系统资源和数据起到了一个很好的协调作用。为了保证分布式锁可用,我们在确保锁实现的同时,需要至少满足以下几个条件:
(1)互斥性:在任何时候,锁只能由一个客户端获取,而不能由两个或多个客户端同时获取。
(2)高可用的获取锁与释放锁;
(3)高性能的获取锁与释放锁;
(4)具备可重入特性;
(5)具备锁失效机制:由于某些原因(如宕机等),获取锁的客户端未能及时释放锁,导致其它客户端无法进行获取,从而导致死锁,导致进程无法进行下去。因此,分布式锁应具备锁失效机制。
(6)具备非阻塞锁特性:当客户端没有获取到锁时,应直接将信息返回,通知获取锁失败。

3. 分布式锁的实现

分布式锁主要用于解决分布式系统中数据不一致的问题,分布式锁一般有3种实现方式:
(1)基于数据库的分布式锁;
(2)基于 Redis的分布式锁;
(3)基于ZooKeeper的分布式锁。

3.1 基于数据库的分布式锁

3.1.1选用数据库实现分布式锁的原因

  • 优点:简单,易实现;

3.1.2 基于数据库实现分布式锁的缺点

  • 没有锁失效机制、可用性、性能较差;
  • ·当并发量较大时,对数据库压力较大;

3.1.3分布式锁的实现

  • (1)基于表记录

最简单的方法是直接创建一张锁表,通过操作该表中的数据来实现分布式锁,此时,我们对method_name做一些唯一性约束。以保证,数据库同时接收到多个请求,保证只有一个操作可以成功获取锁,假设某个线程操作成功,那么我们可以认为该线程获得了该方法的锁,也就说明可以开始执行方法体内容。当我们想要获取某个资源时,我们就要向表中添加一条数据,执行完成相应方法体后删除对应的行数据即表示释放锁。

  • (2) 悲观锁

基于select … where … for update 排他锁来实现的,我们可以认为,获得排它锁意味着获得了分布式锁,接着进一步,我们就可以执行方法的业务逻辑,并通过connection.commit()操作来提交事务,从而释放锁。但是,数据库单点问题和可重入问题,在悲观锁中还是无法得到直接解决。

  • (3) 乐观锁

乐观锁是基于CAS思想(Compare and swap(比较与交换),用来解决多线程并发情况下使用锁造成性能开销的一种机制)来实现的,但不具有互斥性,并不会进行加锁,而是假设可以没有冲突的去完成某项操作,操作过程中认为不存在并发冲突,我们是通过增加递增的version字段对乐观锁进行实现。
若数据库中的version字段值和更新时携带的version字段值不同,则表示更新失败(即返回0)。由于写一条sql都需要判断,增加了数据库操作的次数,在高并发的一下,对数据库连接的开销是巨大的。

3.2 基于Redis的分布式锁

3.2.1选用Redis实现分布式锁的原因

(1)Redis是建于内存高性能数据库;
(2)Redis命令对此支持较好,实现起来较为方便;

3.2.2基于Redis实现分布式锁的缺点

(1)Redis实现分布式锁,需要自己不断地去尝试获取锁,比较消耗性能(需要轮询,占用CPU资源);
(2)如果在集群中,出现master宕机的情况。此时,锁key还没有同步到slave节点上,这时候会出现机器B从新的master上获取到了一个重复的锁的现象;
(3)假设拥有Redis锁的客户端因为某些原因挂了,如果想要释放锁,那么只能等到设定的超时时间到了。

3.2.3 基于Redis的分布式锁的实现

分布式架构 --- 分布式锁_第1张图片

(1)一般来讲,我们会使用setnx加锁,并设置一个唯一的分布式锁key,并对key设置对应的客户端唯一的标识。为了避免死锁,我们可以通过expire命令为锁设置一个超时时间,超过时间则自动释放锁。我们可以设置一个随机生成的UUID(随机字符串)来表示锁的value值,来判断在某一时刻是否释放该锁;
(2)我们在获取锁的同时还应设置一个获取的超时时间,若是在时间内一直未获取到锁,则表示放弃获取该锁;
(3)当释放锁时,我们需要通过UUID来判断锁的确定性,若是该锁,则执行delete命令进行锁释放。

3.3 基于Zookeeper的分布式锁

3.3.1选用Zookeeper实现分布式锁的原因

(1)Zookeeper具备高可用、可重入、阻塞锁等特性,有较好的的性能和可靠性;
(2)Zookeeper可解决失效死锁的问题;
(3)当获取不到锁时,我们可以通过注册个监听器,来自动尝试获取锁,因此,性能开销较小。
(4)当获取锁的客户端由于某些原因挂了,因为在Zookeeper中,创建的是临时znode,只要客户端挂了,临时节点znode就会消失,此时锁就会自动被释放;

3.3.2 Zookeeper实现分布式锁的缺点

(1)需要频繁的创建和删除节点,获取锁和释放锁都需要在Leader上执行,然后同步到Follower中,因此,在性能上不如Redis实现的分布式锁。
(2)易出现“羊群效应”;

3.3.3基于Zookeeper的分布式锁的实现

分布式架构 --- 分布式锁_第2张图片

基于Zookeeper的分布式锁的实现,主要有以下两种方法。

(1)可以借助开源库进行实现,例如,Apache的开源库Curator,是一个Zookeeper客户端。它提供的InterProcessMutex是可重入互斥锁,跨JVM工作,可以帮助我们来实现分布式锁。通过acquire方法来获取锁,release方法来释放锁。

(2)也可以通过自己编写,创建节点(临时节点)即加锁,删除节点即解锁,基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)某一时刻,线程thread_A想要申请获取锁,需要在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点后,然后寻找比自己小的兄弟节点,若找到并获取,若未找到,则说明不存在,表示当前线程顺序号最小,从而申请获得锁;
(4)线程thread_B想要获取锁,则它也要获取所有节点,判断自己不是最小节点,若不是,则需要设置监听比自己次小的节点;
(5)当线程thread_A的业务逻辑处理完之后,会删除自己的节点,线程thread_B监听到了事件变更,会重新判断自己是不是最小的节点,如果是,则申请获得锁。

4. 三种分布式锁实现方式的比较

实现方式 实现思路 优点 缺点
基于数据库 利用数据库自身提供的锁机制,要求数据库支持行级锁。 实现简单,稳定可靠。 性能差,无法适应高并发的场所;容易出现思索的情况。
基于Redis 使用setnx和lua脚本机制实现,保证对存序列的原子性操作。 性能好。 实现相对较复杂,有出现思索的可能性。
基于Zookeeper 基于zk的节点特性以及watch机制实现。 性能较好,可靠性高,有较好的实现阻塞锁。 实现相对复杂。

5. 总结

对于分别基于数据库、Redis、Zookeeper实现分布式锁的三种方式,任一方法都无法做到完美,都有利有弊。就像CAP理论一样,在性能、可靠性、复杂性等方面无法同时满足,所以,我们在使用分布式锁的时候应该根据业务场景来进行选择。

从各个角度对比可以得到以下结论:
(1)从理解的难易程度角度(从低到高):数据库 > Redis > Zookeeper
(2)从实现的复杂性角度(从低到高):Zookeeper >= Redis > 数据库
(3)从性能角度(从高到低):Redis > Zookeeper >= 数据库
(4)从可靠性角度(从高到低):Zookeeper > Redis > 数据库

参考文献:
[1]朱永超. 异构分布式系统的可靠性任务调度策略研究[D]. 南京理工大学.
[2]陈文武. 分布式锁技术研究[D]. 华南理工大学.
[3]周梅. 分布式锁的设计与实现[J]. 计算机工程, 2008, 34(16):3.
[4]徐彬. 基于分布式处理技术的物联网数据库创新研究与设计探析[J]. 数字通信世界, 2021(12):3.
[5]赖歆. 基于Redis的分布式锁的实现方案[J]. 信息通信, 2016(10):2.
[6]张俊. 基于Redis实现关系型数据库内存化研究[D]. 四川师范大学.
[7]韩雅丽. 分布式Redis高可用集群的设计与实现. 南京大学, 2019.
[8]刘芬, 王芳, 田昊. 基于Zookeeper的分布式锁服务及性能优化[C]// 2014:6.
[9]黄毅斐. 基于ZooKeeper的分布式同步框架设计与实现[D]. 浙江大学, 2012.
[10]赵玉京. 基于Zookeeper的分布式范围锁的设计与实现[D]. 华中科技大学, 2015.
[11]陈玉林, 王武. Zookeeper分布式锁的4种异常状态分析[J]. 2022(9).
[12]肖国云. 大数据时代电子商务安全问题探讨[J]. 商展经济, 2022(4):3.
[13]陈飞. 基于中间件的分布式服务器负载均衡方法[J]. 电子技术与软件工程, 2020(3):2.
[14]丁学智. 一种面向分布式服务器集群的动态负载均衡系统的实现[J]. 北京邮电大学, 2013.
[15]张君. 操作系统中进程死锁的探讨[J]. 电脑知识与技术:学术版, 2012, 8(1):3.
[16]陈更力,张青. Java并发线程中的死锁问题研究[J]. 长江大学学报自然科学版:理工卷(2期):72-75.
[17]朱洪伟. 基于单机的计算机网络实验平台的实现与应用[J]. 科技风, 2013(4):1.

你可能感兴趣的:(论文,分布式)