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); 每一个服务都会花费几秒甚至几分钟来操做外部硬件,这就致使了超长的任务处理时长。服务器
传统的 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
异步操做在计算机世界很广泛;异步 I/O, AJAX(Asynchronous Javascript And XML 异步的(Javascript 和 XML)是广为人知的例子。然而,要构建异步的全业务逻辑,特别象是 IaaS 这样的集成软件,仍然由不少挑战 。异步
最大的挑战在于,不是部分,而是所有的组件都要实现异步;例如,若是只是构建一个异步存储服务,但其余相关服务都是同步。那么,整个系统仍是没有多少优点。这是由于,要调用存储服务,即便它是异步的,调用方的服务仍是不得不等待其结束,那么整个工做流依然是同步的。async
图:线程中,业务流程服务要调用存储服务,直到存储服务返回了,线程才能结束。 虽然,存储服务经过异步方式和外部存储系统交互。ide
ZStack's 异步架构包含三部分:
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 */ } });
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); } }
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。