实现分布式锁的三种方式

引言:

        很多小伙伴在学习Java的时候,总是感觉Java多线程在实际的业务中很少使用,以至于不会花太多的时间去学习,技术债不断累积!

        等到了一定程度的时候对于与Java多线程相关的东西就很难理解,今天需要探讨的东西也是一样的和Java多线程相关的!做好准备,马上开车!

        学过Java多线程的应该都知道什么是锁,没学过的也不用担心,Java中的锁可以简单的理解为多线程情况下访问临界资源的一种线程同步机制。

        在学习Java的过程中会遇到各种各样的锁的概念:公平锁、非公平锁、自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、读写锁、互斥锁等。

一、为什么要使用分布式锁

        为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行。

        在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。

        但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。

        为了解决这个问题,就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

二、分布式锁应该具备哪些条件

        1、在分布式系统环境下,一个方法在同一时间只能被一台服务器的一个线程执行;

        2、高可用、高性能的获取锁与释放锁;

        4、具备可重入特性;

        5、具备锁失效机制,防止死锁;

        6、具备非阻塞锁特性,即没有获取到锁,将直接返回获取锁失败信息。

三、分布式锁的三种实现方式

        在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。

        有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

1. 基于数据库实现分布式锁;

2. 基于 Redis 实现分布式锁;

3. 基于Zookeeper实现分布式锁;

尽管有这三种方案,但是不同的业务也要根据自己的情况进行选型,他们之间没有最好只有更适合

1)基于数据库的实现方式

基于数据库的实现方式的核心思想是:

在数据库中创建一个表,表中包含 "method_name" 等字段,并在 "method_name" 字段上创建唯一索引;(唯一性约束要求该列不出现重复值。)

这样的话,想要执行某个方法,就使用 同一个名字 向表中的"method_name"字段插入数据,成功插入则获取锁,执行完成后删除对应的数据,释放锁。

即,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。

注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的玩法!

2)基于Redis的实现方式

2.1)使用的命令介绍:

(1)SETNX

SETNX key val

  • 当且仅当key不存在时,set一个key为val的字符串,返回1;

  • 若key存在,则什么都不做,返回0。

(2)expire

expire key timeout:为key设置一个超时时间,单位为second,超过这个时间key会失效。

(3)delete

delete key:删除key。

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

2.2)条件:

设置一个key,所有线程的key都是这同一个key;

2.3)流程:

1、线程1先执行setnx方法,申请获取锁资源,

        setnx返回的结果为1,所以线程1获取到了锁资源,开始处理请求,此时并通过expire方法设置一个过期时间;

2、key未超时的时候:

        2.1、如果,线程1还未释放锁资源: 其他的线程尝试获取锁资源,先执行setnx方法,由于所有线程的key都是这同一个key,所以setnx返回的结果为0,判定为获取锁资源失败。

        2.2、如果,线程1释放了锁资源: 其他的线程们开始抢夺锁资源,抢到了锁资源的那个线程开始执行处理(并设置过期时间),其他的线程则继续等待;

3、key超时的时候:

        强制迫使线程1释放锁资源,然后其他的线程们开始抢夺锁资源...

3)基于ZooKeeper实现分布式锁:

1> Zookeeper的实现分布式锁的特点:

  • ZooKeeper的内部是一个分层的文件系统目录树结构,而且,同一个目录下只能有一个唯一的文件名。

  • 临时有序节点;

  • 先创建的节点的节点值比较小;

2> 基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock;

(2)线程A想获取锁,就必须先在mylock目录下创建一个 临时顺序节点;

(3)创建之后,线程A再查看一下mylock目录下所有的子节点,看一下是否存在有比自己这个临时有序节点还小的兄弟节点,如果不存在,则说明当前线程的顺序号最小,于是获得锁;

(4)同理,线程B也要先创建一个临时顺序节点,然后获取所有的节点,再判断自己的节点是不是最小节点。不是,则监听比自己次小的那个节点;

(5)线程A处理完请求,删除掉自己的节点,线程B监听到变更事件,再次判断自己是不是最小的节点,如果是则获得锁。还不是,则继续监听比自己次小的节点。

四,总结

        上面的三种实现方式,没有哪一种在所有的场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。

        在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。

        当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值