分布式锁是一种用于在分布式系统中协调多个进程或线程对共享资源的访问的机制。分布式系统中的多个节点之间需要协调访问共享资源,以避免竞争条件和数据不一致问题。分布式锁的目标是确保在任何给定时间只有一个节点可以访问共享资源,从而保证数据的一致性和正确性。

分布式锁的特性

互斥

互斥性很好理解,这也是最基本功能,就是在任意时刻,只能有一个客户端才能获取锁,不能同时有两个客户端获取到锁

避免死锁

为什么会出现死锁,因为获取锁的客户端因为某些原因(如 down 机等)而未能释放锁,其它客户端再也无法获取到该锁,从而导致整个流程无法继续进行

面对这种情况,当然有解决办法啦!

引入过期时间:通常情况下我们会设置一个 TTL(Time To Live,存活时间) 来避免死锁,但是这并不能完全避免。

  1. 比如 TTL 为 5 秒,进程 A 获得锁
  2. 问题是 5 秒内进程 A 并未释放锁,被系统自动释放,进程 B 获得锁
  3. 刚好第 6 秒时进程 A 执行完,又会释放锁,也就是进程 A 释放了进程 B 的锁

仅仅加个过期时间会设计到两个问题:锁过期和释放别人的锁问题

锁附加唯一性:针对释放别人锁这种问题,我们可以给每个客户端进程设置【唯一 ID】,这样我们就可以在应用层就进行检查唯一 ID。

自动续期:锁过期问题的出现,是我们对持有锁的时间不好进行预估,设置较短的话会有【提前过期】风险,但是过期时间设置过长,可能锁长时间得不到释放。

这种情况同样有处理方式,可以开启一个守护进程(watch dog),检测失效时间进行续租,比如 Java 技术栈可以用 Redisson 来处理。

可重入

一个线程获取了锁,但是在执行时,又再次尝试获取锁会发生什么情况?

是的,导致了重复获取锁,占用了锁资源,造成了死锁问题。

我们了解下什么是【可重入】:指的是同一个线程在持有锁的情况下,可以多次获取该锁而不会造成死锁,也就是一个线程可以在获取锁之后再次获取同一个锁,而不需要等待锁释放。

解决方式:比如实现 Redis 分布式锁的可重入,在实现时,需要借助 Redis 的 Lua 脚本语言,并使用引用计数器技术,保证同一线程可重入锁的正确性。

容错

容错性是为了当部分节点(redis 节点等)宕机时,客户端仍然能够获取锁和释放锁,一般来说会有以下两种处理方式:

一种像 etcd/zookeeper 这种作为锁服务能够自动进行故障切换,因为它本身就是个集群,另一种可以提供多个独立的锁服务,客户端向多个独立锁服务进行请求,某个锁服务故障时,也可以从其他服务获取到锁信息,但是这种缺点很明显,客户端需要去请求多个锁服务。

分类

自旋方式

基于数据库和基于 Etcd 的实现就是需要在客户端未获得锁时,进入一个循环,不断的尝试请求是否能获得锁,直到成功或者超时过期为止。

监听方式

这种方式只需要客户端 Watch 监听某个 key 就可以了,锁可用的时候会通知客户端,客户端不需要反复请求,基于 zooKeeper 和基于 Etcd 实现分布式锁就是用这种方式

实现方式

基于 MySQL

基于 Redis

Redis 分布式锁原理分析和实践

参考

https://mp.weixin.qq.com/s/JzCHpIOiFVmBoAko58ZuGw

https://garden.3dot141.top/1-Inputs/Article/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81