ZStack 核心架構(gòu)設(shè)計使得 99% 的任務(wù)異步執(zhí)行,因此確保了單個的管理節(jié)點能夠管理十萬級的物理服務(wù)器,百萬級的虛擬機,數(shù)萬級的并行任務(wù)。
架構(gòu)的創(chuàng)新動力
對于要管理大量的硬件和虛擬機的公有云,伸縮性是IaaS軟件要解決的主要問題之一。一個中等規(guī)模的數(shù)據(jù)中心,可能會有50.000臺物理服務(wù)器,大約 1,500,000的虛擬機,舉例來說,同時分屬于10000用戶。雖然,用戶不太可能象刷新Facebook頁面一樣開/關(guān)虛擬機,但是IaaS 系統(tǒng)還是會在某個時刻被數(shù)千任務(wù)擁塞,這些任務(wù)有來自API的,還有來自內(nèi)部組件的。在某些更糟糕的情況下,一個用戶可能會等一個小時才能創(chuàng)建虛擬機,就是因為系統(tǒng)線程池只有1000,而等待處理的任務(wù)有5000個。
問題
首先,我們明確反對某些文章中的觀點,針對 IaaS 伸縮性問題歸結(jié)于,其聲稱 “支撐基礎(chǔ),特別是數(shù)據(jù)庫和消息代理是 IaaS 伸縮性的問題的罪魁禍首”。 這完全是錯誤的!首先,就數(shù)據(jù)庫的規(guī)模來講,其頂多算是小型和中型;像 Facebook 和 Twitter 這樣的互聯(lián)網(wǎng)巨頭,還在擁 MySQL 作為其主數(shù)據(jù)庫。IaaS 的數(shù)據(jù)難道超過了 Facebook 或 Twitter 嗎?完全不可能,他們是十億級,IaaS 只有百萬級(超級數(shù)據(jù)中心)。其次,相較與 Apache Kafka 或者 ZeroMQ 此類的消息代理服務(wù)器,ZStack 所應(yīng)用的 RabbitMQ 只能算是一個中等伸縮性的代理。但是,其依然可以保持每秒 50.000 的消息處理量。(參考,RabbitMQ 性能測試 , part 2)。難道這在 IaaS 軟件系統(tǒng)中做通信還不夠嗎?完全足夠。
其實,IaaS 伸縮性問題的根源在于:任務(wù)處理慢。確實是,在 IaaS 軟件系統(tǒng)中任務(wù)處理非常慢,慢到要有幾秒甚至是幾分鐘才能完成。因此,當系統(tǒng)中全是這種慢慢處理的任務(wù)時候,當然就帶來了新任務(wù)的巨大的延遲。而這種慢處理的任務(wù)源于任務(wù)路徑過長。舉例說明,創(chuàng)建虛擬機,一般要經(jīng)過以下路徑 身份服務(wù)(service)-->規(guī)劃器(scheduler)-> 圖象服務(wù)(service)->存儲服務(wù)->網(wǎng)絡(luò)服務(wù)->系統(tǒng)管理(Hypervisor); 每個服務(wù)都會花費幾秒甚至幾分鐘來操作外部硬件,這就導(dǎo)致了超長的任務(wù)處理時長。
同步 vs 異步
傳統(tǒng)的 IaaS 軟件系統(tǒng)同步處理任務(wù);其往往是基于線程池機制。在此機制下,線程分配給每一個任務(wù),只有當前任務(wù)結(jié)束后,下一個任務(wù)才能被處理。因為,任務(wù)處理緩慢,在遇到并行任務(wù)的峰值時, 系統(tǒng)由于超過了線程池的極限所以變的很慢,新來的任務(wù)只能緩存排隊。
解決之道,直觀的認為要增加線程池的容量;不過,現(xiàn)在操作系統(tǒng)雖然可以允許程序啟動數(shù)萬的線程,但是調(diào)度效率很低。因此,人們就開始做橫向擴展,把處理任務(wù)分布在類似軟件程序上,這些程序駐留在不同操作系統(tǒng)上;因為每個程序擁有其獨有的線程池,從而最終增加了整個系統(tǒng)的線程池的容量。但是,以上橫向擴展的方案帶來了成本問題,其加大了管理的難度,并且,從軟件設(shè)計的角度講,集群軟件本身也還是不小的挑戰(zhàn)。最后,雖然其他的包括數(shù)據(jù)庫,消息代理和外部系統(tǒng)(例如,成千的物理服務(wù)器)在內(nèi)的基礎(chǔ)設(shè)施有足夠的能力來服務(wù)于更多的并行任務(wù),但是IaaS軟件系統(tǒng)本身變成了云系統(tǒng)的瓶頸。
ZStack 通過異步架構(gòu)來解決這個問題。如果,我們考慮 IaaS 軟件系統(tǒng)和數(shù)據(jù)中心其他設(shè)施的關(guān)系,IaaS 軟件系統(tǒng)其實是一個中間人的角色。其協(xié)調(diào)外部系統(tǒng)但不做時實的任務(wù);例如,IaaS 不作具體工作,而是存儲系統(tǒng)創(chuàng)建物理卷,鏡像系統(tǒng)下載模板,虛擬機由虛擬管理系統(tǒng)創(chuàng)建。那么,IaaS 實際的工作任務(wù)就是決定如何分發(fā)子任務(wù)(sub-tasks)給外部系統(tǒng)。例如,對 KVM,子任務(wù)就包括了準備邏輯卷,網(wǎng)絡(luò)和創(chuàng)建虛擬機,這些子任務(wù)都是 KVM 主機實施的;這個過程可能花費5秒鐘,其中 IaaS 軟件 0.5s, 其余 4.5s 被 KVMz 主機占用。根本上,ZStack 的異步架構(gòu)確保了不用等這 4.5s,而是僅僅用0.5s 來選擇執(zhí)行的 KVM 主機,然后把任務(wù)分發(fā)出去。一旦,KVM 主機完成了指定的任務(wù),它就會通知 IaaS 管理軟件。以異步架構(gòu)的方式,一個 100 線程的線程池就能輕松處理數(shù)千的并行任務(wù)。
ZStack 的異步方式
異步操作在計算機世界很普遍;異步 I/O, AJAX(Asynchronous Javascript And XML 異步的(Javascript 和 XML)是廣為人知的例子。然而,要構(gòu)建異步的全業(yè)務(wù)邏輯,特別象是 IaaS 這樣的集成軟件,仍然由很多挑戰(zhàn) 。
最大的挑戰(zhàn)在于,不是部分,而是全部的組件都要實現(xiàn)異步;例如,如果只是構(gòu)建一個異步存儲服務(wù),但其他相關(guān)服務(wù)都是同步。那么,整個系統(tǒng)還是沒有多少優(yōu)勢。這是因為,要調(diào)用存儲服務(wù),即使它是異步的,調(diào)用方的服務(wù)還是不得不等待其結(jié)束,那么整個工作流依然是同步的。
圖:線程中,業(yè)務(wù)流程服務(wù)要調(diào)用存儲服務(wù),直到存儲服務(wù)返回了,線程才能結(jié)束。 雖然,存儲服務(wù)通過異步方式和外部存儲系統(tǒng)交互。
ZStack's 異步架構(gòu)包含三部分: 異步消息,異步方法,異步 HTTP 調(diào)用。
1. 異步消息
ZStack 使用 RabbitMQ 作為消息總線以便連接各個服務(wù)。當某個服務(wù)調(diào)用另一個服務(wù)時,源服務(wù)發(fā)消息給目的服務(wù)并注冊一個回調(diào)函數(shù),然后馬上返回;一旦目的服務(wù)完成了任務(wù),它就會通過觸發(fā)回調(diào)函數(shù)來回復(fù)任務(wù)結(jié)果。
AttachNicToVmOnHypervisorMsg amsg = new AttachNicToVmOnHypervisorMsg();
amsg.setVmUuid(self.getUuid());
amsg.setHostUuid(self.getHostUuid());
amsg.setNics(msg.getNics());
bus.makeTargetServiceIdByResourceUuid(amsg, HostConstant.SERVICE_ID, self.getHostUuid());
bus.send(amsg, new CloudBusCallBack(msg) {
@Override
public void run(MessageReply reply) {
AttachNicToVmReply r = new AttachNicToVmReply();
if (!reply.isSuccess()) {
r.setError(errf.instantiateErrorCode(VmErrors.ATTACH_NETWORK_ERROR, r.getError()));
}
bus.reply(msg, r);
}
});
單個服務(wù)也可以發(fā)送一串消息給其他服務(wù) ,并異步的等待回復(fù)。
final ImageInventory inv = ImageInventory.valueOf(ivo);
final List
@Override
public DownloadImageMsg call(String arg) {
DownloadImageMsg dmsg = new DownloadImageMsg(inv);
dmsg.setBackupStorageUuid(arg);
bus.makeTargetServiceIdByResourceUuid(dmsg, BackupStorageConstant.SERVICE_ID, arg);
return dmsg;
}
});
bus.send(dmsgs, new CloudBusListCallBack(msg) {
@Override
public void run(List
/* do something */
}
}
更進一步,也能發(fā)送具有一定并行性的消息串。 比如,一串十個的消息,能夠兩兩發(fā)送,第三,第四個消息只有第一,第二個消息收到后在一起發(fā)出。
final List
for (String uuid : hostsToLoad) {
ConnectHostMsg connectMsg = new ConnectHostMsg(uuid);
connectMsg.setNewAdd(false);
connectMsg.setServiceId(serviceId);
connectMsg.setStartPingTaskOnFailure(true);
msgs.add(connectMsg);
}
bus.send(msgs, HostGlobalConfig.HOST_LOAD_PARALLELISM_DEGREE.value(Integer.class), new CloudBusSteppingCallback() {
@Override
public void run(NeedReplyMessage msg, MessageReply reply) {
/* do something */
}
});
2. 異步方法
ZStack 服務(wù),就像以上段一所示,它們之間通過異步消息通信; 對于服務(wù)內(nèi)部,一系列的互相關(guān)聯(lián)的組件,插件是通過異步方法調(diào)用來交互的。
protected void startVm(final APIStartVmInstanceMsg msg, final SyncTaskChain taskChain) {
startVm(msg, new Completion(taskChain) {
@Override
public void success() {
VmInstanceInventory inv = VmInstanceInventory.valueOf(self);
APIStartVmInstanceEvent evt = new APIStartVmInstanceEvent(msg.getId());
evt.setInventory(inv);
bus.publish(evt);
taskChain.next();
}
@Override
public void fail(ErrorCode errorCode) {
APIStartVmInstanceEvent evt = new APIStartVmInstanceEvent(msg.getId());
evt.setErrorCode(errf.instantiateErrorCode(VmErrors.START_ERROR, errorCode));
bus.publish(evt);
taskChain.next();
}
});
}
同樣, 回調(diào)也能包含返回值:
public void createApplianceVm(ApplianceVmSpec spec, final ReturnValueCompletion
CreateApplianceVmJob job = new CreateApplianceVmJob();
job.setSpec(spec);
if (!spec.isSyncCreate()) {
job.run(new ReturnValueCompletion
@Override
public void success(Object returnValue) {
completion.success((ApplianceVmInventory) returnValue);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
} else {
jobf.execute(spec.getName(), OWNER, job, completion, ApplianceVmInventory.class);
}
}
3. 異步HTTP調(diào)用
ZStack 使用了很多代理來管理外部系統(tǒng)。 例如: 管理 KVM 主機的代理,管理 Console Proxy 的代理,管理虛擬路由的代理等等。這些代理都是構(gòu)建在 Python CherryPy 上的輕量級的 Web 服務(wù)器。因為,沒有類似 HTML5 中的 Web Sockets 技術(shù)就不能實現(xiàn)雙向通信,ZStack 就為每個請求,放置了一個回調(diào) URL 在 HTTP 的包頭 。這樣,任務(wù)結(jié)束后,代理就能夠發(fā)送應(yīng)答給調(diào)用者的 URL。
RefreshFirewallCmd cmd = new RefreshFirewallCmd();
List
cmd.setRules(tos);
resf.asyncJsonPost(buildUrl(ApplianceVmConstant.REFRESH_FIREWALL_PATH), cmd, new JsonAsyncRESTCallback
@Override
public void fail(ErrorCode err) {
/* handle failures */
}
@Override
public void success(RefreshFirewallRsp ret) {
/* do something */
}
@Override
public Class
return RefreshFirewallRsp.class;
}
});
通過這三個異步方式,ZStack 已經(jīng)構(gòu)建了一個分層架構(gòu),保證所有組件能夠?qū)崿F(xiàn)異步操作。
總結(jié)
此文,我們闡述了 ZStack 的異步架構(gòu),此架構(gòu)解決了由于并行任務(wù)慢而導(dǎo)致的 IaaS 伸縮性問題。在測試中,使用模擬器,在單 ZStack 管理節(jié)點中,1000 線程就能輕易處理創(chuàng)建 1,000,000 虛擬機的10.000 個并行任務(wù)。除了單節(jié)點具有足夠伸縮性處理大部分云系統(tǒng)負載的優(yōu)點外,想要支持高可用行(High Availability)或者朝大規(guī)模負載(比如,100,000 并行任務(wù)),就必須安裝多個管理節(jié)點。