分布锁——redis实现

分布式锁的场景

首先在读文章之前,我们要考虑一个问题,为什么要用分布式锁,也就是什么场景下要用分布式锁?

假如我们有一个抢购业务,之前是单机的时候我们可以用程序锁,扩展到了多个服务节点的时候,那我无法再继续使用lock sync等程序的锁来控制并发中可能会造成的超卖。

这时候我们就该引入一个分布式锁来解决这个问题,当然上面的例子有更好的解决办法,这里仅仅提供一个分布式锁的场景引入。

设计一个分布式锁的要素

OK,我们知道什么情况下用分布式锁了之后,我们要考虑下,如果让我们设计一个分布式锁,要考虑哪些问题?

第一点,既然是锁,那么我要确保这个锁在整个集群中唯一性
第二点,我要确保我某个获取到锁的节点挂掉之后不会因为无法释放而产生死锁的问题
第三点,我要确保我的锁不会被其他的节点误操作而错误的解锁
第四点,我们要考虑我们的锁其他的竞争线程,如何在持有锁的节点释放之后快速的受到通知重新竞争锁
第五点,就是锁的性能效率
第六点,就是锁的可重入性(这里提一下,对于大多数程序和业务来讲是没必要实现这个功能的)

ok,上面就是我们做一个分布式锁应该注意的地方,当然,这里说的情况并不是很全面,但是基本上已经足够大多数的业务使用了,那么我们带着上面的这些注意的点,一起去看一下怎么实现一个分布式锁。

构建分布式锁

锁的唯一性问题

大家应该都知道redis里有个过期时间的概念,也就是expire这个api可以设置一个key的过期时间,那么利用这个功能我们可以设置一个最大值,避免死锁的问题。

那么说道这里,大家可能跟我之前一样,会考虑到一个问题?
这个过期时间设置为多少好呢,如果设置太小了,会造成业务没操作完,锁就提前被其他线程获取了,如果设置太大了,又可能比死锁没好多少。

redisson是怎么解决这个问题的?

redisson默认是设置一个key的过期时间为30秒,那么大家可能想,这也没区别啊!
如果看过redisson源码的应该注意到他用了netty,那么他用netty干嘛了?他用netty做了这样的一个事,他给每个上锁的操作都加了一个事件。

什么样的事件?
如果我一个上锁操作,上锁失败了,就订阅锁,直到收到通知,否则就暂时等待,这里他是利用java用的信号量来实现的,如果有兴趣的可以看一下他的具体代码。
那么如果上锁成功了呢?他会开启一个异步线程,等待通知,这个通知可以是这样的:如果我收到的通知是,我工作完了,要释放锁了。那么这时候他就把这个异步线程从工作者队列中干掉。
那么,如果我没有收到通知呢?这一步其实就是redisson的关键实现

redisson锁的代码

如果我没收到通知,我每隔离10s会调用一次这个事件,判断一下过期时间,然后给这个持有锁的线程的key,也就是当前锁,重新设置上为30秒的过期时间,也就说,即使我这台机器挂掉了,那我这个机器持有的锁最多会保留30s的“死锁”时间。

如果我有一堆远程调用,30s根本不够用呢?

没关系,每隔10s你的过期时间都会更新为30s。也就是一直到你释放锁。当然,如果你害怕你的业务会发送阻塞而造成了锁的一直持有的"假死锁"情况,那怎么办?
redisson提供了lockInterruptibly(long leaseTime, TimeUnit unit)的时间限制哈。也就是你在多少秒之内如果没完成任务也会自动释放这个锁。

锁的标志

我怎么要确保我的锁不会被其他的节点误操作而错误的解锁呢?

这个其实很好解决,一般对于一个锁来讲,都是需要一个onwer的标示,对于大多数的做法:都是使用uuid+ThreadId,然后操作线程保留这个onwer标示,在set的时候吧这个owner的标示放到value中,解锁的时候判断这个owner标示。

note:一般来讲这个owner标示还起着做重入的时候的作用.

解锁后的快速通知

这里其实是有两种做法:

  • 第一种,类似本地锁的不断重试(自旋)。
  • 第二种方式,也就是redlock的实现RedisSon的做法,pub/sub的方式

自旋如何实现

我原来做这一块的时候利用locksupport的park来做了短暂时间的暂停,再暂停之后不断的重试获取锁。

但是这样就会有这样的几个问题:

  1. 锁的通知被释放的时候我无法及时的收到通知,并且这个能获取锁的机会有可能就看运气了,也就是说谁暂停完之后重试的时间正好是我释放的时间,也就是无法实现公平锁(按申请锁的顺序来获取锁)
  2. redis毕竟是网络的,无论是网络抖动的影响还是自身这种不断发请求来讲,都是很大的开销,性能上烂到爆

但是上面的锁翩翩适用一种常见,业务操作比较简单短暂,不会出太多问题,耗时比较短,需要简单的锁模型

pub/sub如何实现

相信了解过redis的都知道它有个发布订阅的功能
RedisSon是这么实现快速通知的:

获取锁的线程会去redis中发布一个key,然后所有没有取到锁的就去订阅相应的channel,来接收锁释放的通知,获取锁的释放了之后就会去这里发布释放的通知。收到消息的就会继续重试获取锁的过程

性能问题

首先,大家都知道,一个分布式锁可以基于zk和redis来实现。
但是rediss做分布式锁的效率要比zk高上很多很多倍,因为zk是基于文件系统的实现,而redis是基于内存的操作实现。
而且zk做分布式锁的时候还会有可能因为网络抖动的问题发生锁被误释放的问题(这里我们暂时不讨论)

可冲入特性

我这里简单的说一下redisson是怎么实现的:

redisson是吧获取锁的行为变成了一次hashset的操作

redisson锁的代码

这里就是核心的实现:

上面lua代码中第一个if就是先尝试获取锁,如果获取成功就返回,如果不成功就判断要获取锁的线程,和持有锁的线程是否是同一个线程,如果是,那就在value上加个1,代表重入了一次,最后那个return的pttl其实是一个else逻辑,也就是说我既没有获取锁,也不是持有锁的那个线程,也就意味我获取锁失败,那我就返回一个过期时间的值

Redisson的流程简述

redisson的整个过程简介:利用lua在redis中的原子性,获取锁保证唯一性,在value中加上标示防止误解锁,不断的叠加expire来保证持有锁的时候不会被误拿到,利用redis的pub/sub来即使的通知锁的释放,利用Semaphore来实现没有获取锁的线程的等待。

要思考的问题

自旋锁 然后 自旋锁会导致饥饿 就开始使用阻塞,然后 阻塞会导致CPU等资源空置 就开始使用异步解耦(最常见的就是做完了,通知的方式),其实整个过程跟IO的几种模式很像 从BIO到NIO到AIO的整个过程,然后大家看到发布订阅这种通知的模式了。。
但如果发布订阅模式突然挂了,你的线程可能永远不会醒来了?这里我还没有完全的关注到这个点,日后有机会吧这个点看一下然后补充上来。

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

推荐阅读更多精彩内容