這一章我們會討論分佈式系統中一些常見的問題,在下一章中我們會討論這些問題的解決辦法。
1.故障與部分失敗
在分佈式系統中,系統的部分組件常常會以以某種未知的方式被破壞,我們稱之爲部分失敗/部分故障。而我們的目標就是在不可靠的組件上構建一個可靠的系統。
爲了容忍部分失敗:
- 第一步是檢測失敗:大多數系統利用超時來判斷節點是否可用,但是超時機制沒辦法區分是網絡失效還是節點宕機
- 在檢測到失敗之後,如何使系統去容忍失敗也是個大問題:節點間通信的唯一方法是通過不可靠的網絡發送信息,不能由一個節點來做決策,因此我們需要一致性協議算法來幫助我們完成這個目標。
2.不可靠的網絡
在分佈式系統中,節點與節點間的網絡通信是不可靠的,它表現在:
- 網絡分區:網絡的一部分發生了故障而被切斷
- 延遲問題:TCP協議雖然提供了數據校驗、超時重傳等機制,但是沒辦法保證數據包在一定時間內保證到達,這種延遲可能是無限的。
3.不可靠的時鐘
在分佈式系統中,時鐘是一個非常重要且棘手的問題:
- 節點與節點間的通信是有延遲的,我們很難確定多個節點間事件的順序問題
- 每臺機器都有自己的時鐘(硬件設備),這些設備不都是完全準確的
時鐘分爲兩種,單調時鐘與Time-of-Day時鐘:
- 單調時鐘一般用於計算持續時間,如Java中的System.nanoTime()
- Time-of-Day時鐘根據某個日曆返回當前的日期和時間,一般依賴於NTP服務器或其他外部時間源
時鐘不同步導致的問題
時鐘的不同步會導致很多問題,比如前面的博客中,我們談到多主備份數據庫解決寫入衝突的一個常見辦法是LWW,即最後寫入者勝出,在時鐘不同步的情況下可能會導致如下問題:
ClientA在node1上寫入x=1,並同步到node3。 ClientB在node3上寫入x=x+1,也同步到node2。但是B的寫入同步比A的更早到達,從而導致在node2上x+1的操作消失了。
進程的暫停問題
假設在一個分佈式數據庫中,節點通過選舉方式成爲leader(只有leader能接受寫請求),並獲得一個租約,且同一時間只有一個節點能獲得租約。假設有這麼一段代碼:
while (true) {
request = getIncomingRequest();
// Ensure that the lease always has at least 10 seconds remaining
if (lease.expiryTimeMillis - System.currentTimeMillis() < 10000) {
lease = lease.renew();
}
if (lease.isValid()) {
process(request);
}
}
這段代碼的問題:
如果當前線程在lease.isValid()之後暫停了15秒,然後才繼續運行process(request),而這時候租約可能已經到期了,它已經不再是leader了!
那麼這個進程爲什麼要暫停這麼久呢? 可能的原因如下:
- GC運行了很久
- 虛擬機被掛起,當前所有進程被暫停並把內存內容保存到磁盤
- 進程接收到了SIGSTOP信號,暫停使用CPU週期;之後又接收到SIGCONT信號才恢復
- 執行了同步訪問磁盤的操作,如果磁盤需要走網絡(比如EBS),可能延遲很大