分布式锁的由来:
其实:分布式锁的原理和jdk中提供的自旋锁,偏向锁,公平锁/非公平锁,同步锁的原理是一样的。但是jvm中的锁只针对一个jvm有效。如果是在多JVM上执行共享资源操作的时候,这个jdk中的锁就失效了,这时候的锁不能锁住每个jvm,多个jvm就相当于多个java虚拟机了操作共享资源,这时就应该使用分布式锁。举个例子:以秒杀为例:相当于每台机子上部署了相同的程序去执行对共享资源操作。三台机器同时去抢商品10件,谁先获得到时间片,谁就先执行count--。可能第二,三台机器拿到的是10,这时商品总数为9,第一台拿到是9,这时商品总数为8,现在三个人都抢购完了,第一个人却拿到了9。明明是三个人抢到了,商品数量却为8,肯定是不行的。所以这就是在多线程的情况可能导致数据不安全的问题,因为商品是共享资源。
解决:
①、如下图所示:在操作商品的时候不能去直接操作商品的数量,需要先拿到锁。一般为了效率问题,一般会用zk的分布式锁,因为分布式锁可以做集群。prod_lock是zk集群中的永久节点,下面是三个jvm。在同一个时间去抢在永久节点下面创建Lock节点。刚开始Lock节点是不存在的。
②、如下图所示:
谁创建成功了Lock节点,谁就可以操作商品的数量,比如A先创建了Lock节点,然后A就可以去操作商品,这时商品的数量减1为9。
③、然后A这个客户端将Lock节点给删掉。然后去通知B,C两个客户端。
B,C就马上去抢占Lock这个节点。谁先创建成功,谁就先获得到锁。
④、如果C创建了Lock节点,然后C拿到了锁,从而去操作商品。然后操作完又会去释放Lock,然后通知。
这里当每一个客户端释放Lock的时候,下一轮也会去抢锁。
上面的分布式锁是排它锁(X锁):当一个客户端获取到了锁之后,其他的客户端将处于一种阻塞的状态。
上面的锁是有问题的,比如当一个客户端拿到锁之后,不下单,这时锁就不会被释放,所有的客户端都会在等待,这时就会成为死锁的状态。
针对上面的问题:解决方案如下:
A、可以加一个过期的时间,如果超过了规定的时间内,还没有删除的话,可以定义一个定时器去删除这个节点。
B、创建的节点为临时节点,超过了有效的时间,它会自动的断开,那么会话将会被结束,会话结束,临时节点将自动删除掉,一旦干掉,这里又可以竞争锁了。
排它锁的执行效率是不高的,针对小项目是可以够用的,针对大项目的话效率是低的因为比如说有100个客户端,其中有一个拿到了锁,剩下的99个客户端都在等。
2、实际项目中用的锁是共享锁:
①、这个Lock锁是共享的,任何的人都可以去拿它。不需要这个Lock删了才可以去拿,这个Lock不需要平凡的建和删,每一个客户端都可以拿到这个Lock锁,只要拿到了这个Lock节点,由于线程是无序的,就可以在Lock节点下面创建很多个临时顺序节点。
②、临时顺序节点会随着顺序的增加而增加,由于网速的原因,所以创建的节点不能按照1,2,3,4,5这样的顺序而排序 。这时需要对Lock节点下面的所有的子节点进行排序,比如从小到大的排序,然后筛选出最小的节点,然后最小的节点所对应的客户端获取到锁,这里是获得锁的时候就需要排序。
③、最小的节点获取到锁之后,会释放-Lock-0000021,从而释放锁。
方式1:然后没有获取到锁的所有的顺序节点将会订阅一个最小节点被删除的订阅事件。
这种方式是不可取的,假如说有100个客户端,这个时候将会最小的节点获取到锁,将会有99个节点将会收到通知,这时会引起网络阻塞或者锁故障。因为这些的节点也不知道自己是最小的,所以都会去尝试获得锁,所以99个节点将会同时去获得锁,这时就会出现网络故障。这就有点像经济学中的羊群效应。如果业务量非常小的话,用这种方法是可取的。
方式2:业务量比较大,集群比较大的话,客户端很大的情况下是不可取的。
解决办法:所有的临时顺序节点将监听比自己小的节点就可以了。小的节点删掉的话会通知下一个节点。每次获取锁的时候,执行完然后删除,然后再进行排序。-lock-00000021监听-lock-00000011,-lock-00000031监听-lock-0000021。这样就可以解决了大流量去获取到锁的情况。
共享锁更有效的减少了创建了锁的时间,效率会很高。
3、分布式锁的优化:
如果在创建临时顺序节点的时候会出现网络抖动(网络不稳定的情况)
由于网络不稳定导致节点15的会话断开,从而17的节点和11的节点从而同时拿到锁。这时会造成并发的问题。下面的图是解决方案:
革命尚未成功,继续努力!!!