《深入浅出分布式技术原理》 学习笔记 day6

92 阅读6分钟

大家好,我是砸锅。一个摸鱼八年的后端开发。熟悉 Go、Lua。今天和大家一起学习分布式技术😊

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 17 天,点击查看活动详情

分布式锁

分布式的背景

锁是用于并发控制,能够确保在多 CPU、多个线程的环境里,某一个时间上只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性

在分布式系统里,多个进程同时往一个中心存储的同一个位置写入一个文件,会出现文件写入冲突。多个实例在同一时刻只能有一个实例运行,这是一个典型的分布式锁的场景。分布式锁是一个跨进程的锁,是一个更高维度的锁

分布式锁的实现

三种不同维度的锁:

  1. 进程内部的锁。由操作系统直接提供的,本质上是一个整数,用不同数值表示不同状态,例如 0 为空闲,加锁时判断锁是否空闲。空闲则修改为加锁状态 1,并返回成功。如果已经加锁了,则返回失败。解锁时则将锁状态改成空闲状态 0 ,加锁和解锁过程都是操作系统保证原子性
  2. 跨进程、跨机器之间的分布式锁。通过一个状态来表示加锁和解锁,只是要将锁放在所有需要锁的服务都能访问的地方,也就是一个存储服务,再通过网络来访问锁服务来修改状态信息,进行加锁和解锁
  3. 同一台机器上多进程之间的锁。也是通过操作系统的锁来实现,只是要将锁放在所有进程都可以访问的共享内存里,所有进程通过共享内存中的锁来进行加锁和解锁

分布式锁满足的特性

  1. 互斥。保证不同节点、不同线程的互斥访问
  2. 超时机制。超时设置可以防止死锁,避免获得锁的节点故障或者网络异常,导致它持有的锁不能归还出现死锁情况 同时还要考虑持有锁的节点需要处理的临界区代码非常耗时这种问题,避免出现锁操作还没处理完,锁就被释放了,之后其他节点再获得锁,导致锁的互斥失败这种情况。所以我们可以在每次成功获得锁的时候,为锁设置一个超时时间,获得锁的节点和锁服务保持心跳,锁服务每一次收到心跳就延长锁的超时时间
  3. 完备的锁接口。即阻塞接口 Lock 和非阻塞接口 tryLock,通过阻塞接口 Lock 获取锁,如果当前锁被其他节点获得了,那锁服务就将获取锁的请求挂起,直到获得锁为止才响应获取锁的请求;通过非阻塞接口 tryLock 接口获取锁,如果当前锁已经被其他节点获得了,锁服务直接返回失败,不挂起当前锁的请求
  4. 可重入性。如果一个节点的一个线程已经获得锁了,那么该节点持有锁的这个线程可以再次获得锁。在锁服务加锁请求时,记录当前获得锁的节点+线程组合的唯一标识,后续的加锁请求时,如果当前请求的节点+线程的唯一标识和当前持有锁的相同,直接返回加锁成功。否则按照正常加锁流程处理
  5. 公平性。对于阻塞接口 Lock 的获取锁失败被阻塞等待的加锁请求,在锁释放之后,如果按照先来后到的顺序,将锁分配给等待时间最长的一个加锁请求,那就是公平锁。否则就是非公平锁。公平锁实现也简单,对于被阻塞的加锁请求,记录好它们的顺序,在锁释放之后按顺序分配就可以了

分布式锁的正确性

进程内的锁,如果一个线程持有锁,只要它不释放,就只有它能操作临界区的资源。进程内锁的解锁操作是进程内部的函数调用,这个过程是同步的,不论是硬件还是其他原因,只要发起解锁操作就一定会成功,如果出现失败的情况,整个进程或者机器都会挂掉。所以整体失败和同步通信可以保证进程内的锁是绝对正确

同一台机器内多个进程锁,由于锁是存放在多进程的共享内存里,所以进程和锁之间的通信依然是同步的函数调用,不会出现解锁之后信息丢失,导致死锁的情况。父进程在获得进程挂掉的信号之后,可以查看当前挂掉的进程是否有锁,如果持有锁就进行释放,算是进程崩溃之后清理工作的一部分

分布式系统的锁,部分失败和异步网络这两个问题是会同时存在的。如果一个进程获得了锁,但是这个进程和锁服务之间的网络出现了问题,导致无法通信了。一般这种情况下,锁服务在进程加锁成功后会设置一个超时时间,避免死锁。分布式锁的设计应该多多关注高可用和性能,以及如何提高正确性,而不是追求绝对的正确性

分布式锁的权衡

分布式锁的场景下,无法保证 100% 的正确性,所以要避免通过外部分布式锁来保证需要 100% 正确性的场景。一般来说,一个分布式锁服务,它的正确性要求越高,性能可能就会越低

高可用是设计分布式锁时,需要考虑的关键因素。因为分布式锁是一个非常底层的服务组件,是整个分布式系统的基石之一,如果出现故障,会导致整个分布式系统大面积出现故障

此文章为2月Day13学习笔记,内容来源于极客时间《深入浅出分布式技术原理》

一般来说,在成本可以接受的范围内,选择性能最好的分布式锁服务