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