分布式锁简单介绍

工作中经常会遇到争抢共享资源的场景,比如用户抢购秒杀商品,如果不对商品库存进行保护,可能会造成超卖的情况。超卖现象在售卖火车票的场景下更加明显,两个人购买到同一天同一辆列车,相同座位的情况是不允许出现的。交易系统中的退款同样如此,由于网络延迟和重复提交极端时间差的情况下,可能会造成同一个用户重复的退款请求。以上无论是超卖,还是重复退款,都是没有对需要保护的资源或业务进行完善的保护而造成的,从设计方面一定要避免这种情况的发生。

本文以退款交易场景入手,引入分布式锁,尝试分析分布式锁需要考虑关注点,包括以下内容:

  • 锁的引入和局限性
  • 分布式锁的三要素
  • 分布式锁进阶
    • 获取锁操作的原子性
    • 锁与保护共享资源的数据一致性
    • 分布式锁的性能
    • 可重入
    • 公平锁和非公平锁
  • 分布式锁的容错,使用分布式锁时注意考虑哪些问题
锁的引入和局限性

锁是一种控制共享资源争抢的机制,采用互斥方式防止多线程(或多进程)间造成的冲突。锁是一种获取保护资源的凭证,就像公园门票,只有持有门票才有资格入园;锁是使得对同一类共享资源的访问串行化。没有获得锁只能排队等待,直到其他线程释放掉锁。这里需要对“同一类共享资源”正确理解,比如订单系统中的同一种商品库存,退款系统中同一个用户。

在多线程中,Java 已经提供了很好原生锁(包括synchronized,lock),前面的其他文章中也已经讲到了内置锁和显示锁的理解和使用,在此不再赘述。但是(是不是已经料到了我要说但是了呢?),在分布式系统中,因为要跨进程或者跨服务器 ,这种场景下JDK原生锁已经无法满足我们的需求,需要一种能够分布式系统中保护共享资源的方式,分布式锁在这种情况下产生了。

很多事情往往都是如此,为了解决一个问题,引入了新方案,而新方案却会带来其他的问题,又需要用更多的时间去解决新方案带来的问题。没有一个完美的方案,因此对方案的取舍,就是具体场景中应该重点关注哪些问题,忽略哪些问题的选择。

分布式锁的三要素

分布式锁是一个在分布式环境中很重要的原语,它表明不同进程间采用互斥的方式操作共享资源。如何才称得上分布式锁呢?分布式锁需要满足三个基本的条件:

外部存储
顾名思义,分布式锁是在分布式部署环境中给多个主机提供锁服务。Java具有天生的多线程优势,在同一个进程的线程中可以通过互斥锁住共享资源来保证多线程之间干扰,锁的载体是堆中共享变量,使用JDK原生锁synchronized和lock可以很方便的解决,但是将问题扩展到分布式环境中,就超出了JDK原生锁作用范畴。需要另外的存储载体,可以是共享内存或者磁盘文件。考虑到分布式锁的高可用性,避免单点问题,因此共享内存中数据是需要持久化的,这点内容会在下文中的分布式锁的高可用中涉及到。

全局唯一标识
与JDK原生锁类似,分布式锁同样需要标记为全局唯一。在多线程环境中,锁可以使一个对象引用,也可以是基本类型变量,都有唯一的标识来区分锁保护的不同资源。仍然以上面的退款为例,为了保护用户的账户资金,不允许同一个用户并发退款。因此同一个用户退款操作采用互斥锁保护起来,不同用户之间不需要互斥操作。具体方法一种可以通过锁用户账户的方式,另一种对用户userId设置不同的状态标识,这两种方式都是采用对堆中变量的原子操作保证互斥的。
分布式环境中上述第一种方法就不适用了,举个例子,小明的账户可以同时在A、B两个不同实例中加锁。那么可以采用第二种方法,自定义一个标识,使其全局唯一即可,每次申请退款时,首先尝试获取该标识,如果该标识已经被其他占用,则需要等待,直到释放该标识(是不是与synchronized很相似)。对于交易而言,全局唯一的标识很简单:业务+userId即可唯一标识。

至少有两种状态
锁至少需要两种状态:加锁(lock)和解锁(unlock)。用状态区分当前尝试获取的锁是否已经被其他操作占用,被占用只有等待锁释放后才能尝试获取锁并加锁,保护共享资源。

分布式锁进阶

为解决共享资源在分布式环境下并发访问带来的问题,引入分布式锁采用互斥访问的方式将并发访问串行化。下文中以Redis为例,分析使用分布式锁时重点需要考虑的情况。

获取锁操作的原子性
从读取锁的状态,到设置锁状态为加锁(获取锁的过程),不是原子性的操作,如果不能保证这两步作为一个的原子操作,可能存在竞态条件,在极端的时间差的情况下,会有多个服务同时获取到同一个锁,从而获取操作工作资源的凭证,这是不允许的。幸运的是Redis提供了CAS原子性功能SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置。

锁与保护共享资源的数据一致性
获取锁与开始操作共享资源必须保证一致性,结束操作共享资源和释放锁必须保证一致性。共享资源操作结束后必须释放锁,退出临界区,否则会造成锁饥饿;开始操作共享资源,必须是在获取锁之后,否则锁就无法保护共享资源。

分布式锁的性能
分布式锁需要考虑网络传输时间,超时时间同样需要考虑网络时间消耗。

可重入
某个请求试图获得一个已经由它自己持有的锁,那么这个请求就会成功,这是重入。当重入时需要将计数器加一,释放锁时,计数器相应减一,一般分布式锁同样支持可重入,因此需要设计标记不同的请求。

公平锁和非公平锁
公平锁设定按照请求的顺序获取锁,不允许插队。公平是个好东西,不过大多数情况下非公平锁的性能要高于公平锁。

分布式锁的容错

正常情况下,加锁,执行保护资源,释放锁。如果没有异常,那这世界就太美好了。那么生产环境中,使用分布式锁时应该注意哪些容错的问题。

锁无法释放
以退款为例,退款服务宕机,分布式锁服务正常。此时锁保护的资源(或部分)已无法对外提供服务,无法通知锁自身运行情况,为避免锁服务一直无法释放,可以为锁设置超时时间,当锁执行时间超过了超时时间,锁会过期,从而保证锁与保护服务的最终一致性。当然锁设置超时时间又会引出另一个问题:比如锁的超时时间是500ms,而部分退款服务可能由于网络等原因执行时间为800ms(退款服务没有宕机,仅仅是执行时间相比平均执行时间较长而已),这种情况下,锁已经过期,而退款服务仍在执行,锁作为保护资源的功能失效了。有一个办法可以兼顾超时时间和锁失效的问题,退款服务保持心跳通知锁服务,锁服务收到心跳后延长锁的超时时间,不足在于即使退款服务已经宕机,锁服务仍然需要到达超时时间后才会解锁。Redisson分布式锁就是采用这种方式。分布式锁时效设置的必要性:确保在未来的一定时间内,无论获得锁的节点发生了什么问题,最终锁都能被释放掉。

性能
针对访问量大的共享资源,尝试自旋方式获取锁时的长时间等待,即容易造成CPU空转性能的消耗,又容易造成节点阻塞;而每隔一段时间尝试获取锁,便无法保证资源的高效利用。基于以上两种解决方案的弊端,可以采用尝试获取锁一定次数后,加入到等待队列中,当锁释放后,通知等待队列中的下一个等待节点获取锁。既可以避免CPU空转带来的性能消耗,又可以及时响应,保证系统的性能和稳定性,避免毛刺的出现。设计上可以参考Java并发包中AQS。

锁饥饿
一个线程在尝试获取锁的过程中一直无法获取锁,这种情况就是锁饥饿,比如体弱的狼很难在一群强壮的狼群中抢到食物,很多情况下锁饥饿是由于优先级较低造成的。发生锁饥饿时,无法获取锁便无法进行锁保护资源的操作。为避免锁饥饿情况的发生,设计时需要将锁设计成公平锁。

监控
监听锁的运行情况,掌握锁持有者的动态,若判断锁持有者处于不活动状态,要能够强制释放其持有的锁,引入第三方监控系统。

当然,分布式锁还有一些其他的问题:比如频繁获取锁释放锁带来的系统稳定和性能问题,如何保证锁的高可用,分布式锁的持久化,分布式锁单点问题,分布式锁网络传输性能等,还有分布式锁主节点宕机,从节点还没同步到锁,锁的唯一性被破坏,多个客户端可以获得同一个锁…

原文链接:http://geyifan.cn/2017/02/11/what-problems-when-using-distributed-locks/

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

推荐阅读更多精彩内容

  • 锁 分布式锁 distributed locks 资源有限,争抢难免,最简单粗暴的办法是谁的拳头大谁就可以抢到最好...
    曲水流觞TechRill阅读 11,474评论 9 43
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 我初中的时候还是一个不知世事的孩子,很崇拜那些学校里的校园人物,自己也加入他们。很崇拜一位学姐,她喜欢别人叫她田哥...
    LEEJONGSUK98阅读 215评论 0 1
  • 数组中可以包含任何数据类型的元素 var arr = [10,8,'hello',null,true,3.1415...
    夏臻Rock阅读 188评论 0 0
  • 周末和帝都的产品朋友在一起胡侃的时候,提到一个问题,如果这两三年行业泡沫爆发,一些没有商业模式的公司倒闭或转行,那...
    Nick777阅读 310评论 0 1