我们用一张图开始这章的内容。
在上图中,management node
作为一个独立的进程是所有微服务的容器,用于管理微服务的生命周期,提供统一的服务监控和HA(高可靠)等功能。图中的每一个深蓝色方块都是一个独立的服务,根据功能的不同,在逻辑上归为:计算服务、存储服务、网络服务以及其它服务。虽然运行在同一进程中,所有服务在代码逻辑上仍然高度松耦合,服务之间通过外部消息总线通信,跟运行在独立进程中的微服务类似。
在传统微服务架构中,诸如“API Gateway”、“Self Registration”、“Client-side Discovery”、“Server-side Discovery”这样的功能在ZStack的微服务架构中都能找到,有些实现的还更为简单精巧。让人头疼的监控、高可靠、配置等诸多问题则不复存在,一切都由管理节点进程代为处理,开发人员只需要专心实现服务的业务逻辑即可。
我曾在多次技术交流中给国内主流公有云、互联网公司的技术团队讲解这个设计,收获的最多的一个问题是:如果某个服务存在性能瓶颈,在这个架构中无法对单一服务进行横向扩展。凑巧的是,提出这个问题的朋友都是来自OpenStack技术团队,可见“能够对单服务横向扩展”是OpenStack宣称的一个架构优势。
是的,在这个架构中,进程一旦启动就会加载所有服务,你不能选择性的加载一部分服务,要么没有,要么全有。你也不能对特定服务进行单独横向扩展,只能横向扩展管理节点进程,这样所有的服务都会获得相同程度的扩展。这听起来似乎不灵活也不合理,我已经听见你在心里这么说了。在后面的章节中,你会发现在ZStack架构中,运行特定服务和横向扩张特定服务其实都可以通过更改XML配置实现,但我不建议用户这么做,原因如下:
任何软件都存在一组核心模块/服务,缺少任何一个都会导致整个系统不工作,IaaS亦然。目前为止,ZStack绝大多数服务都是核心服务,例如虚拟机服务、物理机服务、主存储服务、镜像服务等;一些非核心服务,例如搜索服务,虽然不加载也不会影响整体功能,但会极大的损伤用户体验,甚至导致某些外围组件(例如UI)不工作。这些服务缺一不可,单独部署除了增加运维的复杂度外,并不能带来任何好处。
永远不要低估微服务集群的运维复杂度,OpenStack就是是个鲜明的例子。
当某个服务存在热点时,只需要扩展多个管理节点就能产生多份该服务的实例用于分担压力。虽然扩展节点也会同时生成其它服务的实例,但它们不会占用任何资源(如果服务不活跃),这后面的线程模型一节会介绍。也就是说,新增加的管理节点资源可以只被忙碌的服务消耗,其它服务虽然拥有实例,但并不做任何事情。不用担心会产生[Starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science)问题,在后面的章节你会看到ZStack是全异步架构,没有资源会被阻塞忙等待。
还记得在上一章中我们为ZStack设定的架构目标吗?要能够单节点管理数万物理机、百万级虚拟机,同时响应数万并发API,我相信这个性能指标世界上绝大多数公有云做不到。所以如果你是私有云和混合云,排除高可靠的因素,你可能永远不需要部署第二个管理节点。即使你是公有云,两个管理节点或许永远满足你对单套环境性能要求。既然如此,干嘛还纠结要横向扩展某个特定服务?
进程内微服务架构只是仿造微服务架构来解耦合代码,实现业务逻辑的高度模块化和自治化,但它并不是真正的微服务架构,它们之间最大的区别是:微服务架构是动态的,而进程内微服务架构是静态的。
微服务架构中的服务实例通常运行在虚拟机或容器之中。实例的数量会根据系统负载情况动态变化,弹性扩展。大量的服务实例的生命周期可能是短暂的,在负载降低时会被销毁,在负载上升时又再次被创建。与之相比,进程内微服务架构中的服务实例是静态的,它们的生命周期和数量都与管理节点进程绑定,一旦创建就一直运行。而IaaS的特性又决定管理员通常会预先创建足够多的管理节点来应对可能的负载,服务实例的数量很少会动态调整,即使有,规模也是非常小的。
这种动与静的区别导致两种架构管理服务的方式非常不同,相比之下,进程内微服务架构更为简单直接,而简单带来稳定。
从动态和静态的角度,进程内微服务架构更类似于传统的分布式SOA系统,服务实例运行在固定的位置(IP、端口),实例数量变动不频繁,服务间预先知道对方信息并能容易的进行相互调用。
很多人在见到进程内微服务架构后的第一发印象是:每个服务有一个独立的线程循环处理请求,有活就干,没活就sleep等待。所以他们的接下来的问题就是:如果这个服务线程崩溃了怎么办?
产生这种想法多是受传统微服务架构的影响,因为在这种架构中,每个服务实例是运行在单独进程中的,必须有HA机制来保证服务挂掉后能自动恢复。于是在看到进程内微服务架构时,他们把进程映射想象成了线程。
实际上,在进程内微服务架构中,除了管理节点的心跳线程,没有任何一个服务独占线程,相反,它们共享同一个线程池,如下图所示:
在不工作的时候,服务只是躺在进程地址空间内的代码,除了代码段和数据结构所占用的内存外,不占用任何资源。当一个任务发生时(通常被一个消息或一个HTTP请求触发),对应的服务才会从线程池中获取一个线程执行业务逻辑。当一切完成后,线程被归还给线程池,准备响应下一个服务。所以服务不会崩溃,它们只会在需要的时候被触发,按需向线程池申请资源。
当然,管理节点进程是可能崩溃的,这时所有的服务同时崩溃,整个系统不再工作,除非部署了多个管理节点做高可靠。
为什么心跳享有独立的线程
管理节点的心跳线程用于周期性的更新数据库以告知其它管理节点它还健康的活着。为了避免在一个繁忙系统中,心跳代码不能按时从线程池中获取线程,造成其它节点误判该节点已经死亡而导致错误的接管,我们为心跳分配了独占线程以排除系统的干扰。除此之外,心跳线程还享有独立的数据库连接,原因同上。
在这一章中,我们介绍了ZStack进程内微服务架构的总体设计,以及与传统微服务架构比较的一些异同点。需要再次强调的是,ZStack的设计目标是一款能够被成千上万家企业使用的软件产品,我们期望所有的用户都可以自己部署运维私有云、混合云。纯粹的微服务架构并不适合用于打造软件产品,前期门槛和后期运维成本都太高。采用单一进程 + 微服务的方式,是ZStack解决IaaS软件部署运维困难的第一个努力。