ZStack 的伸縮性秘密(第一部分)異步架構

jopen 9年前發布 | 17K 次閱讀 ZStack 軟件架構

ZStack 的伸縮性秘密(第一部分)異步架構

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

架構的創新動力

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

問題

首先,我們明確反對某些文章中的觀點,針對 IaaS 伸縮性問題歸結于,其聲稱 “支撐基礎,特別是數據庫和消息代理是 IaaS 伸縮性的問題的罪魁禍首”。 這完全是錯誤的!首先,就數據庫的規模來講,其頂多算是小型和中型;像 非死book 和 推ter 這樣的互聯網巨頭,還在擁 MySQL 作為其主數據庫。IaaS 的數據難道超過了 非死book 或 推ter 嗎?完全不可能,他們是十億級,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 線程的線程池就能輕松處理數千的并行任務。

ZStack 的異步方式

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

最大的挑戰在于,不是部分,而是全部的組件都要實現異步;例如,如果只是構建一個異步存儲服務,但其他相關服務都是同步。那么,整個系統還是沒有多少優勢。這是因為,要調用存儲服務,即使它是異步的,調用方的服務還是不得不等待其結束,那么整個工作流依然是同步的。

ZStack 的伸縮性秘密(第一部分)異步架構

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

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 的伸縮性秘密(第一部分)異步架構

總結

此文,我們闡述了 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


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!