分布式锁背景,在单机的场景下,可以使用语言的内置锁来实现同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。

实现方法有:

  1. 数据库唯一索引
  2. Redis的SETNX指令
  3. Redis的RedLock算法
  4. Zookeeper的有序节点

数据库唯一索引

获得锁时向表中插入一条记录,释放锁时删除这条记录。

唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否处于锁定状态。

缺点为:

  • 锁没有失效时间,解锁失败其他进程就无法获得该锁。
  • 非阻塞锁,插入失败无法重试
  • 不可重入

Redis的SETNX指令

SETNX指令在key已存在的情况会返回0并取消插入,若key不在则插入成功返回1.

SETNX保证只存在一个指定Key的键值对,并且可以用EXPIRE指令为键值对设置一个过期时间,避免释放锁失败的问题。

将SETNX和Expire合在一起的方式:set $key $value ex $time nx

分布式锁要确保以下几点:

  1. 命令必须保证互斥
  2. 设置的key必须要有过期时间,防止崩溃时锁无法释放
  3. value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁,使用Lua脚本让判断标识和删除操作合并为原子操作。

Redis的RedLock算法

使用了多个Redis实例来实现分布式锁,为了保证单点故障时仍然可用。

  1. 尝试从N个独立的Redis实例获取锁
  2. 计算锁消耗的时间,只有时间小于锁的过期时间,并且大多数实例(N/2 +1)获得了锁,才认为获取锁成功
  3. 如果获取锁失败,就到每个实例上释放锁

Zookeeper的有序节点

Zookeeper提供了一种树形的命名空间

image.png

节点类型

  1. 永久节点:不会因为会话结束或超时而消失。
  2. 临时节点:如果会话结束超时就会消失
  3. 有序节点:会在节点名的后面加一个数字后缀,并且存在一定顺序

监听器

为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息。

分布锁实现

  1. 创建一个锁目录/lock;
  2. 当一个客户端需要获得锁时,在/lock下创建临时的有序子节点。
  3. 客户端获取/lock下的子节点列表,判断自己创建的有序子节点是否为列表中序号最小的子节点。
  4. 如果是则认为获得锁,否则就监听自己的前一个子节点,当子节点变更后重复此步骤直至获得锁。
  5. 执行业务代码,完成后,删除对应的子节点。

因为创建的是临时节点,当发生超时时该节点会被删除,其他会话就可以获得锁了。可以看到,这种实现方法不会出现锁释放失败的问题。

羊群效应

一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应,一只羊动起来,其它羊也会一哄而上),而我们只希望它的后一个子节点收到通知

分布式事务

2PC

两阶段提交,通过引入协调者来协调参与者的行为,并最终决定这些参与者是否真正执行事务

  1. 准备阶段

协调者询问参与者事务是否执行成功,参与者发送事务执行结果,并未提交或回滚。

  1. 提交阶段

如果事务的每个参与者都执行成功,事务协调者发送通知让参与者提交事务;否则发送通知让参与者回滚事务。

存在的问题:

  1. 同步阻塞:所有事务参与者在其他参与者响应时都处于同步阻塞状态,无法进行其他操作
  2. 单点故障:协调者若故障则会造成很大影响,特别是在提交阶段发送故障,所有参与者会一直同步阻塞等待。
  3. 数据不一致:如果协调者值发送了部分Commit给参与者,此时网络异常或者协调者故障,则会导致只有部分参与者提交了事务,是的数据不一致

本地消息列表(异步确保)

本地消息表与业务数据表处于同一数据库中,并利用本地事务保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。

  1. 在分布式事务操作的一方完成写入数据的操作后向本地消息表发生一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
  2. 之后本地消息表的消息转发到消息队列中,如果转发成功则将消息从消息表中删除,否则继续重新转发。
  3. 分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

image.png

CAP

一致性(consistency)

一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。

对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。

可用性(availability)

可用性指的是分布式系统是否能够正常提供访问能力,通常指的是能否在一定时间内返回数据,不管这个数据是否是最新的版本。

分区容忍性(Partition tolerance)

网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。

分区容忍性的意思是,在分布式系统中是可以允许分区问题的存在的,不会因为分区问题而导致整个系统瘫痪。

权衡

在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。

可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,一般都是保证AP,放弃C,通过应用系统来处理数据的不一致性。

  • 为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;
  • 为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。