分布式锁常用实现

业务背景: 在分布式系统中, 当收到一次退款请求,进行退款操作的期间, 可能会接收到重复的退款请求,造成订单的重复退款,从而造成资产亏损;  所以我们一般都会在对一笔订单进行退款操作的同时,进行"锁订单"的操作,当收到请求的时候,先判断该订单是否被锁,然后进行操作; 下面介绍几种常用的分布式锁的实现方式以及这些方式可能存在的问题;

1. 基于传统数据库实现

    使用传统的数据库实现分布式锁,只需要新建一个表, 设置字段: "主键唯一标识", "订单编号", "创建时间"三个字段,每次进行退款操作的时候, 插入一条数据, 操作完毕或者退款失败(包括各种异常失败)后,删除该条记录; 

    其实这样实现会出现很多弊端,比如: 

    (1) 删除该记录是出现异常,造成订单变成永久锁怎么办?

     回答: 我们可以做一个定时的Task,根据锁的创建时间字段,定时的删除过期记录,释放掉锁; 

    (2) 数据库发生单点故障, 数据库挂掉, 此时退款业务就不能进行下去了? 

    回答: 我们可以新建多个数据库实例, 获取锁时实现写入多个数据库表,成功数达一半以上时候就算是获取订单锁成功;

    上面解释了使用传统数据库实现分布式锁出现问题怎么解决, 或许使用传统型数据库实现分布式锁是我们最好理解的一种实现方式,但是伴随着各种不稳定的因素,导致实现起来会很复杂,要考虑的特殊情况会很多,造成这个方案变得越来越复杂; 比如: 如何保证多个数据库实例的稳定? 如果保证Task执行时间和锁的过期时间保持平衡?....

2.基于内存数据库实现

    首先,基于缓存实现分布式锁, 性能上相对传统数据库会有所提升; 并且缺少了对表操作,具体到开发效率上也会有很大的提升;  我们拿常用市面上比较成熟的内存数据库Redis为例:

(1) 把订单号作为Key, 当前线程操作的唯一标识作为value, 使用SETNX命令, 返回值为0 , 则获取锁失败; 返回值为1, 则获取锁成功

SETNX    [订单号]    [当前时间戳]

说明: SETNX命令在赋值时判断该KEY是否存在,若存在:返回0; 不存在: 赋值并返回1

(2) 使用EXPIRE 对key设置过期时间,防止出现死锁

EXPIRE   [订单号] [过期时间,单位:s]

(3) 订单操作完成, 释放锁之前: 拿之前设置的时间戳去执行" GET  [订单号]" 获取值;

       作比较: 若不相等: 说明锁已经被释放; 若相等: 执行下面命令释放锁

DEL  [订单号]

存在的问题?

(1) 单点故障:

    如果只有一台Redis, 挂掉之后, 相关分布式锁服务全部挂掉; 这时有人考虑使用“主从”尝试解决单点故障的问题, 但是因为"主从"模式在数据同步时,使用的是异步通信,不能保证数据的强一致, 可能会出现多个客户端同时获取到一个订单锁的情况,所以这个“主从”方案被PASS

针对Redis单点故障最优解决方案

    Redlock是Redis的作者antirez给出的集群模式的Redis分布式锁,它基于N个(通常是5个)完全独立的Redis节点,用来解决Redis的单点故障问题

具体实现:

(1) 获取当前时间(毫秒数)。

(2) 按顺序依次向N个Redis节点执行获取锁的操作。这个获取操作跟前面基于单Redis节点的获取锁的过程相同,包含当前时间戳,设置KEY的过期时间。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个获取锁的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有。

(3) 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。

(4) 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。

(5) 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起释放锁的操作。

Redlock的不足:

同样,RedLock分布式锁方案的实现也存在不足;

比如我们介绍下面一个场景:

(1)客户端A请求锁,此时锁住了Redis节点1 2 3 ,4和5两台节点由于各种不确定的原因没有被锁住,但是此时已经符合(>= N/2+1)的条件,所以获取锁成功;

(2)此时节点3发生服务崩溃,“加锁”数据没有被持久化,丢失了;节点3重启后,导致客户端B锁住了节点3 4 5, 此时客户端A和客户端B同时获得了锁 ;

    上述的场景中,客户端A和B同时获取到锁。

    针对,RedLock中存在的不足,《掘金》社区作者Wang_Coder提出了优化的方案:延迟节点3的恢复时间,时间长度应大于等于一个锁的过期时间。的确,这个能在大大的降低不同客户端同时获取锁的概率。

    前辈对RedLock的使用,给我们总结出来的优化方案已经相对比较完善,但是看似完美的优化方案,实际上还有很多漏洞,当数据量达到一定程序,这些漏洞会被一一的暴露出来。



本文参考:《掘金》社区作者Wang_Coder文章 和 Redis官方文档

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