分佈式併發原語
常用來做協調工作的軟件系統是 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 客户端庫通常會自動重試整個事務邏輯。