每一个ZStack服务都是无状态的,让服务高可用以及横向拓展(scale out)能够很简单,只须要启动剩余的服务实例,而后进行负载均衡便可。此外,ZStack将全部的服务打包到名为管理节点(management node)的单个进程,它让部署和管理变得超级简单。java
在ZStack的伸缩性秘密武器——1、异步架构(ZStack's Scalability Secrets Part 1: Asynchronous Architecture)一文中, 咱们已经详细解释了异步架构,它让单个ZStack管理节点能胜任大多数的云端工做负载。然而,当用户但愿创建高可用的生产环境,或者处理超级大的并发工做负载的时候,一个管理节点是不够的。解决方案是,构建一个分布式的系统,这样工做负载能够延展到每个单一管理节点。这种增长新节点来拓展整个系统的容量的方式称为 横向拓展(scale out).node
设计一个分布式的系统并不容易。一个分布式的系统,特别是一个有状态的系统,必须处理一致性,可用性,以及分区容忍性(请查看 CAP理论(CAP theorem)),全部这些都很复杂。相反,一个无状态的分布式系统,在某种程度上摆脱了这种复杂性。首先,由于在节点之间无需状态共享,系统天然保持了一致性;其次,因为节点之间是相似的,当系统遇到一个分区问题一般也是OK的。鉴于此,一个分布式的系统,一般更倾向于保持无状态而不是有状态。可是,设计一个无状态的分布式系统也是很困难的,同时,经常比设计有状态的分布式系统更加困难。提高了消息总线(message bus)和数据库优点的ZStack,构建了一个包含了无状态服务的无状态分布式系统。算法
因为无状态服务是保证整个系统无状态的根基,在讨论它是什么以前,让咱们先了解下什么是“状态”。在ZStack里面,资源,如主机,虚拟机,镜像,以及用户,都是由单个服务管理的;当系统中存在多余一个服务实例的时候,资源会被划分为不一样的实例。例如,假若有10,000个虚拟机和两个虚拟机服务实例,理想的状况下,每一个实例将会管理5000个虚拟机:数据库
因为存在两个服务实例,在向虚拟机发送请求以前,请求者必须知道哪个实例正在管理虚拟机;不然,它将没法知道将请求发往何处。像 ”哪一个服务实例正在管理什么资源“ 的认知,正是咱们正在谈论的状态。若是服务是有状态的,状态也就显如今服务之中。请求者须要在某个地方咨询这些状态。当服务实例的数目发生变化的时候,服务须要交换状态,例如,当一个新的服务实例加入,或者当前的服务实例脱离的时候。api
状态交换是让人担心的,它很容易致使错误,经常会限制系统的可拓展性。为了让系统更可靠,同时更易于横向拓展,理想的方式是,经过彼此分隔状态来让服务保持无状态(查看 服务无状态原则(Service Statelessness Principle)。 有了无状态的服务,请求者再也不须要询问何处发送请求;当新的服务实例加入,或者旧的服务实例脱离的时候,服务也再也不须要交换状态。架构
注意:在接下来的内容中,为了简单起见,术语“服务”和“服务实例”交换着使用。并发
服务,经过中央消息总线(central message bus)--RabbitMQ,来彼此通信,它们是ZStack中的“第一等公民”。app
不像一般的微服务架构,其每一个服务都在单独的进程或单独的机器上运行,ZStack将全部的服务打包到一个名为管理节点的单一进程。对于这个号称 进程中的微服务(in-process microservices)架构,咱们有充分的理由,你能够参看进程中的微服务架构(The In-Process Microservices Architecture)。负载均衡
一个管理节点是一个完整功能的ZStack软件。因为包含了无状态服务,管理节点没有共享状态,可是有心跳记录,以及一致性哈希算法环(consistent hashing ring)--接下来咱们将详细介绍。 心跳用来监控管理节点的“健康”(译者注:即此管理节点是否存活,是否正常运转),只要一个管理节点在给定的间隔内中止更新心跳,其它的管理节点将会驱除它,同时开始接管它所管理的资源。less
实现无状态服务的核心技术,特别是对于ZStack的业务逻辑,就是一致性哈希算法(consistent hashing algorithm)。在启动的时候,每一个管理节点都会被分配一个 版本4UUID(version 4 UUID)(管理节点UUID),它会和服务名一块儿,在消息总线上注册一个服务队列。例如,管理节点可能注册以下所示的服务队列:
zstack.message.ansible.3694776ab31a45709259254a018913ca zstack.message.api.portal zstack.message.applianceVm.3694776ab31a45709259254a018913ca zstack.message.cloudbus.3694776ab31a45709259254a018913ca zstack.message.cluster.3694776ab31a45709259254a018913ca zstack.message.configuration.3694776ab31a45709259254a018913ca zstack.message.console.3694776ab31a45709259254a018913ca zstack.message.eip.3694776ab31a45709259254a018913ca zstack.message.globalConfig.3694776ab31a45709259254a018913ca zstack.message.host.3694776ab31a45709259254a018913ca zstack.message.host.allocator.3694776ab31a45709259254a018913ca zstack.message.identity.3694776ab31a45709259254a018913ca zstack.message.image.3694776ab31a45709259254a018913ca zstack.message.managementNode.3694776ab31a45709259254a018913ca zstack.message.network.l2.3694776ab31a45709259254a018913ca zstack.message.network.l2.vlan.3694776ab31a45709259254a018913ca zstack.message.network.l3.3694776ab31a45709259254a018913ca zstack.message.network.service.3694776ab31a45709259254a018913ca zstack.message.portForwarding.3694776ab31a45709259254a018913ca zstack.message.query.3694776ab31a45709259254a018913ca zstack.message.securityGroup.3694776ab31a45709259254a018913ca zstack.message.snapshot.volume.3694776ab31a45709259254a018913ca zstack.message.storage.backup.3694776ab31a45709259254a018913ca
说明:你应该注意到,全部队列都以一样的UUID结尾,那是管理节点的UUID。
资源,如主机,容量,虚拟机,也是经过UUID来标识的。消息,经常和资源相关联,是在服务间传递的。在发送消息以前,发送者必须选择基于资源的UUID的接收者服务,这时,一致性哈希算法就开始登场了。
一致性哈希(Consistent hashing)是一种特别的哈希,当哈希表调整大小的时候,就会用到一致性哈希,其中只有一部分键(key)须要从新映射。关于一致性哈希的更多内容,更详细的请参阅 这里。在ZStack之中,管理节点由一致性哈希环组成,以下所示:
每一个管理节点都维护一份一致性哈希环的拷贝,这个环包含了系统中全部管理节点的UUID。当管理节点加入或者脱离的时候,生命周期事件(lifecycle event)就会经过消息总线广播到其它节点,这样使得这些节点扩展或者收缩环,以呈现当前系统的状态。当发送消息的时候,发送者服务将使用资源的UUID,经过哈希的方式得出目标管理节点的UUID。例如,发送VM的UUID为932763162d054c04adaab6ab498c9139的StartVmInstanceMsg,伪代码以下:
msg = new StartVmInstanceMsg(); destinationManagementNodeUUID = consistent_hashing_algorithm("932763162d054c04adaab6ab498c9139"); msg.setServiceId("vmInstance." + destinationManagementNodeUUID); cloudBus.send(msg)
若是有一个稳定的环,那么包含一样资源UUID的消息就老是会路由到某个管理节点上一样的服务,这就是ZStack无锁架构的基础(参阅 ZStack的伸缩性秘密(第三部分):无锁架构(Stack's Scalability Secrets Part 3: Lock-free Architecture)。
当一致性哈希环收缩或释放的时候,因为一致性哈希的特性,只有少数节点受到轻微影响。
因为一致性哈希环,发送者无需知道哪个服务实例即将处理消息;取而代之的是,这将会被处理掉。服务无需维护和交换,关于它们正在管理什么资源的信息;它们所须要作的就是,处理即将到来的消息,由于环可以保证消息找到正确的服务实例。这就是服务如何变得超级简单和保持无状态的。
除包含资源UUID的消息以外(如 StartVmInstanceMsg, DownloadImageMsg),也有一类无资源UUID的消息,一般是建立型的消息(如 CreateVolumeMsg)和非资源消息(如 AllocateHostMsg)--它们不会操控单独的资源。考虑到这些消息能够发送到任意管理节点的服务,它们可能被故意发送到本地的管理节点,因为发送者和接收者在一样的节点,当发送者发送消息的时候,接收者固然也是可达的。
对 API 消息(例如:APIStartVmInstanceMsg)来讲,有一个特殊的处理,它们老是发送一个众所周知的服务 ID api.portal 。在消息总线上,一个全局的队列被叫作 zstack.message.api.portal ,它被全部的管理节点 API 服务所共享,消息服务 ID api.portal 将会自动对其中的一个API服务作负载均衡,这个服务还会路由转发消息到正确的目的地,并使用了一致性哈希环(consistent hashing ring)。经过这种作法,ZStack 隐藏了来自 API 客户端消息路由转发的细节,并简化了写一个ZStack API 客户端的工做。
msg = new APICreateVmInstanceMsg() msg.setServiceId("api.portal") cloudBus.send(msg)
在这篇文章中,咱们证实了Zstack 构建伸缩性的分布式系统。由于管理节点共享的信息比较少,很容易创建一个大的集群,可能有几十个甚至几百个管理节点。然而实际上,在私有云方面,两个管理节点能够有很好的扩展性;在公共云方面,管理员能根据工做量建立一个管理节点。依靠异步架构和无状态的服务,Zstack可以处理大量的并发任务,现有的IaaS软件则不能处理。