ZStack--可拓展性的秘密武器1:异步架构

ZStack的架构使得其中99%的任务能被异步执行。基于这点,ZStack中单一的管理节点可以管理几千台物理服务器,上万台虚拟机,处理成千上万个并发任务。

动机

对于管理大量硬件和虚拟机的公有云而言,可拓展性是一个IaaS软件必须解决的关键问题之一。对于一个大概拥有5万台物理服务器的中型数据中心,预计可能有150万台虚拟机,1万名用户。虽然用户开关虚拟机的频率不会像刷朋友圈一样频繁,但是在某一时刻,IaaS系统可能有成千上万个任务要处理,这些任务可能来自API也可能来自内部组件。在糟糕的情况下,用户为了创建一台新的虚拟机可能需要等待一个小时,因为系统同时被5000个任务阻塞,然而线程池仅有1000条线程。

问题

首先,我们非常不赞同一些文章里面描写的关于“一些基础配套设施,尤其是数据库和消息代理(message brokers)限制了IaaS的可拓展性”的观点。首先,对于数据库而言,IaaS软件的数据量相比facebook和twitter而言只能勉强算中小型,facebook和twitter的数据量是万亿级别,IaaS软件只处于百万级别(对于一些非常大型的数据中心),而facebook和twitter依旧坚强的使用Mysql作为他们主要的数据库。其次,对于消息代理而言,ZStack使用的rabbitmq相对Apache Kafka或ZeroMQ是一个中型的消息代理,但是它依然可以维持平均每秒5万条消息的吞吐量,对于IaaS软件内部通信而言这不就足够了么?我们认为足够了。

限制IaaS可拓展性的主要原因在于:任务执行缓慢。IaaS软件上的任务运行非常缓慢,通常一项任务完成需要花费几秒甚至几分钟。所以当整个系统被缓慢的任务填满的时候,新任务的延迟非常大是很正常的。执行缓慢的任务通常是由一个很长的任务路径组成的,比如,创建一个虚拟机,需要经过身份验证服务à调度器à镜像服务à存储服务à网络服务à虚拟机管理程序,每一个服务可能会花费几秒甚至几分钟去引导外部硬件完成一些操作,这极大的延长了任务执行的时间。

同步和异步

传统的IaaS软件使用同步的方式执行任务,他们通常给每一个任务安排一个线程,这个线程只有在之前的任务执行完毕时才会开始执行下一个任务。因为任务执行缓慢,当达到一个任务并发的高峰时,系统会因为线程池容量不足,运行非常缓慢,新来的任务只能被放在队列中等待被执行。

为了解决这个问题,一个直观的想法是提高线程池容量,但是这个想法在实际中是不可行的,即使现代操作系统允许一个应用程序拥有成千上万条线程,没有操作系统可以非常有效率的调度他们。随后有一个想法是把线程分发出去,让不同的操作系统上相似的软件分布式的处理线程,因为每一个软件都有它自己的线程池,这样最终增加了整个系统的线程容量。然而,分发会带来一定的开销,它增加了管理的复杂度,同时集群软件在软件设计层面依旧是一个挑战。最后,IaaS软件自身变成云的瓶颈,而其他的基础设施包括数据库,消息代理和外部的系统(比如成千台物理服务器)都足够去处理更多的并发任务。

ZStack通过异步架构来解决这个问题,如果我们把目光投向IaaS软件和数据中心的设备之间的关系,我们会发现IaaS软件实际上扮演着一个协调者的角色,它负责协调外部系统但并不做任何真正耗时的操作。举个例子,存储系统可以分配磁盘容量,镜像系统可以下载镜像模板,虚拟机管理程序可以创建虚拟机。IaaS软件所做的工作是做决策然后把子任务分配给不同的外部系统。比如,对于KVM,KVM主机需要执行诸如准备磁盘、准备网络、创建虚拟机等子任务。创建一台虚拟机可能需要花费5s,IaaS软件花费时间为0.5s,剩下的4.5s被KVM主机占用,ZStack的异步架构使IaaS管理软件不用等待4.5s,它只需要花费0.5s的时间选择让哪一台主机处理这个任务,然后把任务分派给那个主机。一旦主机完成了它的任务,它将结果通知给IaaS软件。通过异步架构,一个只有100条线程容量的线程池可以处理上千数的并发任务。

ZStack的异步方法

异步操作在计算机科学中是非常常见的操作,异步I/O,AJAX等都是一些众所周知的例子。然而,把所有的业务逻辑都建立在异步操作的基础上,尤其是对于IaaS这种非常典型的集成软件,是存在很多挑战的。

最大的挑战是必须让所有组件都异步,并不只是一部分组件异步。举个例子,如果你在其他服务都是同步的条件下,建立一个异步的存储服务,整个系统性能并不会提升。因为在异步的调用存储服务时,调用的服务自身如果是同步的,那么调用的服务必须等待存储服务完成,才能进行下一步操作,这会使得整个工作流依旧是处于同步状态。

(一个执行业务流程服务的线程同步调用了存储服务,然而存储服务和外部存储系统是异步通信的,因此它依旧需要等到存储服务返回之后,才能往下执行,这里的异步就等同于同步了。)

ZStack的异步架构包含了三个模块:异步消息,异步方法,异步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 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 */

}

}

更甚,以设定的并行度发送一列消息也是可以实现的。也就是说,含有10条消息的消息列表,可以每次发送2条消息,也就是说第3、4条消息可以在第1、2条消息的回复收到后被发送。

final List msgs = new ArrayList(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 completion) { CreateApplianceVmJob job = new CreateApplianceVmJob(); job.setSpec(spec); if (!spec.isSyncCreate()) { job.run(new ReturnValueCompletion(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使用一组agent去管理外部系统,比如:管理KVM主机的agent,管理控制台代理的agent,管理虚拟路由的agent等,这些agents全部都是搭建在Python CherryPy上的轻量级web服务器。因为如果没有HTML5技术,如websockets技术,是没有办法进行双向通信的,ZStack每个请求的HTTP头部嵌入一个回调的URL,因此,在任务完成后,agents可以发送回复给调用者的URL。

RefreshFirewallCmd cmd = new RefreshFirewallCmd();

List tos = new RuleCombiner().merge();

cmd.setRules(tos);

resf.asyncJsonPost(buildUrl(ApplianceVmConstant.REFRESH_FIREWALL_PATH), cmd, new
JsonAsyncRESTCallback(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已经建立了一个可以使所有组件都异步进行操作的全局架构。

总结

为了解决由缓慢且并发的任务引起的IaaS软件可拓展性受限的问题,我们演示了ZStack的异步架构。我们使用模拟器进行测试后发现,一个具有1000条线程的ZStack管理节点可以轻松处理创建100万台虚拟机时产生的10000个并发任务。虽然单一管理节点的拓展性已经可以满足大多数云的负载需要,考虑到系统需要高可用性以及承受巨大的负载量(10万个并发任务),我们需要一组管理节点来满足这些需求,如需了解ZStack的无状态服务,请阅读下一篇“ZStack可拓展性秘密武器2:无状态服务”。

猜你喜欢

转载自blog.csdn.net/zstack_org/article/details/86622183
今日推荐