分布式并发原语
常用来做协调工作的软件系统是 Zookeeper、etcd、Consul 之类的软件
Zookeeper - Java
Consul 分布式并发原语一般
etcd🐂:分布式互斥锁、分布式读写锁、Leader 选举
Leader 选举
Leader + Slave
主节点常常执行写操作,从节点常常执行读操作,如果读写都在主节点,从节点只是提供一个备份功能的话,那么,主从架构就会退化成主备模式架构。
通过 etcd 基础服务来实现 leader 选举
方法:
- Campaign:把一个节点选举为主节点,并且会设置一个值,这是一个阻塞方法,在调用它的时候会被阻塞,直到满足下面的三个条件之一,才会取消阻塞。成功当选为主/返回错误/ctx 被取消。
- Proclaim:重新设置 Leader 的值,但是不会重新选主
- Resign:开始新一次选举。这个方法会返回新的选举成功或者失败的信息
- Leader:查询当前的主节点是哪一个节点,主节点的值,版本
- Observe:返回一个 chan,显示主节点的变动信息。
在使用的过程中,还需要做一些额外的设置,比如查询当前的主节点、启动一个 goroutine 阻塞调用 Campaign 方法,等等。
分布在不同机器中的不同进程内的 goroutine,如何利用分布式互斥锁来保护共享资源:
- 使用互斥锁的不同节点是没有主从这样的角色的,所有的节点都是一样的,只不过在同一时刻,只允许其中的一个节点持有锁。
- Locker + Mutex + RWMutex
总结:
在使用这些分布式并发原语的时候,你需要考虑异常的情况,比如网络断掉等。同时,分布式并发原语需要网络之间的通讯,所以会比使用标准库中的并发原语耗时更长。
| 节点宕机,锁会释放吗? | 会,一定会。 | 租约 (Lease) + 心跳 (KeepAlive) 。节点无法为租约续命,etcd 会自动删除与租约绑定的锁 Key。 |
|---|---|---|
| 读写锁有优先级吗? | 有,写优先。 | 基于 Revision 的排队。一个等待中的写锁请求,会阻塞所有在它之后到达的新读锁请求,防止写操作被饿死。 |
分布式队列和优先级队列
- NewQueue 创建队列
- Enqueue 入队
- Dequeue 出队
可以在一个节点将元素放入队列,在另外一个节点把它取出
- PriorityQueue 优先级队列
- 总结:etcd 的队列适用于低吞吐量、但对任务分发的一致性和可靠性要求极高的场景(比如 Kubernetes 的 Job 调度),而不适合用作通用的高吞吐量消息总线。
分布式栅栏
-
Barrier:分布式栅栏。如果持有 Barrier 的节点释放了它,所有等待这个 Barrier 的节点就不会被阻塞,而是会继续执行。
- Hold 方法是创建一个 Barrier。如果 Barrier 已经创建好了,有节点调用它的 Wait 方法,就会被阻塞。
- Release 方法是释放这个 Barrier,也就是打开栅栏。如果使用了这个方法,所有被阻塞的节点都会被放行,继续执行。
- Wait 方法会阻塞当前的调用者,直到这个 Barrier 被 release。如果这个栅栏不存在,调用者不会被阻塞,而是会继续执行。
-
DoubleBarrier:计数型栅栏。在初始化计数型栅栏的时候,我们就必须提供参与节点的数量,当这些数量的节点都 Enter 或者 Leave 的时候,这个栅栏就会放开。
STM
事务能够保证这些更改要么全成功,要么全失败
-
隔离级别:etcd 的 STM 提供的是可串行化 (Serializable) 的隔离级别,这是最高的隔离级别。它通过乐观锁 (Optimistic Locking) 和 多版本并发控制 (MVCC) 来实现。
-
工作方式 (乐观锁) :
-
开始事务:你开始一个 STM 事务。etcd 不会在此时加任何锁。
-
本地读写:你在事务中进行的所有读写操作,都只是在本地的缓存中进行的。你读取的是某个特定版本(Revision)的数据快照。
-
提交事务 (Commit) :当你提交事务时,最关键的事情发生了:
- etcd 会检查你在事务期间读取过的所有 Key,看看它们在 etcd 中的当前版本是否和你当初读取时的版本一致。
- 如果一致 (无冲突) :说明在你操作期间,没有其他人修改过这些数据。etcd 会原子性地将你的所有写操作应用到数据库中,事务成功。
- 如果不一致 (有冲突) :说明在你“思考”的时候,有人抢先修改了数据。etcd 会拒绝你的提交,整个事务失败。
- 重试:etcd 的 STM 客户端库通常会自动重试整个事务逻辑。
-