分布式锁的应用场景到底有哪些?

212 阅读6分钟

本文首发于公众号:托尼学长,立个写 1024 篇原创技术面试文章的flag,欢迎过来视察监督~

在技术面试中,经常会遇到以下几个场景。

场景一

面试官:“如果出现了系统流量激增的问题,你应该如何解决呢?”

候选人:“这种情况可以通过分布式锁来控制流量,避免出现系统被打挂的情况。”

场景二

面试官:“你说说电商秒杀场景下,如何解决订单和库存的数据一致性问题?”

候选人:“这种情况可以通过分布式锁来解决,保证同一时段只有一个线程去下单并扣减库存,问题也就解决了。”

场景三

面试官:“Kafka消息的有序消费问题,你有什么比较好的方案吗?”

候选人:“这种情况可以通过分布式锁来解决,加锁之后消息就一条条进行处理了,具备了有序性。”

如果我是面试官,听到候选人会把分布式锁用在限流、数据一致性和有序消费场景中,内心一定是崩溃的。

其实,无论是在面试场景中,还是在真实的项目场景中,都存在分布式锁滥用的情况。

本文我们就来对分布式锁进行深度细致的剖析拆解。

使用原因

分布式锁的目的,是确保在可能尝试执行同一项工作的几个节点的线程中,只有一个真正执行了某项工作。

该项工作可能是将一些数据写入存储系统、执行复杂计算、调用外部系统的API,等等。

分布式锁有两个主要的使用原因:效率和正确性。

效率,使用分布式锁可以避免不必要地重复相同的工作,这势必会对服务器的执行效率有所影响,比如:执行两次复杂计算或是给用户发送两次短信。

正确性, 使用分布式锁可以避免并发场景下的数据计算错误、文件损坏问题,对于医疗就诊系统来说,这意味着患者用药剂量错误。

场景剖析

如上图所示,这种场景下使用分布式锁是否合理?我认为95%的场景下是不合理的。

原因在于,在MySQL数据库层面本身就具备锁机制,根本没有必要在程序中自己实现分布式锁,这样做只会徒增系统复杂性。

不过,还是有5%的场景是合理的,比如在高并发下,MySQL数据库的主库已经成为单点瓶颈的场景。

此时出于为MySQL数据库减负的目的,可以将其挪到应用程序中通过Redission客户端进行实现。

当然,这种应用场景并不属于分布式锁主要的使用原因,跟效率和正确性没有关系。

适用场景

说到这里,同学们肯定会有所疑问,说了半天分布式锁的不适用场景,那它的适用性场景有哪些呢?下面我们就来盘点几个。

1、定时任务

想象这样一种场景,余额宝每天都会给用户进行计息操作,若定时任务只部署在一台服务器上,那这台服务器宕机了就会导致计息操作停止运行。

此时,就需要集群中的N台服务器都有定时任务代码,到执行时间后一起运行去争抢分布式锁,先获得锁的定时任务来执行计息操作。

这N台服务器中,就算有一两台服务器出现宕机的情况,只要不是全部宕机,那计息操作就会正常执行。

2、周期性幂等

幂等(Idempotent)‌是一个数学与计算机科学概念,指操作或函数在相同条件下重复执行时,其效果与执行一次相同,不会因多次执行而产生额外影响。

分布式锁由于其具备加锁、解锁的特质,只能实现一段时间内的幂等性(周期性幂等),防止用户短时间内重复提交订单场景,就属于这个范畴。

我们认为以用户正常操作情况下,对一件商品重复下单一定是超过三秒钟的。

反之,三秒钟内重复提交订单的原因无外乎两种:

  • 一种是由于用户在短时间内多次点击下单按钮,或浏览器刷新按钮导致。

  • 另一种则是由于Nginx或类似于SpringCloud Gateway的网关层,进行超时重试造成的。

那么,我们就可以用 ”用户ID + 分隔符 + 商品ID“ 作为分布式锁的Key,并设置三秒过期时间,使其到期自动解锁。

若分布式锁加锁成功,则可以继续走下单的业务逻辑,执行不成功,直接返回给前端”下单失败“就可以了。

3、复杂资源访问

如上文所述,如果只在MySQL层面修改资源的话,数据库本身就具备锁机制,根本没有必要自己在程序中实现分布式锁,这样做只会徒增系统复杂性。

但遇到下图中的场景就不好说了,毕竟分布式锁的下游不仅仅是数据库,还有下游系统和文件系统。

若在次场景中控制资源访问,保证效率和正确性的话,就必须要用到分布式锁机制了。

在如上三种场景中,分布式锁就具备了效率和正确性两个使用原因。

分布式锁 VS 分布式事务

我们都知道,数据库事务具备ACID(原子性、一致性、隔离性、持久性)特性的,其中的隔离性就是通过锁机制或MVCC(多版本并发控制)实现的。

这种说法也可以沿用到分布式锁和分布式事务上,也就是说,如果我们的业务场景需要同时满足于原子性、一致性、隔离性、持久性的,就用分布式事务。

反之,我们的业务场景中只需要满足于隔离性,那使用分布式锁就可以了。

我们再来回顾一下文章开头的场景二。

面试官:“你说说电商秒杀场景下,如何解决订单和库存的数据一致性问题?”

候选人:“这种情况可以通过分布式锁来解决,保证同一时段只有一个线程去下单并扣减库存,问题也就解决了。”

候选人的这种说法就是错误的,该场景下不仅仅需要锁的隔离性。

该场景也需要Redo Log和Undo Log实现原子性,以及需要具备Redo Log的WAL 机制和Double Write机制实现持久性,进而实现事务的一致性。

也就是说,该场景还需要保证系统扣减库存后,一定可以下单成功,否则就全部回滚,并且具备持久化功能,这是仅仅具备隔离性的分布式锁做不到的。

再说一句,事务的 ACID 之间,并不是像《金字塔原理》一书中提到的 MECE 原则一样 —— 相互独立,完全穷尽。

事务的原子性、持久性和隔离性都是为了实现事务的一致性,让业务状态可以正常流转。