1.分布式锁产生的背景
开发人员很多时候会遇到在一个JVM实例内,多个线程要竞争使用具有排它性的共享资源,恰好JDK中提供了如synchronized 、 JUC包中的xxxLock 这些锁工具,使用它们可以方便我们实现对临界资源的互斥使用;使用锁是一种显式同步的方式,来控制线程间操作发生相对顺序的机制,除此之外其实还有另外一种隐式同步方式,即消息传递,但大家似乎对显式使用锁的方式更为熟悉一些。
现在的系统大多已演进为分布式的架构,如实例内多线程要互斥使用临界资源,分布式情况下多实例之间也存在需要排它的使用共享资源,这个问题就是分布式互斥问题。虽然大佬Lamport 在论文 《Time, clocks, and the ordering of events in a distributed system》中早就证明了使用状态机(如共识算法)就能够去中心化解决多进程互斥问题,但我们还是会更偏向使用分布式锁,原因大概有以下3点:
- 使用分布式锁服务更易于保持现有程序的结构
- 程序员更熟悉用加锁的方式来同步访问资源
- 共识算法可能会要求客户端运行在更多的服务器上以满足共识算法要求,而实际只满足客户端功能性的需求却不需要使用这么多服务器
概括来说,分布式锁更轻便、更易用、也更节省资源。
2.使用分布式锁的目的
使用分布式锁的目的主要有两种:
- 效率(Efficiency):通过锁来避免多次做重复的工作,计算重复的内容等等。这种场景下即便偶然出现多个用户同时持有锁,并同时与资源服务发生交互,也是可以忍受的。
- 正确性(Correctness):也就是“安全”,我们希望资源服务在锁的保护下能够做“正确”的事。更严谨的说,我们希望任一时刻,只有一个用户能够访问资源服务,而且即便锁在该用户与资源服务交互的中途过期,也不至于破坏资源服务的一致性。
3.分布式锁的功能特点
一个分布式锁应具备这样一些功能特点:
- 互斥性:在同一时刻,只有一个客户端能持有锁
- 安全性:避免死锁,如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制正确释放,并保证后续其它客户端也能加锁,整个处理流程继续正常执行。
- 可用性:也被称作容错性,分布式锁需要有高可用能力,避免单点故障,当提供锁的服务节点故障(宕机)时不影响服务运行,这里有两种模式:一种是分布式锁服务自身具备集群模式,遇到故障能自动切换恢复工作;另一种是客户端向多个独立的锁服务发起请求,当某个锁服务故障时仍然可以从其他锁服务读取到锁信息(Redlock)
- 可重入性:对同一个锁,加锁和解锁必须是同一个进程,即不能把其他进程持有的锁给释放了。
- 高效灵活:加锁、解锁的速度要快;支持阻塞和非阻塞;支持公平锁和非公平锁。
4.多视角下的分布式锁
通过不同视角,可以从多维度充分了解各种分布式锁的实现差异
视角1:
- 轮询类:基于数据库和基于 Redis 实现的分布式锁,这类实现需要客户端不停反复请求锁服务以查看是否能够获取到锁;
- 监听类:基于 ZooKeeper 或 etcd 实现的分布式锁,这类实现客户端只需监听(watch) 某个 key,当锁可用时锁服务会通知客户端,无需客户端不停请求锁服务。
视角2:
基于数据库实现的分布式锁:使用乐观锁、悲观锁或基于主键唯一约束来实现
基于分布式缓存实现分布式锁:Redis 和基于 Redis 的 RedLock(Redisson)
基于分布式一致性算法实现的分布式锁:ZooKeeper、 Etcd
视角3:
Martin Kleppmann 在个人博客中发了一篇文章 How to do distributed locking,其中涉及了大量对 Redlock 算法安全性的质疑,Salvatore Sanfilippo(Redis 的创始人,也是这里 Redlock 算法的作者)随后发表 Is Redlock safe? 回应这些质疑;这两位大佬的对决一定要围观。
本文转载自微信公众号「架构染色」,可以通过以下二维码关注。转载本文请联系【架构染色】公众号作者。