老板问我分布式锁,结果悲剧了......

公司交给了萌新小猿一个光荣而艰巨的项目,该项目需要使用分布式锁,这可难道了小猿。

老板问我分布式锁,结果悲剧了......

图片来自 Pexels

只是听说过分布式锁很牛掰,其他就一概不知了,唉,不懂就问呗,遂向老板请教。

老板:我们每天不都在经历分布式锁吗,我来给你回忆回忆。

小猿:好勒,瓜子板凳已备好。

本文结构:

  • 为什么要使用分布式锁
  • 分布式锁有哪些特点
  • 分布式锁流行算法及其优缺点
  • 总结

为什么要使用分布式锁

这个问题应该拆分成以下 2 个问题回答。

①为什么使用锁

保证在同一时刻共享资源只能被一个客户端访问;根据锁用途分为以下两种:

  • 共享资源只允许一个客户端操作
  • 共享资源允许多个客户端操作

仅允许一个客户端访问:共享资源的操作不具备幂等性。常见于数据的修改、删除操作。

老板问我分布式锁,结果悲剧了......

在上面的例子中:

老板问我分布式锁,结果悲剧了......

允许多个客户端操作:主要应用场景是共享资源的操作具有幂等性;如数据的查询。

既然都具有幂等性了,为什么还需要分布式锁呢,通常是为了效率或性能,避免重复操作(尤其是消耗资源的操作)。

例如我们常见的缓存方案:

老板问我分布式锁,结果悲剧了......

在上面的例子中:

老板问我分布式锁,结果悲剧了......

由于此处的资源是幂等的,通常会将这类资源做缓存,这就是常见的锁+缓存架构。

常适用于获取较为消耗资源(时间、内存、CPU 等)的幂等资源,如:

  • 查询用户信息
  • 查询历史订单

当然,如果资源仅在一段时间范围内具有幂等性,这时候,架构就应该升级了:

锁+缓存+缓存失效/失效重新获取/缓存定时更新。

②锁为什么需要分布式的?

还是以上面的缓存方案为例,此处略作变化:

老板问我分布式锁,结果悲剧了......

在上面的例子中:

老板问我分布式锁,结果悲剧了......

分布式锁有哪些特点?

①互斥性

在任意时刻,仅允许有一个客户端获得锁。

PS:如果多个客户端都能同时获得锁,那锁就没意义了,共享资源的安全性也就无法保证了。

老板:当我在会议室接待客户 A 时,其他客户只有等待,你需要等到我空闲了才能把其他人带到我办公室。

小猿:明白。

接待客户(非幂等共享资源);等到老板空闲(获取锁)。

②可重入性

客户端 A 获得了锁,只要锁没有过期,客户端 A 可以继续获得该锁。锁在我这里,我还要继续使用,其他人不准抢。

这种特性可以很好的支持【锁续约】功能。例如:客户端 A 获取锁,锁释放时间为 10S,即将到达 10S 时,客户端 A 未完成任务,需要再申请 5S。若锁没有可重入性,客户端 A 将无法续约,导致锁可能被其他客户端抢走。

小猿:受教了,老板 3 分钟后你还有一场面试。

老板:小猿啊,难得你这么好学,我很欣慰,我们的交流时间延10分钟吧,其他会议延后。

③高性能

获取锁的效率应该足够高;总不能让业务阻塞在获取锁上面吧?

小猿:好的,我已在钉钉申请将会议延长 10 分钟了。

老板:嗯,我已经接受会议邀请了;

小猿:老板你真高效。

④高可用

分布式、微服务环境下,必须保证服务的高可用,否则轻则影响其他业务模块,重则引发服务雪崩。

老板:我手机 24 小时开机,有会议时联系不上我也可以联系我秘书。

⑤支持阻塞和非阻塞式锁

获取锁失败,是直接返回失败,还是一直阻塞知道获取成功?不同的业务场景有不同的答案。

例如:

老板问我分布式锁,结果悲剧了......

⑥解锁权限

客户端仅能释放(解锁)自己加的锁。常见的解决方案是,给锁加随机数(或 ThreadID)。

老板:小猿啊,给你讲了这么多,都明白了吗?

笼子里的鹦鹉:明白啦,明白啦。

老板:闭嘴,我问的是小猿,只有小猿自己有资格回答。

⑦避免死锁

加锁方异常终止无法主动释放锁;常规做法是 加锁时设置超时时间,如果未主动释放锁,则利用 Redis 的自动过期被动释放锁。

秘书破门而入:老板,你们 10 分钟的会议已经到点了,隔壁的李总已经等不及了。

老板:一不留神就忘记时间了,我得去见李总了。

小猿:老板,我们还没聊完呢...

⑧异常处理

常见的异常情况有 Redis 宕机、时钟跳跃、网络故障等。

小猿:不管出现哪种情况,我获取锁都会失败啊,这可怎么办呢?

PS:这就复杂了,需要根据具体的业务场景分析。对于必须同步处理的业务,则必须失败告警,对于允许延迟处理的业务可以考虑记录失败信息待其他系统处理。

分布式锁流行算法

基本方案 SETNX

基于 Redis 的 SETNX 指令完成锁的获取。

①获取锁 SET lock:resource_name random_value NX PX 30000

lock:resource_name:资源名字,加锁对象的唯一标记。

random_value:通常存储加锁方的唯一标记,如“UUID+ThreadID”。

NX:Key 不存在才设置,即锁未被其他人加锁才能加锁。

PX:锁超时时间。

当然,此种加锁方式是不支持“锁重入性”的。

②释放锁(LUA 脚本)

checkValueThenDelete:检查解锁方是否是加锁方,是则允许解锁,否则不允许解锁。

伪代码是:

public class RedisTool {     // 释放锁成功标记     private static final Long RELEASE_LOCK_SUCCESS = 1L;      /**      * 释放分布式锁      *      * @param jedis     Redis客户端      * @param lockKey   锁标记      * @param lockValue 加锁方标记      * @return 是否释放成功      */     public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String lockValue) {         String script = "" +                 "if redis.call('get', KEYS[1]) == ARGV[1] then" +                 "    return redis.call('del', KEYS[1]) " +                 "else" +                 "    return 0 " +                 "end";         // Collections.singletonList():用于只有一个元素的场景,减少内存分配         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));         if (RELEASE_LOCK_SUCCESS.equals(result)) {             return true;         }         return false;     } } 

Redlock 算法

此算法由 Redis 作者 antirez 提出,作为一种分布式场景下的锁实现方案。

Redlock 算法原理:【核心】大多数节点获取锁成功且锁依旧有效。

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

Step 2:按序想 N 个 Redis 节点获取锁。设置随机字符串 random_value;设置锁过期时间:

  • Note 1:获取锁需设置超时时间(防止某个节点不可用),且 timeout 应远小于锁有效时间(几十毫秒级)。
  • Note 2:某节点获取锁失败后,立即向下一个节点获取锁(任何类型失败,包含该节点上的锁已被其他客户端持有)。

Step 3:计算获取锁的总耗时 totalTime。

Step 4:获取锁成功。

获取锁成功:客户端从大多数节点(>=N/2+1)成功获取锁,且 totalTime 不超过锁的有效时间。

重新计算锁有效时间:最初锁有效时间减 3.1 计算的获取锁消耗的时间。

Step 5:获取锁失败。

获取失败后应立即向【所有】客户端发起释放锁(Lua 脚本)。

Step 6:释放锁。

业务完成后应立即向【所有】客户端发起释放锁(Lua 脚本)。

老板问我分布式锁,结果悲剧了......

Redlock 算法优点:

  • 可用性高,大多数节点正常即可。
  • 单 Redis 节点的分布式锁在 failover 时锁失效问题不复存在。

Redlock 算法问题点:

  • Redis 节点崩溃将影响锁安全性:节点崩溃前锁未持久化,节点重启后锁将丢失;Redis 默认 AOF 持久化是每秒刷盘(fsync)一次,最坏情况将丢失 1 秒的数据。
  • 需避免始终跳跃:管理员手动修改时钟;使用[不会跳跃调整系统时钟]的 ntpd(时钟同步)程序,对时钟修改通过多次微调实现。
  • 客户端阻塞导致锁过期,导致共享资源不安全。
  • 如果获取锁消耗时间较长,导致效时间很短,是否应该立即释放锁?多段才算短?

带 fencing token 的实现

分布式系统专家 Martin Kleppmann 讨论提出 RedLock 存在安全性问题。

神仙之战:Martin Kleppmann 认为 Redis 作者 antirez 提出的 RedLock 算法有安全性问题,双方在网络上多轮探讨交锋。

Martin 指出 RedLock 算法的核心问题点如下:

  • 锁过期或者网络延迟将导致锁冲突:客户端 A 进程 pause→锁过期→客户端 B 持有锁→客户端 A 恢复并向共享资源发起写请求;网络延迟也会产生类似效果。
  • RedLock 安全性对系统时钟有强依赖。

fencing token 算法原理:

  • fencing token 是一个单调递增的数字,当客户端成功获取锁时随同锁一起返回给客户端。
  • 客户端访问共享资源时带上 token。
  • 共享资源服务检查 token,拒绝延迟到来的请求。

fencing token 算法问题点:

  • 需要改造共享资源服务。
  • 如果资源服务也是分布式,如何保证 token 在多个资源服务节点递增。
  • 2 个 fencing token 到达资源服务的顺序颠倒,服务检查将异常。
  • 【antirez】既然存在 fencing 机制保持资源互斥访问,为什么还需要分布式锁且要求強安全性呢。

其他分布式锁

数据库排它锁:

  • 获取锁(select for update ,悲观锁)。
  • 处理业务逻辑。
  • 释放锁(connection.commit())。

注意:InnoDB 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。So 必须给 lock_name 加索引。

ZooKeeper 分布式锁:

  • 客户端创建 znode 节点,创建成功则获取锁成功。
  • 持有锁的客户端访问共享资源完成后删除 znode。
  • znode 创建成 ephemeral(znode 特性),保证创建 znode 的客户端崩溃后,znode 会被自动删除。
  • 【问题】Zookeeper 基于客户端与 Zookeeper 某台服务器维护 Session,Session 依赖定期心跳(heartbeat)维持。

Zookeeper 长时间收不到客户端心跳,就任务 Session 过期,这个 Session 所创建的所有 ephemeral 类型的 znode 节点都将被删除。

Google 的 Chubby 分布式锁:

  • sequencer 机制(类似 fencing token)缓解延迟导致的问题。
  • 锁持有者可随时请求一个 sequencer。
  • 客户端操作资源时将 sequencer 传给资源服务器。
  • 资源服务器检查 sequencer 有效性:①调用 Chubby 的 API(CheckSequencer)检查。②对比检查客户端、资源服务器当前观察到的 sequencer(类似 fencing token)。③lock-delay:允许客户端为持有锁指定一个 lock-delay 延迟时间,Chubby 发现客户端失去联系时,在 lock-delay 时间内组织其他客户端获取锁;

总结

我们该使用怎样的分布式锁算法?

  • 技术都是为业务服务的,避免选择“高大上”的炫技;
  • 依托业务场景,尽可能选择最简单的做法;
  • 最简单的分布式锁导致偶发性异常如何处理呢?建议增加额外的机制甚至人工介入保证业务准确性,通常这部分成本低于复杂的分布式锁的开发、运维成本。

分布式锁的另类玩法,“分而治之”经久不衰:

  • 如果共享资源本身可以拆分,那就分开处理吧。
  • 比如电商系统防止超卖,假设有 10000 个口罩将被秒杀,常规做法是一个锁控制所有资源。另类玩法就是将 10000 个口罩交由 20 个锁控制,整体性能瞬间提升几十倍。

PS:此处超卖仅是举例,真实场景下的秒杀超卖有更加复杂的场景,慎重。

来源:https://www.tuicool.com/articles/v6bYfey

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

推荐阅读更多精彩内容

  • 最近看了极客时间左耳听风的专栏,对于分布式系统的设计有了更深的认识,准备结合陈皓的总结加上自己看过的资料对于分布式...
    仰泳的双鱼阅读 3,680评论 0 23
  • 引言 目前很多系统都是使用redis作为分布式锁,如果redis是单节点部署,基本上不会出现什么问题。但如果red...
    落落的博客阅读 467评论 0 1
  • 在单实例JVM中,常见的处理并发问题的方法有很多,比如synchronized关键字进行访问控制、volatile...
    朱小厮阅读 790评论 2 8
  • 各位肌友们我是泡芙,今天看到了一个话题想分享给大家。顺便想知道你们的想法。 很多人开始决定健身时斗志满满,毫不犹豫...
    JOINFIT阅读 3,617评论 1 19
  • 原创两首 文:飘逸 七绝.秋浓枫好时 (水平韵) 正是秋浓枫好时, 入心经目化为诗。 阶前落叶应知意, 韵写相思犹...
    飘逸1阅读 817评论 2 12