主流分布式锁实现方案

谈到分布式系统,就不得不谈谈分布式锁。而主流的实现java分布式锁实现方案也就那么几种,有基于Redis实现的,也有基于ZK的,有基于数据库实现的分布式锁,下面我将谈谈它们各种的实现方案。

基于Redis实现分布式锁

基于Redis实现分布式锁应该是比较普遍的,实现起来比较简单.其主要是利用setnx来实现的,具体语法是setnx key val,当该key不存在时就设置value,如果已经存在该key了就直接返回。能这样做主要得益于Redis的单线程结构,能保证setnx是原子性的,其伪代码为:

 if (conn.setnx(lockKey, value) == 1) {

 }

这样做存在一个问题:

  1. 没有给lockKey设置过期时间,有可能导致该key一直不能释放,从而使其它线程不能访问。

有人立马反应过来了,"这还不简单?,用expire设置一下过期时间即可",所以就有了下面的这段代码:

if (conn.setnx(lockKey, value) == 1) {
      conn.expire(lockKey, expireTime);//expireTime为过期时间
}

到底这样做有没有问题呢?考虑一下这种情况,当线程刚刚通过setnx设置值完毕后,系统因为某个原因宕机(断点或者系统问题)导致还没来得及执行expire方法,这时也会引发和上面同样的问题。也就是说简单的设置一个过期时间还不行,因为没法保证setnxexpire是原子操作,随时都可能setnx成功但expire失败。幸运的是,Redis提供了这样一个原子性保证。像Jedis没有直接通过setnx设置过期时间的方法,可以使用这个方法:

 @Override
  public String set(final String key, final String value, final String nxxx, final String expx,
      final long time) {
    return new JedisClusterCommand<String>(connectionHandler, maxRedirections) {
      @Override
      public String execute(Jedis connection) {
        return connection.set(key, value, nxxx, expx, time);
      }
    }.run(key);
  }

        还有一种情况,因为生产环境上Redis一般都是采用的集群,当master节点挂了以后会自动切换到slave。当其中一个线程拿到锁后,此时Redis 的master节点宕机了,因为Redis是通过异步复制将数据同步到从节点的,锁信息完全有可能没有同步成功到从节点,其它线程就能通过setnx获取到锁,从而引发多个线程同时获取锁的问题。那该怎么解决呢?如果业务上可以接受,我觉得没必要考虑这种情况,因为这种情况出现的几率非常少,反之就必须想一个解决方案。
如果Redis是单主单从的话,这个问题基于Redis很难解决,如果是多主多从,可以考虑对所有的master都加锁,即使其中一个master获取锁失败,也不会影响其它两个节点,当总数超过一半时也让其获取到锁。因为涉及到跨多个节点,需要小心的控制超时时间和锁的释放问题。

最后,在方法结束时和抛出异常的时候需要手动释放锁,最好是在finally执行。

基于Zookeeper实现分布式锁

一般用Zookeeper实现分布式锁有两种方案:

  • 基于Zookeeper不能重复创建同一个节点
    利用名称唯一性,加锁操作时,只需要所有客户端一起创建/Lock/test节点,只有一个创建成功,成功者获得锁。解锁时,只需删除/Lock/test节点,其余客户端再次进入竞争创建节点,直到所有客户端都获得锁.
    这种方案的正确性和可靠性是ZooKeeper机制保证的,实现简单。缺点是会产生“惊群”效应,假如许多客户端在等待一把锁,当锁释放时候所有客户端都被唤醒,仅仅有一个客户端得到锁。
  • 基于临时有序节点
    对于加锁操作,可以让所有客户端都去/Lock目录下创建临时顺序节点,如果创建的客户端发现自身创建节点序列号是/Lock/目录下最小的节点,则获得锁。否则,监视比自己创建节点的序列号小的节点(比自己创建的节点小的最大节点).进入等待。对于解锁操作,只需要将自身创建的节点删除即可,然后唤醒自己的后一个节点。
    特点:利用临时顺序节点来实现分布式锁机制其实就是一种按照创建顺序排队的实现。这种方案效率高,避免了“惊群”效应,多个客户端共同等待锁,当锁释放时只有一个客户端会被唤醒。

由于Curator客户端已经提供了分布式锁的实现,可以直接使用如下代码:

InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) ) {
    try 
    {
        
    } finally {
        lock.release();
    }
}
基于数据库实现分布式锁

利用DB来实现分布式锁,有两种方案。两种方案各有好坏,但是总体效果都不是很好。但是实现还是比较简单的。

  1. 利用主键唯一约束:
    我们知道数据库是有唯一主键规则的,主键不能重复,对于重复的主键会抛出主键冲突异常。
    其实这和分布式锁实现方案基本是一致的,首先我们利用主键唯一规则,在争抢锁的时候向DB中写一条记录,这条记录主要包含锁的id、当前占用锁的线程名、重入的次数和创建时间等,如果插入成功表示当前线程获取到了锁,如果插入失败那么证明锁被其他人占用,等待一会儿继续争抢,直到争抢到或者超时为止
  2. 利用Mysql行锁的特性:
    利用for update加显式的行锁,这样就能利用这个行级的排他锁来实现分布式锁了,同时unlock的时候只要释放commit这个事务,就能达到释放锁的目的。
总结

Redis做分布式锁实现起来比较简单,如果不考虑master宕机引发的并发获取锁的问题,通过简单的setnx就可以实现,性能也很好。
如果项目中已经使用了ZK也可以考虑使用使用zk来做分布式锁,使用curator封装好的分布式锁即可,没必要自己实现。但是zk的主要优势还是做分布式协调,如果针对大压力场景下可能会引发性能问题,最好将其它业务进行隔离。
数据库做分布式锁适合压力比较小的情况,因为频繁的for update可能造成数据库连接的的耗尽,从而引发单点问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341