目录
一、分布式锁简介
分布式锁是一种在分布式系统中实现锁机制的技术,用于控制多节点对共享资源的访问,确保在并发环境下能够正确地实现互斥访问。在传统的单机环境下,我们可以使用线程锁来解决并发控制问题,但在分布式环境下,由于多个节点间物理分离,无法直接使用线程锁,因此需要设计一套能够在分布式系统中工作的锁服务。
分布式锁需要一个单独的分布式系统来实现,可以实现分布式锁的主要方式有:Redis、Zookeeper、基于数据库实现等。本文主要介绍基于 Redis 实现的分布式锁。
分布式锁应该具有以下特性:
- 互斥性:确保同一时刻只有一个客户端持有锁
- 避免死锁:锁在客户端异常或网络中断后能自动释放
- 容错性:在部分节点失效的情况下,锁依然可以正常工作
- 可重入性:支持一个客户端再持有锁的情况下能再次获取锁
- 公平性:按申请顺序依次分配锁资源
二、Redis 实现的分布式锁
对于在单机上运行的多线程程序来说,锁本身其实就是一个变量,比如用变量 0 表示没有线程获取锁,此时可以获取锁;变量值为 1 时,表示已经有线程持有了锁。
和单机上多线程加锁的方式类似,Redis 分布式锁同样可以用一个变量来实现。但是,和线程在单机上操作锁不同的是,在分布式场景下,锁变量需要由一个共享存储系统来维护,只有这样,多个客户端才可以通过访问共享存储系统来访问锁变量。相应的,加锁和释放锁的操作就变成了读取、判断和设置共享存储系统中的锁变量值。
这样一来,实现分布式的要求如下:
- 要求一:分布式锁的加锁和释放锁过程涉及多个步骤。读取、判断、设置这三个步骤要保证原子操作,不能被打断。
- 要求二:Redis 中保存了锁变量,如果 Redis 挂了,那么客户端无法操作分布式锁,这时要保证其可靠性。
2.1 单机 Redis 加锁流程
作为分布式锁实现过程中的共享存储系统,Redis 可以使用键值对来保存锁变量,再接收和处理不同客户端发送的加锁和释放锁的操作请求。
客户端 A 和 C 同时请求加锁。因为 Redis 使用单线程处理请求,所以,即使客户端 A 和 C 同时把加锁请求发给了 Redis,Redis 也会串行处理它们的请求。假设 Redis 先处理客户端 A 的请求,读取 lock_key 的值,发现 lock_key 为 0,所以,Redis 就把 lock_key 的 value 置为 1,表示已经加锁了。紧接着,Redis 处理客户端 C 的请求,此时,Redis 会发现 lock_key 的值已经为 1 了,所以就返回加锁失败的信息。
加锁的流程包含三个步骤,分别是:读取、判断、设置。Redis 中要想保证操作的原子性,有两种通用的方法,分别是使用 Redis 的单命令操作和使用 Lua 脚本。那么,在分布式加锁场景下,该怎么应用这两个方法呢?
先来看下,Redis 可以用哪些单命令操作实现加锁操作。</