文章目录
在某些环境里,不同的进程需要以互斥的方式使用共享的资源,这时候就需要用到分布式锁。
Distributed locks are a very useful primitive in many environments where different processes must operate with shared resources in a mutually exclusive way.
目前有很多的库和博客都有介绍要如何通过Redis来实现一个DLM(Distributed Lock Manager 即分布式锁管理器),但是这些库都使用了不同的方法来实现,并且其中大部分方法都较为简单。其实我们只要在分布式锁的设计上采用略微复杂一点的方案,就可以达到更高的可靠性的保障。
There are a number of libraries and blog posts describing how to implement a DLM (Distributed Lock Manager) with Redis, but every library uses a different approach, and many use a simple approach with lower guarantees compared to what can be achieved with slightly more complex designs.
本页描述了一种更为规范的算法,来使用Redis实现分布式锁。我们提出了一种算法,叫Redlock,它实现了一个DLM,我们认为它比普通的单实例方法更安全。我们希望社区能帮忙一起进行分析、提供反馈,并基于此算法来构建更复杂的设计或实现。
This page describes a more canonical algorithm to implement distributed locks with Redis. We propose an algorithm, called Redlock, which implements a DLM which we believe to be safer than the vanilla single instance approach. We hope that the community will analyze it, provide feedback, and use it as a starting point for the implementations or more complex or alternative designs.
安全性和存活性保障
我们将从如下三个点进行建模设计,从我们的角度来看,这三个点是以有效的方式使用分布式锁所需的最低的保障:
- 安全:互斥,在任何指定的时候,只有一个客户端可以持有锁;
- 存活性A:无死锁,最终总是有机会获取到锁,即使持有锁的客户端崩溃或出现分区错误;
- 存活性B:容错,只要大多数Redis节点是正常的,那客户端就能获得和释放锁;
We are going to model our design with just three properties that, from our point of view, are the minimum guarantees needed to use distributed locks in an effective way.
- Safety property: Mutual exclusion. At any given moment, only one client can hold a lock.
- Liveness property A: Deadlock free. Eventually it is always possible to acquire a lock, even if the client that locked a resource crashes or gets partitioned.
- Liveness property B: Fault tolerance. As long as the majority of Redis nodes are up, clients are able to acquire and release locks.
为什么基于故障转移的实现是不够的
为了了解我们想要改进的地方,我们先来分析下大多数基于Redis的分布式锁的现状。
To understand what we want to improve, let’s analyze the current state of affairs with most Redis-based distributed lock libraries.
最简单的通过Redis锁定资源的方法,就是创建一个key,这个key通常有过期时间,所以它最终将会被释放(存活性A),当客户端需要释放资源时,它就会删除该key。
The simplest way to use Redis to lock a resource is to create a key in an instance. The key is usually created with a limited time to live, using the Redis expires feature, so that eventually it will get released (property 2 in our list). When the client needs to release the resource, it deletes the key.
从表面上看,这是可行的,但存在一个问题,就是我们架构中存在单点故障,如果Redis的master发生故障怎么办?好吧,我们尝试添加一个副本,当master不可用时,就使用该副本。但不幸的是,这也是行不通的,通过这种方式的实现,我们不能保障互斥性,因为Redis的复制是异步的。
Superficially this works well, but there is a problem: this is a single point of failure in our architecture. What happens if the Redis master goes down? Well, let’s add a replica! And use it if the master is unavailable. This is unfortunately not viable. By doing so we can’t implement our safety property of mutual exclusion, because Redis replication is asynchronous.
这种模式下存在竞态条件:
- 客户端A在master节点中获取到锁;
- master还未将key写入副本时就已经奔溃了;
- 副本被提升为master;
- 此时客户端B可以获取客户端A已经获取过的锁;
There is a race condition with this model:
- Client A acquires the lock in the master.
- The master crashes before the write to the key is transmitted to the replica.
- The replica gets promoted to master.
- Client B acquires the lock to the same resource A already holds a lock for. SAFETY VIOLATION!
有时候,在特定情况下(如故障期间),多个客户端同时持有锁是可接受的话,那你可以继续沿用基于复制的解决方案,否则,我们建议实施本文件中描述的解决方案。
Sometimes it is perfectly fine that, under special circumstances, for example during a failure, multiple clients can hold the lock at the same time. If this is the case, you can use your replication based solution. Otherwise we suggest to implement the solution described in this document.
在单个实例中正确实现
在尝试突破单实例方式存在的限制之前,我们先看一下如何在这种简单的场景下正确的实现分布式锁,因为在时常出现竞态条件的应用程序中,这实际上是一个可行的解决方案,并且在单个实例中正确的实现分布式锁是我们即将实现的分布式算法的基础。
Before trying to overcome the limitation of the single instance setup described above, let’s check how to do it correctly in this simple case, since this is actually a viable solution in applications where a race condition from time to time is acceptable, and because locking into a single instance is the foundation we’ll use for the distributed algorithm described here.
获取锁的方法如下:
To acquire the lock, the way to go is the following: