欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

分布式锁探讨

程序员文章站 2022-04-08 19:09:00
一、分布式锁背景 a、什么是锁? 从使用场景定义:当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。 锁的实现方式有多种,只要能满足所有线程都能看得到这个锁标记即可。 Java中常见的锁: synchronized Reentrant ......

一、分布式锁背景

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、结论

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

 

三、存在的问题