zookeeper 分布式锁是怎么实现的?

547 阅读5分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

Zookeeper 都能做啥

  1. Zookeeper 非公平锁/公平锁/共享锁

  2. Leader 选举在分布式场景中的应用

  3. Spring Cloud Zookeeper注册中心实战

zookeeper的分布式锁

非公平锁

image.png 啥是非公平锁呢?打个比喻,就是售票窗口,只有站到通道的情况下,才能购买票。第一个先去的人,本来应该最享有优先购买权,但是呢后面一堆人都着急买票,在没有任何约束的情况下,一起拥挤到售票窗口,结果体弱的N就被挤到后面去了,身强体壮的X抢占到了购票同道处(create -e /exclusive/lock),可以优先购买。这对N来说是不是太不公平了。在X号买票的过程中,剩下的人都盯着X号(get -w /exclusive/lock),只要X号买完票(delete /exclusive/lock),剩下的人再次一拥而上,抢占购票通道(create -e /exclusive/lock),抢占购票权。这就是非公平锁,被称为:惊群效应(羊群效应)

流程如如下: image.png 如上实现方式在并发问题比较严重的情况下,性能会下降的比较厉害,主要原因是,所有的连接都在对同一个节点进行监听,当服务器检测到删除事件时,要通知所有的连接,所有的连接同时收到事件,再次并发竞争,这就是羊群效应。这种加锁方式是非公平锁的具体实现:如何避免呢,我们看下面这种方式。

公平锁

image.png 售票厅遭到举报售票秩序太乱,需要整顿,然后整顿方案来了,让所有人按秩序进行排队,先到先购买。于是想了一个办法,每来一个人就按顺序给他发一个编号,排在最前面的编号最小,越往后编号越大,这样持有编号的人,只要盯住前面编号的购票人就行,只要前面的购票人员离开购票通道那下一个就是自己。

流程图如下

image.png 如上借助于临时顺序节点,可以避免同时多个节点的并发竞争锁,缓解了服务端压力。这种实现方式所有加锁请求都进行排队加锁,是公平锁的具体实现。

共享锁

这售票厅也是倒霉,不久又遭到投诉了,理由是:
有些人闲的没事,虽然不买票,但是也在排队,想问窗口排到他的时候还有没有票(实在想不到啥场景了,就凑合吧),群众有想法,总得解决不是。

于是内部开会,针对这个问题进行讨论

1、读写不一致

image.png 如上图,三个线程同时进来,但是每个线程由于各种原因,执行的时间不一致。
1、线程1,写到数据库是10,并删除了缓存
2、线程3,读缓存无数据,然后查询数据库,开始做自己的业务逻辑
3、线程2,进来将数据库更新为6,并删除缓存
4、线程3,做完自己的逻辑之后,将缓存更新为10(之前读取到10)
这样也就造成了缓存准确。

2、双写不一致

image.png 1、线程1,更新数据库,但是由于某些原因,未及时更新缓存。
2、线程2,进来更新数据,然后更新了缓存。
3、线程3,执行完逻辑,更新缓存,此时把线程2的缓存更新
这样造成缓存不准确。

3、共享锁实现原理

前面这两种加锁方式有一个共同的特质,就是都是互斥锁,同一时间只能有一个请求占用,如果是大量的并发上来,性能是会急剧下降的,所有的请求都得加锁,那是不是真的所有的请求都需要加锁呢?答案是否定的,比如如果数据没有进行任何修改的话,是不需要加锁的,但是如果读数据的请求还没读完,这个时候来了一个写请求,怎么办呢?有人已经在读数据了,这个时候是不能写数据的,不然数据就不正确了。直到前面读锁全部释放掉以后,写请求才能执行,所以需要给这个读请求加一个标识(读锁),让写请求知道,这个时候是不能修改数据的。不然数据就不一致了。如果已经有人在写数据了,再来一个请求写数据,也是不允许的,这样也会导致数据的不一致,所以所有的写请求,都需要加一个写锁,是为了避免同时对共享数据进行写操作。

image.png 如上图,如果有写请求,他只需要watch他前面排好序最近一个节点即可。如果两个写请求中间有多个读请求,最后一个读请求,不需要监听他前面的读请求,只需要监听他前面的写请求即可,只要写请求完成,就可以进行读取操作。这就实现了一个共享锁