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