一文弄懂“分布式锁”,一直以来你的选择依据正确吗?

本文主要会关注的问题是“分布式锁”的问题。node

多线程状况下对共享资源的操做须要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就须要一个分布式锁服务。redis

常见的分布式锁实现通常是基于DB、Redis、Zookeeper。下面笔者会按照顺序分析下这3种分布式锁的设计与实现,想直接看分布式锁总结的小伙伴可直接翻到文档末尾处。数据库

分布式锁的实现由多种方式,可是无论怎样,分布式锁通常要有如下特色:缓存

  • 排他性:任意时刻,只能有一个client能获取到锁;安全

  • 容错性:分布式锁服务通常要知足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就能够进行加锁解锁操做;网络

  • 避免死锁:分布式锁必定能获得释放,即便client在释放以前崩溃或者网络不可达。多线程

除了以上特色以外,分布式锁最好也能知足可重入、高性能、阻塞锁特性(AQS这种,可以及时从阻塞状态唤醒)等,下面就话很少说,赶忙上(开往分布式锁的设计与实现的)车。架构

1、DB锁并发

在数据库新建一张表用于控制并发控制,表结构能够以下所示:异步

key_id做为分布式key用来并发控制,memo可用来记录一些操做内容(好比memo可用来支持重入特性,标记下当前加锁的client和加锁次数)。将key_id设置为惟一索引,保证了针对同一个key_id只有一个加锁(数据插入)能成功。此时lock和unlock伪代码以下:

注意,伪代码中的lock操做是非阻塞锁,也就是tryLock,若是想实现阻塞(或者阻塞超时)加锁,只修反复执行lock伪代码直到加锁成功为止便可。

基于DB的分布式锁其实有一个问题,那就是若是加锁成功后,client端宕机或者因为网络缘由致使没有解锁,那么其余client就没法对该key_id进行加锁而且没法释放了。为了可以让锁失效,须要在应用层加上定时任务,去删除过时还未解锁的记录,好比删除2分钟前未解锁的伪代码以下:

由于单实例DB的TPS通常为几百,因此基于DB的分布式性能上限通常也是1k如下,通常在并发量不大的场景下该分布式锁是知足需求的,不会出现性能问题。

不过DB做为分布式锁服务须要考虑单点问题,对于分布式系统来讲是不容许出现单点的,通常经过数据库的同步复制,以及使用vip切换Master就能解决这个问题。

以上DB分布式锁是经过insert来实现的,若是加锁的数据已经在数据库中存在,那么用select xxx where key_id = xxx for udpate方式来作也是能够的。

2、Redis锁

Redis锁是经过如下命令对资源进行加锁:

set key_id key_value NX PX expireTime

其中,set nx命令只会在key不存在时给key进行赋值,px用来设置key过时时间,key_value通常是随机值,用来保证释放锁的安全性(释放时会判断是不是以前设置过的随机值,只有是才释放锁)。因为资源设置了过时时间,必定时间后锁会自动释放。

set nx保证并发加锁时只有一个client能设置成功(Redis内部是单线程,而且数据存在内存中,也就是说Redis内部执行命令是不会有多线程同步问题的),此时的lock/unlock伪代码以下:

分布式锁服务中的一个问题

若是一个获取到锁的client由于某种缘由致使没能及时释放锁,而且Redis由于超时释放了锁,另一个client获取到了锁,此时状况以下图所示:

那么如何解决这个问题呢?

一种方案是引入锁续约机制,也就是获取锁以后,释放锁以前,会定时进行锁续约,好比以锁超时时间的1/3为间隔周期进行锁续约。

关于开源的Redis的分布式锁实现有不少,比较出名的有redisson、百度的dlock,关于分布式锁,笔者也写了一个简易版的分布式锁Redis-lock,主要是增长了锁续约和可同时针对多个key加锁的机制。

对于高可用性,通常能够经过集群或者master-slave来解决,Redis锁优点是性能出色,劣势就是因为数据在内存中,一旦缓存服务宕机,锁数据就丢失了。

像Redis自带复制功能,能够对数据可靠性有必定的保证,可是因为复制也是异步完成的,所以依然可能出现master节点写入锁数据而未同步到slave节点的时候宕机,锁数据丢失问题。

3、Zookeeper分布式锁

ZooKeeper是一个高可用的分布式协调服务,由雅虎建立,是Google Chubby的开源实现。

ZooKeeper提供了一项基本的服务:分布式锁服务。想学习Java工程化、分布式架构、高并发、高性能、深刻浅出、微服务架构、Spring,MyBatis,Netty源码分析等技术能够加群:479499375,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们,欢迎进群一块儿深刻交流学习。

Zookeeper重要的3个特征是:zab协议、node存储模型和watcher机制。经过zab协议保证数据一致性,Zookeeper集群部署保证可用性,node存储在内存中,提升了数据操做性能,使用watcher机制,实现了通知机制(好比加锁成功的client释放锁时能够通知到其余client)。

Zookeeper node模型支持临时节点特性,即client写入的数据时临时数据,当客户端宕机时临时数据会被删除,这样就不须要给锁增长超时释放机制了。

当针对同一个path并发多个建立请求时,只有一个client能建立成功,这个特性用来实现分布式锁。注意:若是client端没有宕机,因为网络缘由致使Zookeeper服务与client心跳失败,那么Zookeeper也会把临时数据给删除掉的,这时若是client还在操做共享数据,是有必定风险的。

基于Zookeeper实现分布式锁,相对于基于Redis和DB的实现来讲,使用上更容易,效率与稳定性较好。curator封装了对Zookeeper的API操做,同时也封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等,使用curator进行分布式加锁示例以下:

4、总结

从上面介绍的3种分布式锁的设计与实现中,咱们能够看出每种实现都有各自的特色,针对潜在的问题有不一样的解决方案,概括以下:

  • 性能:Redis > Zookeeper > DB。

  • 避免死锁:DB经过应用层设置定时任务来删除过时还未释放的锁,Redis经过设置超时时间来解决,而Zookeeper是经过临时节点来解决。

  • 可用性:DB可经过数据库同步复制,vip切换master来解决;Redis可经过集群或者master-slave方式来解决;Zookeeper自己本身是经过zab协议集群部署来解决的。注意,DB和Redis的复制通常都是异步的,也就是说某些时刻分布式锁发生故障可能存在数据不一致问题,而Zookeeper自己经过zab协议保证集群内(至少n/2+1个)节点数据一致性。

  • 锁唤醒:DB和Redis分布式锁通常不支持唤醒机制(也能够经过应用层本身作轮询检测锁是否空闲,空闲就唤醒内部加锁线程),Zookeeper可经过自己的watcher/notify机制来作。

使用分布式锁,安全性上和多线程(同一个进程内)加锁是无法比的,可能因为网络缘由,分布式锁服务(由于超时或者认为client挂了)将加锁资源给删除了,若是client端继续操做共享资源,此时是有隐患的。

所以,对于分布式锁,一个是要尽可能提升分布式锁服务的可用性,另外一个就是要部署同一内网,尽可能下降网络问题发生概率。

这样来看,貌似分布式锁服务不是“完美”的(PS:技术貌似也很差作到十全十美 :( ),那么开发人员该如何选择分布式锁呢?最好是结合本身的业务实际场景,来选择不一样的分布式锁实现,通常来讲,基于Redis的分布式锁服务应用较多。

相关文章
相关标签/搜索