首先,分布式锁和咱们日常讲到的锁原理基本同样,目的就是确保在多个线程并发时,只有一个线程在同一刻操做这个业务或者说方法、变量。java
在一个进程中,也就是一个jvm或者说应用中,咱们很容易去处理控制,在jdk java.util并发包中已经为咱们提供了这些方法去加锁,好比synchronized关键字或者Lock锁,均可以处理。node
可是咱们如今的应用程序若是只部署一台服务器,那并发量是不好的,若是同时有上万的请求,颇有可能形成服务器压力过大而瘫痪。想一想双十一和大年三十晚上十点,瓜分支付宝红包等业务场景,天然须要用到多台服务器去同时处理这些业务,这些服务可能会有上百台同时处理。程序员
可是咱们想想,若是有100台服务器要处理分成包的业务,如今假设有1亿的红包,1千万我的分,金额随机,那么这个业务场景下,是否是必须确保这1千万我的最后分的红包金额总和等于1亿?数据库
若是处理很差~~每人分到100万,那马云爸爸估计大年初一,就得宣布破产了~~安全
1、常规锁会形成什么状况?服务器
首先说一下咱们为何要搞集群。网络
简单理解就是,需求量(请求并发量)变大了,一个工人处理能力有限,那就多招一些工人来一块儿处理。多线程
假设1千万个请求平均分配到100台服务器上,每一个服务器接收10w的请求。这10w个请求并非在同一秒中来的,多是在1,2个小时内,能够联想下咱们三十晚上开红包,等到10:20开始,有的人立马开了,有的人等到12点才想起来。架构
那这样的话,平均到每一秒上的请求也就不到1千个,这种压力通常的服务器仍是能够承受的。并发
等因而在每一个服务器中去分1亿,也就是10w个用户分了一个亿,最后总计有100个服务器,要分100亿。
若是真这样了,虽然说马云爸爸不会破产(据最新统计马云有2300亿人民币),那分成包的开发项目组,以及产品经理,能够GG了~
简化结构图以下:
2、分布式锁怎么去处理?
那么为了解决这个问题,让1000万用户只分1亿,而不是100亿,这个时候分布式锁就派上用处了。
分布式锁能够把整个集群就看成是一个应用同样去处理,那么也就须要这个锁独立于每个服务以外,而不是在服务里面。
假设第一个服务器接收到用户1的请求后,不能只在本身的应用中去判断还有多少钱能够分了,而须要去外部请求专门负责管理这1亿红包的人(服务),问他:哎,我这里要分100块,给我100。
管理红包的妹子(服务)一看,还有1个亿,那好,给你100块,而后剩下99999900块。
第二个请求到来后,被服务器2获取,继续去询问,管理红包的妹子,我这边要分10块,管理红包的妹子先查了下还有99999900,那就说:好,给你10块。那就剩下99999890块。
等到第1000w个请求到来后,服务器100拿到请求,继续去询问,管理红包的妹子,我要100,妹子翻了翻白眼,对你说,就剩1块了,爱要不要,那这个时候就只能给你1块了(1块也是钱啊,买根辣条仍是能够的)。
这些请求编号1,2不表明执行的前后顺序,正式的场景下,应该是100台服务器每一个服务器持有一个请求去访问负责管理红包的妹子(服务),那在管红包的妹子那里同时会接收到100个请求,这个时候就须要在负责红包的妹子那里加个锁就能够了(抛绣球),大家100个服务器谁拿到锁(抢到绣球),谁就进来和我谈,我给你分,其余人就等着去吧。
通过上面的分布式锁的处理后,马云爸爸终于放心了,决定给红包团队每人加一个鸡腿。
简化的结构图以下:
3、分布式锁的实现有哪些?
说到分布式锁的实现,仍是有不少的,有数据库方式的,有Redis分布式锁,有Zookeeper分布式锁等等。
咱们若是采用Redis做为分布式锁,那么上图中负“责红包的妹子(服务)”,就能够替换成Redis,请自行脑补。
一、为何Redis能够实现分布式锁?
首先Redis是单线程的,这里的单线程指的是网络请求模块使用了一个线程(因此不需考虑并发安全性),即一个线程处理全部网络请求,其余模块仍用了多个线程。
在实际的操做中过程大体是这样子的:
服务器1要去访问发红包的妹子,也就是Redis,那么他会在Redis中经过"setnx key value" 操做设置一个key进去,value是啥不重要,重要的是要有一个key,也就是一个标记,并且这个key你爱叫啥叫啥,只要全部的服务器设置的key相同就能够。
假设咱们设置一个,以下图:
那么咱们能够看到会返回一个1,那就表明了成功。
若是再来一个请求去设置一样的key,以下图:
这个时候会返回0,那就表明失败了。
那么咱们就能够经过这个操做去判断是否是当前能够拿到锁,或者说能够去访问“负责发红包的妹子”,若是返回1,那我就开始去执行后面的逻辑,若是返回0,那就说明已经被人占用了,我就要继续等待。
当服务器1拿到锁以后,进行了业务处理,完成后,还须要释放锁,以下图所示:
删除成功返回1,那么其余的服务器就能够继续重复上面的步骤去设置这个key,以达到获取锁的目的。
固然以上的操做是在Redis客户端直接进行的,经过程序调用的话,确定就不能这么写,好比java就须要经过jedis去调用,可是整个处理逻辑基本都是同样的。
经过上面的方式,咱们好像是解决了分布式锁的问题,可是想一想还有没有什么问题呢?
对,问题仍是有的,可能会有死锁的问题发生,好比服务器1设置完以后,获取了锁以后,突然发生了宕机。
那后续的删除key操做就无法执行,这个key会一直在Redis中存在,其余服务器每次去检查,都会返回0,他们都会认为有人在使用锁,我须要等。
为了解决这个死锁的问题,咱们就须要给key设置有效期了。
设置的方式有2种:
这种方式至关于,把锁持有的有效期,交给了Redis去控制。若是时间到了,你尚未给我删除key,那Redis就直接给你删了,其余服务器就能够继续去setnx获取锁。
可是这块有一个问题,也就是不光你服务器2可能会发现服务器1超时了,服务器3也可能会发现,若是恰好服务器2 setnx操做完成,服务器3就接着删除,是否是服务器3也能够setnx成功了?
那就等因而服务器2和服务器3都拿到锁了,那就问题大了。这个时候怎么办呢?
这个时候须要用到“GETSET key value”命令了。这个命令的意思就是获取当前key的值,而且设置新的值。
假设服务器2发现key过时了,开始调用getset命令,而后用获取的时间判断是否过时,若是获取的时间仍然是过时的,那就说明拿到锁了。
若是没有,则说明在服务2执行getset以前,服务器3可能也发现锁过时了,而且在服务器2以前执行了getset操做,从新设置了过时时间。
那么服务器2就须要放弃后续的操做,继续等待服务器3释放锁或者去监测key的有效期是否过时。
这块其实有一个小问题是,服务器3已经修改了有效期,拿到锁以后,服务器2也修改了有效期,可是没能拿到锁,可是这个有效期的时间已经被在服务器3的基础上有增长一些,可是这种影响其实仍是很小的,几乎能够忽略不计。
二、为何Zookeeper可实现分布式锁?
百度百科是这么介绍的:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。
那对于咱们初次认识的人,能够理解成ZooKeeper就像是咱们的电脑文件系统,咱们能够在d盘中建立文件夹a,而且能够继续在文件夹a中建立文件夹a1,a2。
那咱们的文件系统有什么特色?那就是同一个目录下文件名称不能重复,一样ZooKeeper也是这样的。
在ZooKeeper全部的节点,也就是文件夹称做Znode,并且这个Znode节点是能够存储数据的。
咱们能够经过“ create /zkjjj nice”来建立一个节点,这个命令就表示,在跟目录下建立一个zkjjj的节点,值是nice。一样这里的值,和我在前面说的Redis中的同样,没什么意义,你随便给。
另外ZooKeeper能够建立4种类型的节点,分别是:
首先说下持久性节点和临时性节点的区别:
Zookeeper有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增长删除)等,Zookeeper会通知客户端。
3、在Zookeeper中如何加锁?
下面咱们继续结合咱们上面的分成包场景,描述下在Zookeeper中如何加锁。
假设服务器1,建立了一个节点 /zkjjj,成功了,那服务器1就获取了锁,服务器2再去建立相同的锁,就会失败,这个时候就只能监听这个节点的变化。
等到服务器1处理完业务,删除了节点后,他就会获得通知,而后去建立一样的节点,获取锁处理业务,再删除节点,后续的100台服务器与之相似。
注意这里的100台服务器并非挨个去执行上面的建立节点的操做,而是并发的,当服务器1建立成功,那么剩下的99个就都会注册监听这个节点,等通知,以此类推。
可是你们有没有注意到,这里仍是有问题的,仍是会有死锁的状况存在,对不对?
当服务器1建立了节点后挂了,没能删除,那其余99台服务器就会一直等通知,那就完蛋了。。。
这个时候就须要用到临时性节点了,咱们前面说过了,临时性节点的特色是客户端一旦断开,就会丢失,也就是当服务器1建立了节点后,若是挂了,那这个节点会自动被删除,这样后续的其余服务器,就能够继续去建立节点,获取锁了。
可是咱们可能还须要注意到一点,就是惊群效应:举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但全部鸽子都会被惊动来争夺,没有抢到…
就是当服务器1节点有变化,会通知其他的99个服务器,可是最终只有1个服务器会建立成功,这样98仍是须要等待监听,那么为了处理这种状况,就须要用到临时顺序性节点。大体意思就是,以前是全部99个服务器都监听一个节点,如今就是每个服务器监听本身前面的一个节点。
假设100个服务器同时发来请求,这个时候会在/zkjjj节点下建立100个临时顺序性节点/zkjjj/000000001,/zkjjj/000000002,一直到/zkjjj/000000100,这个编号就等因而已经给他们设置了获取锁的前后顺序了。
当001节点处理完毕,删除节点后,002收到通知,去获取锁,开始执行,执行完毕,删除节点,通知003~以此类推。
若是你是Java程序员,若是你想提高本身,若是你想变强,加q群:479499375,可获取一份Java架构进阶技术精品视频。(高并发+Spring源码+JVM原理解析+分布式架构+微服务架构+多线程并发原理等...这些成为架构师必备的内容)以及Java进阶学习路线图。