ZStack 的伸縮性祕密(一)異步架構

ZStack 核心架構設計使得 99% 的任務異步執行,所以確保了單個的管理節點可以管理十萬級的物理服務器,百萬級的虛擬機,數萬級的並行任務。java

架構的創新動力

對於要管理大量的硬件和虛擬機的公有云,伸縮性是IaaS軟件要解決的主要問題之一。一箇中等規模的數據中心,可能會有50,000臺物理服務器,大約1,500,000的虛擬機,舉例來講,同時分屬於10000用戶。雖然,用戶不太可能象刷新Facebook頁面同樣開/關虛擬機,可是IaaS 系統仍是會在某個時刻被數千任務擁塞,這些任務有來自API的,還有來自內部組件的。在某些更糟糕的狀況下,一個用戶可能會等一個小時才能建立虛擬機,就是由於系統線程池只有1000,而等待處理的任務有5000個。數據庫

問題

首先,咱們明確反對某些文章中的觀點,針對 IaaS 伸縮性問題歸結於,其聲稱 「支撐基礎,特別是數據庫和消息代理是 IaaS 伸縮性的問題的罪魁禍首」。 這徹底是錯誤的!首先,就數據庫的規模來說,其頂多算是小型和中型;像 Facebook 和 Twitter 這樣的互聯網巨頭,還在擁 MySQL 做爲其主數據庫。IaaS 的數據難道超過了 Facebook 或 Twitter 嗎?徹底不可能,他們是十億級,IaaS 只有百萬級(超級數據中心)。其次,相較與 Apache Kafka 或者 ZeroMQ 此類的消息代理服務器,ZStack 所應用的 RabbitMQ 只能算是一箇中等伸縮性的代理。可是,其依然能夠保持每秒 50.000 的消息處理量。(參考,RabbitMQ 性能測試 , part 2)。難道這在 IaaS 軟件系統中作通訊還不夠嗎?徹底足夠。緩存

其實,IaaS 伸縮性問題的根源在於:任務處理慢。確實是,在 IaaS 軟件系統中任務處理很是慢,慢到要有幾秒甚至是幾分鐘才能完成。所以,當系統中全是這種慢慢處理的任務時候,固然就帶來了新任務的巨大的延遲。而這種慢處理的任務源於任務路徑過長。舉例說明,建立虛擬機,通常要通過如下路徑 身份服務(service)-->規劃器(scheduler)-> 圖象服務(service)->存儲服務->網絡服務->系統管理(Hypervisor); 每一個服務都會花費幾秒甚至幾分鐘來操做外部硬件,這就致使了超長的任務處理時長。服務器

同步 vs 異步

傳統的 IaaS 軟件系統同步處理任務;其每每是基於線程池機制。在此機制下,線程分配給每個任務,只有當前任務結束後,下一個任務才能被處理。由於,任務處理緩慢,在遇到並行任務的峯值時, 系統因爲超過了線程池的極限因此變的很慢,新來的任務只能緩存排隊。網絡

解決之道,直觀的認爲要增長線程池的容量;不過,如今操做系統雖然能夠容許程序啓動數萬的線程,可是調度效率很低。所以,人們就開始作橫向擴展,把處理任務分佈在相似軟件程序上,這些程序駐留在不一樣操做系統上;由於每一個程序擁有其獨有的線程池,從而最終增長了整個系統的線程池的容量。可是,以上橫向擴展的方案帶來了成本問題,其加大了管理的難度,而且,從軟件設計的角度講,集羣軟件自己也仍是不小的挑戰。最後,雖然其餘的包括數據庫,消息代理和外部系統(例如,成千的物理服務器)在內的基礎設施有足夠的能力來服務於更多的並行任務,可是IaaS軟件系統自己變成了雲系統的瓶頸。架構

ZStack 經過異步架構來解決這個問題。若是,咱們考慮 IaaS 軟件系統和數據中心其餘設施的關係,IaaS 軟件系統實際上是一箇中間人的角色。其協調外部系統但不作時實的任務;例如,IaaS 不做具體工做,而是存儲系統建立物理卷,鏡像系統下載模板,虛擬機由虛擬管理系統建立。那麼,IaaS 實際的工做任務就是決定如何分發子任務(sub-tasks)給外部系統。例如,對 KVM,子任務就包括了準備邏輯卷,網絡和建立虛擬機,這些子任務都是 KVM 主機實施的;這個過程可能花費5秒鐘,其中 IaaS 軟件 0.5s, 其他 4.5s 被 KVMz 主機佔用。根本上,ZStack 的異步架構確保了不用等這 4.5s,而是僅僅用0.5s 來選擇執行的 KVM 主機,而後把任務分發出去。一旦,KVM 主機完成了指定的任務,它就會通知 IaaS 管理軟件。以異步架構的方式,一個 100 線程的線程池就能輕鬆處理數千的並行任務。less

ZStack 的異步方式

異步操做在計算機世界很廣泛;異步 I/O, AJAX(Asynchronous Javascript And XML 異步的(Javascript 和 XML)是廣爲人知的例子。然而,要構建異步的全業務邏輯,特別象是 IaaS 這樣的集成軟件,仍然由不少挑戰 。異步

最大的挑戰在於,不是部分,而是所有的組件都要實現異步;例如,若是隻是構建一個異步存儲服務,但其餘相關服務都是同步。那麼,整個系統仍是沒有多少優點。這是由於,要調用存儲服務,即便它是異步的,調用方的服務仍是不得不等待其結束,那麼整個工做流依然是同步的。async

圖:線程中,業務流程服務要調用存儲服務,直到存儲服務返回了,線程才能結束。 雖然,存儲服務經過異步方式和外部存儲系統交互。ide

ZStack's 異步架構包含三部分:

  • 異步消息
  • 異步方法
  • 異步 HTTP 調用

1. 異步消息

ZStack 使用 RabbitMQ 做爲消息總線以便鏈接各個服務。當某個服務調用另外一個服務時,源服務發消息給目的服務並註冊一個回調函數,而後立刻返回;一旦目的服務完成了任務,它就會經過觸發回調函數來回復任務結果。

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);
    }
});

單個服務也能夠發送一串消息給其餘服務 ,並異步的等待回覆。

final ImageInventory inv = ImageInventory.valueOf(ivo);
final List<DownloadImageMsg> dmsgs = CollectionUtils.transformToList(msg.getBackupStorageUuids(), new Function<DownloadImageMsg, String>() {
    @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<MessageReply> replies) {
        /* do something */
    }
}

更進一步,也能發送具備必定並行性的消息串。 好比,一串十個的消息,可以兩兩發送,第三,第四個消息只有第一,第二個消息收到後在一塊兒發出。

final List<ConnectHostMsg> msgs = new ArrayList<ConnectHostMsg>(hostsToLoad.size());
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 服務,就像以上段一所示,它們之間經過異步消息通訊; 對於服務內部,一系列的互相關聯的組件,插件是經過異步方法調用來交互的。

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();
        }
    });
}

一樣, 回調也能包含返回值:

public void createApplianceVm(ApplianceVmSpec spec, final ReturnValueCompletion<ApplianceVmInventory> completion) {
    CreateApplianceVmJob job = new CreateApplianceVmJob();
    job.setSpec(spec);
    if (!spec.isSyncCreate()) {
      job.run(new ReturnValueCompletion<Object>(completion) {
          @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調用

ZStack 使用了不少代理來管理外部系統。 例如: 管理 KVM 主機的代理,管理 Console Proxy 的代理,管理虛擬路由的代理等等。這些代理都是構建在 Python CherryPy 上的輕量級的 Web 服務器。由於,沒有相似 HTML5 中的 Web Sockets 技術就不能實現雙向通訊,ZStack 就爲每一個請求,放置了一個回調 URL 在 HTTP 的包頭 。這樣,任務結束後,代理就可以發送應答給調用者的 URL。

RefreshFirewallCmd cmd = new RefreshFirewallCmd();
List<ApplianceVmFirewallRuleTO> tos = new RuleCombiner().merge();
cmd.setRules(tos);

resf.asyncJsonPost(buildUrl(ApplianceVmConstant.REFRESH_FIREWALL_PATH), cmd, new JsonAsyncRESTCallback<RefreshFirewallRsp>(msg, completion) {
    @Override
    public void fail(ErrorCode err) {
        /* handle failures */
    }

    @Override
    public void success(RefreshFirewallRsp ret) {
        /* do something */
    }

    @Override
    public Class<RefreshFirewallRsp> getReturnClass() {
        return RefreshFirewallRsp.class;
    }
});

經過這三個異步方式,ZStack 已經構建了一個分層架構,保證全部組件可以實現異步操做。

總結

此文,咱們闡述了 ZStack 的異步架構,此架構解決了因爲並行任務慢而致使的 IaaS 伸縮性問題。在測試中,使用模擬器,在單 ZStack 管理節點中,1000 線程就能輕易處理建立 1,000,000 虛擬機的10,000 個並行任務。除了單節點具備足夠伸縮性處理大部分雲系統負載的優勢外,想要支持高可用行(High Availability)或者朝大規模負載(好比,100,000 並行任務),就必須安裝多個管理節點。請參考 ZStack's stateless service in ZStack's Scalability Secrets Part 2: Stateless Services

相關文章
相關標籤/搜索