当前位置: 移动技术网 > IT编程>开发语言>Java > 分布式锁探讨

分布式锁探讨

2019年03月27日  | 移动技术网IT编程  | 我要评论

一、分布式锁背景

a、什么是锁?

从使用场景定义:当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。

锁的实现方式有多种,只要能满足所有线程都能看得到这个锁标记即可。

java中常见的锁:
synchronized
reentrantlock
reentrantreadwritelock

b、什么是分布式?

定义:分布式系统一定是由多个节点(计算机服务器)组成的系统,这些互通的节点上部署了各个服务,并且操作需要协同。

分布式系统对于终端用户而言,他们面对的就好像是一个服务器,提供用户需要的服务而已。

什么是cap?

定义:任何一个分布式系统都无法同时满足一致性consistency、可用性availability、和分区容错性partition tolerance,最多只能同时满足两项。

因为单个服务器偶尔宕机不可避免(或者因为停电或者自然灾害导致单机房不可用),网络状况不稳定,时而会有网络抖动,时而延时比较高,所以必须满足p,所以一般会出现ap系统和cp系统。

c、a+b==》什么是分布式锁?

定义:在分布式环境下,一个共享的可见的公共资源,各个线程通过对这个公共资源的抢占,能够使得一个代码块在同一时间只能被一个机器的一个线程执行,那这个公共资源就是分布式锁,或者说这整个机制就是分布式锁。

或者从使用场景定义:分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性

二、分布式锁实现方式

 锁的实现方式有多种,只要能满足所有线程都能看得到这个锁标记即可。

常见的方式是使用数据库、缓存或者zookeeper来实现分布式锁,除了这些,其实一个网络中的共享可见的可读写的资源就可以用作实现锁。

锁的操作主要有两个,即lock和unlock。

a、数据库

我们可以利用数据库的特性,lock即插入一条数据到数据库,unlock即删除该条数据。直接看图

1、建mysql表,lock_name上建立唯一性索引;

2、server1开始获取分布式锁"aaa";

3、server1调用lock("aaa"),数据库中没有"aaa",插入"aaa"成功,server1成功获得分布式锁"aaa";

4、server2调用lock("aaa"),插入"aaa"失败,获取锁失败;

5、server1调用unlock("aaa"),删除数据行"aaa";

6、server2调用lock("aaa"),插入"aaa"成功,获取锁成功;

 

熟悉java锁的同学发现问题了,这个锁也太简陋了,锁都不阻塞的。

好,我们严格地来讨论下锁的特性,

首先,我们罗列下锁的特性,我能想到的有以下这些,欢迎留言补充:

分布式锁的需求
1、在分布式环境下,一个代码块在同一时间只能被一个机器的一个线程执行
2、高可用地获取锁和释放锁
3、高性能的获取锁和释放锁
4、具备锁失效机制,防止死锁
5、是否可重入
6、是否非阻塞
7、自旋锁
8、乐观锁/悲观锁
9、是否公平
10、独享锁/共享锁
11、分段锁

 刚才提到的问题,锁最基本的应该具有阻塞特性,我们修正一下,如下图

在插入数据失败后,我们通过不断地重试来达到阻塞的特性。

 下面我们来逐条讨论下以上提到的特性:

分布式锁的需求
1、在分布式环境下,一个代码块在同一时间只能被一个机器的一个线程执行

基本的,所有的锁都应该满足
2、高可用地获取锁和释放锁                                                                                 

可用性受数据库单点限制,如果要提高可用性,使用主备库,通过数据同步主备切换达到在主库宕机时的高可用
3、高性能的获取锁和释放锁 数据库读写性能

锁的获取和释放就是数据库的插入和删除,性能因具体数据库不同而不同,表的行数的规模大小也影响着性能,性能还受网络的影响,在笔者的网络中插入大概耗时约80ms
4、具备锁失效机制,防止死锁

如果节点在获取锁之后释放锁之前宕机,会发生死锁。可以在表中加入时间字段,用一个定时任务,定期地清理时间过期的锁,但是这个过期时间的大小值得探讨,很深入地探讨。
5、是否可重入

如果想获得类似reentrantlock的特性,可以在表中写入线程的信息(ip地址+进程号+线程号),可以先查询,如果是线程自己拥有的锁,直接返回成功并计数count
6、是否非阻塞

如果要获得阻塞特性,通过不断自旋重试
7、自旋锁

同上
8、乐观锁/悲观锁

分布式锁就是悲观锁。如果不想用分布式锁,对数据不上锁,在表中加入列version,通过cas版本号来控制并发写
9、是否公平

首先解释下公平锁:公平锁即是根据lock()的时间顺序,先到先得,即fifo,这个是常见的规则。

单机情况下非公平锁的好处:公平锁忽略了线程之间切换以及线程内核态和用户态转换的耗时,其实可以利用线程切换的间隙,让其它正在被调度执行的线程插队去获取锁,然后释放锁,还给等待线程。这样不耽误事的前提下,提高了锁的性能。请自行去了解下非公平锁。

言归正传,如果需要实现公平锁,设计一张排队表,让lock()的线程首先在排队表中排队,当发现自身排在队头才有资格去真正获取锁。当然引入复杂性,必然会导致其它问题,兵来将挡,水来土掩。
10、独享锁/共享锁

以上的分布式锁,是定义的一把独享锁(独占锁、互斥锁、排它锁)。

reentrantlock就是一种排它锁。countdownlatch是一种共享锁。这两类都是单纯的一类,即,要么是排它锁,要么是共享锁。
reentrantreadwritelock是同时包含排它锁和共享锁特性的一种锁,这里主要以reentrantreadwritelock为例来进行分析学习。

如果要实现共享锁,锁类型+排队表

 

 

e、结论

分布式锁可以根据业务需要,量身定制。 

 

三、存在的问题

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网