ZStack:进程内微服务

为了应对诸如惊人的操做开销、重复的努力、可测试性等微服务一般面临的挑战,以及得到诸如代码解耦,易于横向扩展等微服务带来的好处,ZStack将全部服务包含在单个进程中,称为管理节点,构建一个进程内的微服务架构。java

动机

构建一个IaaS软件是很难的,这是一个已经从市场上现存的IaaS软件得到的教训。做为一个集成软件,IaaS软件一般须要去管理复杂的各类各样的子系统(如:虚拟机管理器hypervisor,存储,网络,身份验证等)而且须要组织协调多个子系统间的交互。例如,建立虚拟机操做将涉及到虚拟机管理模块,存储模块,网络模块的合做。因为大多数IaaS软件一般对架构考虑不够全面就急于开始解决一个具体问题,它们的实现一般会演变成:node

随着一个软件的不断成长,这个铁板一块的架构(monolithic architecture)将最终变为一团乱麻,以致于没有人能够修改这个系统的代码,除非把整个系统从头构建。这种铁板一块的编程问题是微服务能够介入的完美场合。经过划分整个系统的功能为一个个小的、专注的、独立的服务,并定义服务之间交互的规则,微服务能够帮助转换一个复杂笨重的软件,从紧耦合的、网状拓扑架构,变成一个松耦合的、星状拓扑的架构。python

由于服务在微服务中是编译独立的,添加或者删除服务将不会影响整个系统的架构(固然,移除某些服务会致使功能的缺失)。 微服务远比咱们已经讨论的内容更多:微服务的确有不少引入注目的优势,尤为是在一个的开发运维 流程(DevOps process)中,当涉及到一个大机构的不少团队时。咱们不打算讨论微服务的全部支持和反对意见,咱们肯定你能够在网上找到大量的相关文章,咱们主要介绍一些咱们认为对IaaS软件影响深远的特性。web

问题

虽然微服务能够解耦合架构,但这是有代价的。阅读Microservices - Not A Free Lunch!和Failing at Microservices会对这句话有更深的理解。在这里,咱们重点强调一些咱们认为对IaaS软件影响重大的事情。spring

1. 难以定义服务的边界和重复作功

建立Microservices架构的挑战之一是决定应该把哪一部分的代码定义为服务,一些是很是明显的,好比说,处理主机部分的逻辑代码能够被定义为一个服务。然而,管理数据库交互的代码很是难以决定应不该该被定义为服务。数据库服务可使得整个架构更加清晰明了,可是这样会致使严重的性能降低。一般,相似于这样的代码能够被定义为库,库能够被各个服务调用。鉴于全部服务通常在互相隔离的目录下开发和维护,建立一个给不一样的单一的软件提供接口的虚拟的库,要求开发者必须具备良好的和各个不一样组的开发者沟通协调的能力。综上,服务很容易重复造轮子和致使没必要要的重复作功。数据库

2. 软件难以部署、升级和维护

服务,尤为是那些分散在不一样进程和机器上的,是难以部署和升级的。用户一般必须去花费几天甚至几周去部署一个完整的可运行的系统,并惧怕升级一个已经构建好的稳定的系统。尽管一些相似puppet的配置管理软件必定程度上缓解了这个问题,用户依旧须要克服陡峭的学习曲线去掌握这些配置工具,仅仅是为了部署或者升级一个软件。管理一个云是很是困难的,努力不该该被浪费在管理这些本来应该使生活更轻松的软件上。 服务的数量确实很重要:IaaS软件一般有许许多多的服务。拿著名的openstack举个例子,为了完成一个基础的安装你将须要:Nova, Cinder, Neutron, Horizon, Keystone, Glance。除了nova是在每台主机都须要部署的,若是你想要4个实例(instances),而且每一个服务运行在不一样机器上,你须要去操纵20台服务器。虽然这种人造的案例将不太可能真实地发生,它依旧揭示了管理相互隔离的服务的挑战。编程

3. 零散的配置

运行在不一样服务器上的服务,分别维护着它们散乱在系统各个角落的配置副本。在系统范围更新配置的操做一般由临时特定的脚本完成,这会致使由不一致的配置产生的使人费解的失败。设计模式

4. 额外的监控努力

为了跟踪系统的健康情况,用户必须付出额外的努力去监控每个服务实例。这些监控软件,要么由第三方工具搭建,要么服务自身维护,仍然受到和微服务面临的问题所相似的问题的困扰,由于它们仍然是以分布式的方式工做的软件。api

5. 插件杀手

插件这个词在微服务的世界中不多被听到,由于每一个服务都是运行在不一样进程中一个很小的功能单元(function unit);传统的插件模式(参考The Versatile Plugin System)目标是把不一样的功能单元相互挂在一块儿,这在微服务看来是不可能的,甚至是反设计模式的。然而,对于一些很天然的,要在功能单元间强加紧密依赖的业务逻辑,微服务可能会让事情变得很是糟糕,由于缺少插件支持,修改业务逻辑可能引起一连串服务的修改。服务器

全部的服务都在一个进程

意识到上述的全部问题,以及这么一个事实,即一个能够正常工做的IaaS软件必须和全部的编排服务一块儿运行以后,ZStack把全部服务封装在单一进程中,称之为管理节点。除去一些微服务已经带来的如解耦架构的优势外,进程内的微服务还给了咱们不少额外的好处:

1. 简洁的依赖

由于全部服务都运行在同一进程内,软件只须要一份支持软件(如:database library, message library)的拷贝;升级或改变支持库跟咱们对一个单独的二进制应用程序所作的同样简单。

2. 高可用,负载均衡和监控

服务能够专一于它们的业务逻辑,而不受各类来自于高可用、负载均衡、监控的干扰,这一切只由管理节点关心;更进一步,状态能够从服务中分离以建立无状态服务,详见ZStack's Scalability Secrets Part 2: Stateless Services。

3. 中心化的配置

因为在一个进程中,全部的服务共享一份配置文件——zstack.properties;用户不须要去管理各类各样的分散在不一样机器上的配置文件。

4. 易于部署、升级、维护和横向扩展

部署,升级或者维护一个单一的管理节点跟部署升级一个单一的应用程序同样容易。横向扩展服务只须要简单的增长管理节点。

5. 容许插件

由于运行在一个单一的进程中,插件能够很容易地被建立,和给传统的单进程应用程序添加插件同样。 进程内的微服务并非一个新发明: 早在90年代,微软在COM(Component Object Model)中把server定义为远程、本地和进程内三种。这些进程内的server是一些DLLs,被应用程序在同一进程空间内加载,属于进程内的微服务。Peter Kriens在四年前就声称已经定义了一种老是在同一进程内通讯的服务,OSGi µservices。 ##服务样例 在微服务中,一个服务一般是一个可重复的业务活动的逻辑表示,是无关联的、松耦合的、自包含的,并且对服务的消费者而言是一个“黑盒子”。简单来讲,一个传统的微服务一般只关心特定的业务逻辑,有本身的API和配置方法,并能像一个独立的应用程序同样运行。尽管ZStack的服务共享同一块进程空间,它们拥有这些特色中的绝大多数。ZStack很大程度上是一个使用强类型语言java编写的项目,可是在各个编排服务之间没有编译依赖性,例如:计算服务(包含VM服务、主机服务、区域服务、集群服务)并不依赖于存储服务(包含磁盘服务、基础存储服务、备份存储服务、磁盘快照服务等),虽然这些服务在业务流程中是紧密耦合的。 在源代码中,一个ZStack的服务并不比一个做为一个独立的jar文件构建的maven模块多任何东西。每个服务能够定义本身的APIs、错误码、全局配置,全局属性和系统标签。例如KVM的主机服务拥有本身的APIs(以下所示)和各类各样的容许用户本身定义配置的方式。

<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="http://zstack.org/schema/zstack">
    <id>host</id>
    <message>
        <name>org.zstack.kvm.APIAddKVMHostMsg</name>
        <interceptor>HostApiInterceptor</interceptor>
        <interceptor>KVMApiInterceptor</interceptor>
    </message>
</service>

##经过全局配置来配置

备注:这里只简单展现一小部分,用户可使用API去更新/获取全局配置,在这里展现一下全局配置的视图。

<?xml version="1.0" encoding="UTF-8"?>
<globalConfig xmlns="http://zstack.org/schema/zstack">
    <config>
        <category>kvm</category>
        <name>vm.migrationQuantity</name>
        <description>A value that defines how many vm can be migrated in parallel when putting a KVM host into maintenance mode.(当一个KVM主机变成维护模式的时候,这里的值定义了能够被并发迁移的虚拟机的数量)</description>
        <defaultValue>2</defaultValue>
        <type>java.lang.Integer</type>
    </config>

    <config>
        <category>kvm</category>
        <name>reservedMemory</name>
        <description>The memory capacity reserved on all KVM hosts. ZStack KVM agent is a python web server that needs some memory capacity to run. this value reserves a portion of memory for the agent as well as other host applications. The value can be overridden by system tag on individual host, cluster and zone level(全部的KVM主机预留的内存容量。ZStack中的KVM代理运行时是一个须要一部份内存容量去运行的python的web服务器,这个值为代理和其余主机应用程序预留了一部份内存,在单一主机上的、集群上的、区域上的系统标签能够覆盖这个值)</description>
        <defaultValue>512M</defaultValue>
    </config>
</globalConfig>

经过全局属性配置

备注:如下代码对应zstack.properties文件夹中相应的属性

@GlobalPropertyDefinition
public class KVMGlobalProperty {
    @GlobalProperty(name="KvmAgent.agentPackageName", defaultValue = "kvmagent-0.6.tar.gz")
    public static String AGENT_PACKAGE_NAME;
    @GlobalProperty(name="KvmAgent.agentUrlRootPath", defaultValue = "")
    public static String AGENT_URL_ROOT_PATH;
    @GlobalProperty(name="KvmAgent.agentUrlScheme", defaultValue = "http")
    public static String AGENT_URL_SCHEME;
}

##经过系统标签配置

备注:如下代码对应数据库中相应的系统标签。

@TagDefinition
public class KVMSystemTags {
    public static final String QEMU_IMG_VERSION_TOKEN = "version";
    public static PatternedSystemTag QEMU_IMG_VERSION = new PatternedSystemTag(String.format("qemu-img::version::%s", QEMU_IMG_VERSION_TOKEN), HostVO.class);

    public static final String LIBVIRT_VERSION_TOKEN = "version";
    public static PatternedSystemTag LIBVIRT_VERSION = new PatternedSystemTag(String.format("libvirt::version::%s", LIBVIRT_VERSION_TOKEN), HostVO.class);

    public static final String HVM_CPU_FLAG_TOKEN = "flag";
    public static PatternedSystemTag HVM_CPU_FLAG = new PatternedSystemTag(String.format("hvm::%s", HVM_CPU_FLAG_TOKEN), HostVO.class);
}

##载入服务 服务在Spring的bean的xml文件中声明自身,例如,kvm的部分声明相似于:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:zstack="http://zstack.org/schema/zstack"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://zstack.org/schema/zstack
    http://zstack.org/schema/zstack/plugin.xsd"
    default-init-method="init" default-destroy-method="destroy">

    <bean id="KvmHostReserveExtension" class="org.zstack.kvm.KvmHostReserveExtension">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.Component" />
            <zstack:extension interface="org.zstack.header.allocator.HostReservedCapacityExtensionPoint" />
        </zstack:plugin>
    </bean>

    <bean id="KVMHostFactory" class="org.zstack.kvm.KVMHostFactory">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.host.HypervisorFactory" />
            <zstack:extension interface="org.zstack.header.Component" />
            <zstack:extension interface="org.zstack.header.managementnode.ManagementNodeChangeListener" />
            <zstack:extension interface="org.zstack.header.volume.MaxDataVolumeNumberExtensionPoint" />
        </zstack:plugin>
    </bean>

    <bean id="KVMSecurityGroupBackend" class="org.zstack.kvm.KVMSecurityGroupBackend">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.network.securitygroup.SecurityGroupHypervisorBackend" />
            <zstack:extension interface="org.zstack.kvm.KVMHostConnectExtensionPoint" />
        </zstack:plugin>
    </bean>  

    <bean id="KVMConsoleHypervisorBackend" class="org.zstack.kvm.KVMConsoleHypervisorBackend">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.console.ConsoleHypervisorBackend"/>
        </zstack:plugin>
    </bean>  

    <bean id="KVMApiInterceptor" class="org.zstack.kvm.KVMApiInterceptor">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.apimediator.ApiMessageInterceptor"/>
        </zstack:plugin>
    </bean>  

管理节点,做为全部服务的容器,将在启动阶段读取它们的XML配置文件,载入每个服务。
# 总结
在这篇文章中,咱们演示了ZStack的进程内微服务架构。经过使用它,ZStack拥有一个很是干净的,松耦合的代码结构,这是建立一个强壮IaaS软件的基础。
相关文章
相关标签/搜索