分布式系統問題之時鐘問題
本文轉載自微信公眾號「程序員阿sir」,作者程序員阿sir。轉載本文請聯系程序員阿sir公眾號。
上一篇文章介紹了網絡問題。這一篇文章將進一步介紹另一個難題:時2. 時鐘問題
2. 時鐘問題
時鐘對應用而言是非常重要的,很多指標可以通過時鐘來衡量。比如每秒的請求數量、平均請求時間等等,這些數據是由時間間隔 (Duration) 來表示的。另一類比如文章發表時間、緩存什么時候過期等等,這些是由時間點 (Points in Time) 來表示的。
在分布式系統中,由于請求都是有網絡延遲的,我們也不知道網絡延遲有多久,所以在涉及到多個機器,每個機器記了一件事情的發生時間,我們可能不能確定事情的發生順序,因為網絡延遲是不確定的,如果是時間非常相近的事件可能還遇到了時鐘問題。
另外由于每個機器都有自己的時鐘,這個機器時鐘由硬件決定,因此可能存在一定的差別。可以通過網絡時間協議 (Network Time Protocal) 來緩解時鐘不同步的問題,或通過GPS等服務來獲取精確的網絡時間。
2.1. 單調時鐘和墻上時鐘 (Monotonic Versus Time-of-Day Clocks)
現代計算機至少包含兩種時鐘:墻上時鐘 (Wall-clock Time)(就是一般的鐘表對應的時鐘)、單調時鐘。本質上他們都表示時間,但是目的不同。
墻上時鐘 (Wall-clock Time)
墻上時鐘根據日歷返回當前的日期和時間,與我們日常理解的時鐘概念一致。比如Java中的System.currentTimeMillis()表示從1970年1月1日以來的毫秒數。
墻上時鐘通常使用NTP來進行時鐘同步,但是如果本地時鐘遠遠快于NTP服務器可能會跳到不正確的時間點。加上墻上時鐘忽略了閏秒,導致它不太適合被用于計算時間間隔 (Elapsed Time)。
單調時鐘 (Monotonic Clocks)
單調時鐘更適合計算時間間隔 (Duration, Time Interval),比如超時時間或者服務器響應時間。比如Java中的System.nanoTime()返回的就是單調時鐘。單調時鐘保證時間數字總是變大。
如果NTP檢測到本地石英比時間服務器上更快或更慢,NTP會調整本地石英的振動頻率。默認情況下,NTP允許改變頻率的最大幅度是。但是NTP不會直接調整單調時鐘的值。單調時鐘的精度很高,通常可以測量微秒級別的時間間隔。
注意單調時鐘的值沒有意義,比較不同節點上的單調時鐘的值也沒有意義,因為它們表示的含義和基準可能都不相同。一般情況下單調始終用于測量一段任務的持續時間。
2.2. 時鐘同步和準確性 (Clock Synchronization and Accuracy)
單調時鐘不需要同步,但是墻上時鐘需要根據NTP服務器做出調整。但是墻上時鐘和NTP也很可能無法對準,比如由于石英鐘本身的震蕩漂移 (Drifts)或者NTP同步時的網絡延遲等等。數據表明,當通過網絡進行時間同步時,誤差至少達到35毫秒,最差時的誤差甚至超過1秒。另外某些用戶可能故意調整本地時鐘,設置為錯誤的日期(比如為了規避游戲的時間檢查等等)。因此墻上時鐘可能是非常不準確的。
如果一個問題是依賴于時鐘同步的,那我們需要考慮如果不同步會對應用帶來哪些問題。
比如一個常見的問題是:跨節點的事件排序。如果它高度依賴于時鐘同步,就可能導致問題。比如下面的例子:
另一個使用時鐘可能導致問題的例子是:假設數據庫每個分區只有一個主節點,只有主節點可以接受寫入。那么其他節點該如何確信當前主節點還是主節點呢?一種思路是主節點從其他節點獲取一個租約 (Lease),當租約沒有超時的時候,則當前節點可以處理請求,否則不可以。偽代碼如下:
- 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);
- }
- }
如果當前租約還是有效的,離結束還有13秒,而 lease.isValid()消耗了15秒,這樣當 process(request) 開始執行時,租約已經過期了,可能其他節點成為了主節點。這樣就導致當前節點不是主節點,但是依然執行了處理寫入請求的操作。這就導致了問題。
而這種情況可能是由于進程暫停 (Process Pause)導致的。可能由于很多原因導致進程暫停,比如垃圾回收 (GC)。
總結
分布式系統可能遇到網絡問題、時鐘問題等。而且分布式系統的關鍵特點就是部分失效。所以在分布式環境下,我們的目標就是建立一個能夠容忍部分失敗的軟件系統。
為了做到這一點,首先要先能檢測錯誤,這個也不簡單,因此分布式算法大多依賴超時來確定服務是否正常。但是超時無法區分是網絡問題還是節點故障。如果因為臨時的網絡原因被誤認為是發生了節點故障,就導致這個節點被“冤枉”了,可能造成服務不穩定。
檢測到錯誤之后,系統如何能容忍錯誤也是一個難題。在分布式環境里,各個節點之間都是通過網絡來進行通信的,而網絡本身就不可靠。因此單個節點可能不能做出正確的決策,需要多個節點共同投票來進行決策。
參考文獻
[1] Kleppmann, Martin. Designing data-intensive applications: The big ideas behind reliable, scalable, and maintainable systems. " O'Reilly Media, Inc.", 2017.