在分布式系統(tǒng)中,最常見的場景就是主備架構(gòu)敛助。但是如果主機不幸宕機秒梅,如何正確的通知客戶端當前后端服務(wù)器的狀況成為一個值得研究的問題。本文描述了一種簡單的模型用于解決此問題瘤睹。
背景
以一個分布式的Key-Value數(shù)據(jù)庫為背景升敲。數(shù)據(jù)庫對外提供3個接口
- Get(key)
- Put(key, value)
- Append(key, value)
客戶端對數(shù)據(jù)庫的操作請求必須發(fā)往主機, 只有當主機不可訪問(主機宕機或網(wǎng)絡(luò)問題)時默蚌,備機代替主機冻晤,并且再從集群中選一個新的機器作為備機苇羡。問題來了客戶端如何知道當前誰是主機誰是備機绸吸?
目的
為了保證客戶端與服務(wù)器就誰是主機誰是備機這個問題達成共識。
整體架構(gòu)
為了解決這個問題设江,我們在Server和Clint中間加入一個ViewServer锦茁。viewserver的作用相當于一個中介。舉個例子叉存,當客戶端想要執(zhí)行Put操作時码俩,需要先向viewserver詢問當前的Primary是誰,隨后客戶端根據(jù)viewserver的回復(fù)將Put請求發(fā)到相應(yīng)的server歼捏。
View
viewserver返回給客戶端的信息需要包含至少3條信息:
- 當前主機是誰
- 當前備機是誰
- 當前狀態(tài)的版本號我們將這3個信息稱為viewserver的當前View稿存。
typedef struct View {
Viewnum int
Primary string
Backup string
}
Server
在多個server中,只有一個主機瞳秽,一個備機瓣履,其余的server都處于空閑狀態(tài)。所有的server都需要每隔一段時間向viewserver發(fā)送Ping消息练俐,以證明自己還活著袖迎。并且通過Ping的返回值,得到當前viewserver認為的主機和備機是誰。這樣燕锥,如果主機和備機都正常辜贵,在一段時間之后,后端server都會有一致的主機和備機归形。
Client
在操作后端server之前托慨,先訊問viewserver當前主機是誰.
ViewService
viewserver的功能比較復(fù)雜,主要負責:
- 回復(fù)客戶端當前View
- 檢測后端的server的存活情況
- 保證當前主機得到最新的View
在檢測后端server存活情況時连霉,有幾種情況
- 主機Primary失聯(lián)
- 備機Backup失聯(lián)
- 空閑機器失聯(lián)當檢測到主機或者備機失聯(lián)時榴芳,viewserver應(yīng)該對當前View做調(diào)整。
如果主機失聯(lián)跺撼,則把備機選做主機窟感,然后從空間機器中挑選一個作為備機。如果備機失聯(lián)歉井,只從空閑機器中選擇一個作為備機即可柿祈。但是,這兩種情況都需要對View的版本號進行增加哩至。這個View版本號只有后端server關(guān)心躏嚎,客戶端只關(guān)心當前主機。加入這個版本號的目的菩貌,主要是為了確保當前主機得到了最新的View卢佣。比如備機的更換會需要主機向備機做數(shù)據(jù)拷貝,如果主機得不到這個消息箭阶,整個主備系統(tǒng)就失效了虚茶。
上圖描述了兩臺server與viewserver之間的通信.每次server端Ping需要包含一個參數(shù),用于表示當前這個server所了解到了View版本號.
- 在初始狀態(tài)仇参,server1向viewserver發(fā)送Ping嘹叫,并且用0做為參數(shù).由于初始狀態(tài)viewserver還沒有選擇主機和備機,所以先到先得诈乒,選擇server1為主機.于是返回給server1的View為[server1, NULL, 1], 表示當前主機為server1罩扇, 備機為NULL, 望的到的版本號為1(希望一段時間后當前的主機Primary以這個新版本號發(fā)起Ping).
- server2此時加入了集群,向viewserver發(fā)送了Ping. 由于server2與server1沒有聯(lián)系怕磨,所以Ping的參數(shù)還是用0喂饥,表示這是新加入的機器.雖然此時viewserver發(fā)現(xiàn)又有了一臺機器加入,并且當前只有主機肠鲫,沒有備機员帮,但是還不能選擇server2做為備機.因為server1還沒有用1做為參數(shù)發(fā)起Ping, 說明viewserver當前的這個View(主機為server1,備機為NULL)可能還沒有被當前主機(server1)收到.因此返回給server2的View為[server1, NULL, 1].
- server1順利收到viewserver返回的View,得知viewserver想要版本號為1的Ping, 于是隔一個PingInerval再向viewserver發(fā)起Ping滩届,同時以1為參數(shù). viewserver順利收到這個Ping, 發(fā)現(xiàn)正是自己希望得到的(當前主機以新版本號發(fā)起的Ping), 并且了解到一個事實:當前主機server1已經(jīng)知道了當前最新的主備情況.于是viewserver將View的版本號更新.返回給server1[server1, NULL, 2].
- server2在一個PingInterval后再次向viewser發(fā)起Ping, 因為之前一次Ping返回的ViewNum為1, 因此用1為參數(shù).當viewserver順利收到Ping時集侯,由于viewserver知道當前主機server1以經(jīng)獲得了它目前保存的這個狀態(tài)被啼,因此將server2選為備機, 構(gòu)成了一個新的狀態(tài)(主機為server1, 備機為server2), 這個狀態(tài)還沒有任何server知道, 當前的主機server1顯然也不知道.因此ViewNum不能更新.于是返回[server1, server2, 2]給server2.
- server1向viewserver發(fā)起Ping(2), viewserver通過這個Ping得知當前主機server1以經(jīng)獲得了自己最新的狀態(tài).然后server1失聯(lián).
- server2向viewserver發(fā)起Ping(2)在長期沒有的到server1的Ping后棠枉,viewserver會認為server1以經(jīng)不能繼續(xù)對外提供服務(wù)了浓体,于是選擇備機server2做為主機,如果有其他空閑機器辈讶,可以從中選一個做為新的備機.viewserver之所以選擇server2做為備機是因為確定server2可以萬全代替以經(jīng)失聯(lián)的server1, 因為server2做為備機這個信息server1以經(jīng)收到了命浴,確保server2的狀態(tài)與server1相同是server1的工作,比如主機會向備機發(fā)送數(shù)據(jù)做數(shù)據(jù)同步.
存在的問題
If the view service has not yet received an acknowledgment for the current view from the primary of the current view, the view service should not change viewseven ifit thinks that the primary or backup has died. That is, the view service may not proceed from view X to view X+1 if it has not received a Ping(X) from the primary of view X.