架构解密从分布式到微服务:基于ZeroCIce微服务架构指南

基于ZeroC Ice的微服务架构指南

微服务架构的概念、术语及其思想都不复杂,复杂的是具体实践过程中所涉及的框架、产品、API及配套的相关开发和运维工具。因此,本章先基于ZeroC Ice的微服务架构实践一个具体的微服务项目开发全过程,有了这个实践过程,在下一章继续学习微服务架构的相关理论时,我们就能更深刻、更直观地理解微服务架构了。

阅读本章之前,我们假设你已经熟悉ZeroC Ice的基本概念和基本用法。

ZeroC lce的前世今生

ZeroC Ice对于很多资深软件工程师或架构师来说并不陌生,特别是对于在电信领域有多年基础开发经验的IT人来说,ZeroC lce是一个很好很强大的RPC架构,腾讯很早就研究(使用)过ZeroCIce,其之后开源的RPC架构Tars在设计理念和实现方面也参考和借鉴了ZeroC Ice的很多方面。

通过前面章节的学习,我们知道,在早期的分布式系统中,RPC技术(框架)是关键的热点技术之一,而高性能、支持多语言跨平台开发的超级RPC架构一直是其终极目标,最早进多语言跨平台尝试的RPC技术是出师未捷身先死的CORBA。

CORBA出现于1991年,是当时几个IT巨头联手发起的支持多语言跨平台开发的超级分布式中间件平台,一度是学院派的“阵地”及商业巨头兜售自己的企业级产品的重要棋子,而在SUN的J2EE 和微软的 DOM 技术兴起后,CORBA因为缺乏企业市场而快速消亡。虽然CORBA已死,但它留下来很多影响深远的珍贵资产,包括SUN的J2EE、微软的DOM技术、后面IBM及微软联手发起的Web Service技术及留存至今的ZeroC Ice,都是在其直接或间接影响下出现的。

ZeroC lce可以说是完美继承了CORBA 的使命和精华,首次实现了支持多语言可跨平台的梦想。同时,相对于CORBA 的指数级复杂程度,ZeroC Ice可谓是很轻量级的平台,它剥离了很多华而不实的功能特性,很容易上手。转眼20多年过去了,无数知名软件公司消失了,甚至SUN这样的巨头也消失了,但仅凭借一个ZeroC lce产品就能延续至今,最大的功劳应当归当年勇敢“反叛”CORBA 的技术老兵,他们组建了ZeroC公司,打出“反叛之冰”的旗号,坚持CORBA 的初心和梦想,潜心打造了一个跨平台的RPC产品,在电信、金融等高端行业吸引了不少客户,并且几十年如一日地坚守代码界的工匠精神,不断优化和扩展ZeroC Ice,如今又彻底开源了其代码,不管是这家公司的精神还是其产品和源码本身,都值得我们学习。

ZeroC公司的Ice产品目前是一个系列,国内大部分人只熟悉它的RPC架构和产品部分,以C++的技术人员为主,这部分技术也是最早用于电信和金融等行业的,高性能和稳定性是它的两大口碑。不过ZeroC Ice的精华却是2005年左右发布的IceGrid,可被认为是第一个公开发行的、支持多语言的、功能完备的微服务架构平台,比之后的各种微服务架构要早很多年。至于为什么没有用微服务(Micro Service)这个词,是因为那时候IT界更流行另一个词——网格计算(Grid Computing),这个词诞生于20世纪90年代,其目标是把大量机器整合成一个虚拟的超级机器以支持超大规模的分布式计算,我们也可以将其理解为现在云计算 (CloudComputing)的前身,所以ZeroC公司以Grid命名其最新的分布式计算框架产品为IceGrid,也就再正常不过了。

从 Kubernetes、Docker Swarm这类最新的基于容器技术的分布式计算平台来看,IceGrid当年所设计的架构、实现方式、运维工具都已经深刻影响许多的后来者,因为跨越的时间很久了,所以我们不能简单地说是“抄袭或模仿”,我们只能说“英雄所见略同”,站在巨人的肩膀上,牛顿才能发现苹果闪耀的不为世人所知的光芒。

如下所示是IceGrid及Kubernetes 与 Docker Swarm的架构图。

首先,整个服务器集群中的机器被分为两类:Master 及 Worker,其中 Master 只有一台或少量几台,为整个集群的控制中心,上面部署着一些控制器类的软件,这些软件与每台Worker上运行的Agent类软件双向交互,在Master机器上可能还部署着一个Registry进程,存储了整个系统的数据,包括集群的信息,状态、已发布的应用的信息,各类服务的地址,以及可用性等信息,甚至包括集群节点采集报告的机器性能信息。我们要发布的应用则采用某种格式的文本文件(如xml、json、yaml)描述架构信息,其中架构被标准化建模为Service 为主的逻辑对象,然后通过Master 提供的工具自动发布到 Worker集群中,大大降低了运维的复杂度和工作量,而 Kubernetes等新一代的集群系统由于采用了容器技术,大大增强了平台的自动化能力,具体表现在应用发布、自我监控和自动修复、自动扩容、滚动升级方面,可以说是一个革命性的进步。

ZeroC lce微服务架构指南

前面我们说过,虽然ZeroC lceGrid (简称IceGrid)是早于微服务架构概念出现的,但IceGrid完全符合微服务架构的理念,当我们采用IceGrid来实施一个具体的微服务架构时,涉及的知识点如下图所示。

整个IceGrid集群由客户端程序、服务注册表(Ice Registry)及多个Ice Node组成。构建于IceGrid 上的微服务系统可以由一个XML文件描述,通过命令行工具icegridadmin可以完成应用的部署、升级、服务启停等管理功能。Ice Node进程运行在IceGrid集群中的每个物理机上,负责启停和监控本机上的所有IceBox,每个IceBox 都是单独的进程,如果是用Java开发的IceBox,其入口类就是 IceBox.Server。我们也可以把IceBox理解为极其轻量级的一个ServletServer,跑在IceBox中的每个Service(对应接口为IceBox.Service)则类似于一个Servlet,这一点从IceBox.Service接口的定义即可看出:

public abstract interface IceBox.Service{
public abstract void start(String serviceName,Ice.Communicator commu,
java. lang.String[] args);
public abstract void stop();
}

上述接口中的start方法用来让我们创建一个具体的RPC远程服务对象——Ice Servant,将它绑定到网络通信组件 Communicator 上并开始提供服务;stop方法则用来销毁和停止 IceServant对象,释放资源。当我们通过管理命令行来重启一个IceBox时,就会触发这个IceBox里所有Service的 stop 与 start方法。虽然在一个IceBox.Service里可以创建多个Ice Servant,但通常情况下我们只会创建一个Ice Servant,这种做法比较符合微服务架构的设计理念,即一个微服务为一个单独的进程。如果我们遵循了上述原则,则可以认为一个IceBox就是一个具体的微服务实例,部署在多个Ice Node上的同一组IceBox就组成了一个微服务的所有实例。

那么,这一组实例是如何实现微服务架构中的另外一个重要特性即负载均衡机制的呢?答案很简单——服务别名机制。DNS负载均衡机制其实也是这个原理,即我们把一组P绑定到一起,给一个别名——域名,每次查询时返回不同的IP地址即可实现简单的负载均衡能力。在IceGrid里采用了一个名为replica-group 的负载均衡机制,如下所示的代码片段定义了一个名为MyServiceRep的服务负载均衡组,采用的是round-robin的简单轮询机制,里面定义了一个名为MyService 的服务,它对应的接口类型为::demo::MyService的 Ice Servant实例。

<replica-group id="MyServiceRep">
<load-balancing type="round-robin" n-replicas="0"/>
<object identity="MyService" type=" ;:demo::MyService"/></replica-group>

然后,IceBox里对应的Service引用这个replica-group,即可完成捆绑过程:

<service name="MyService" entry=" XxXXXXX">
<adapter name="MyService" id="MyService${id)" endpoints="default"replica-group="MyServiceRep" />
</service>

这样一来,当客户端通过Ice Registry查找 MyService这个服务时,就会查询到当前所有可用的服务实例,从而实现透明的负载均衡和故障恢复等高级特性,这一切都在ZeroC Ice客户端的框架代码里实现了,我们直接使用即可。

如果熟悉 Docker,那么你可能发现 Docker仓库是一种很不错的设计,它实现了对二进制分发镜像的集中托管,而且具有类似于Git和 SVN的版本控制特性。安装了Docker运行环境的任何机器,都可以非常方便地从Docker仓库中拉取任何需要的镜像,而不是像之前采用传统的方式时,需要通过FTP或者 HTTP来手动下载并复制到各个目标机器上进行安装。Docker的这种新思路的确解决了分布式系统中服务部署和升级的一个大问题。

那么,ZeroC Ice微服务架构是否也可能引入这种思路来解决服务包的分发问题呢?答案是肯定的。我们知道,Java里的类加载器URLClassLoader是可以从网络上加载一个JAR文件的。下面这段代码就实现了从一个HTTP或者FTP站点加载一些指定的JAR文件:

private URLClassLoader getURLClassLoader(String jarSite, String jarNames) throwsMalformedURLException{
URLClassLoader loader =null;
String fileNames[]- jarNames.split(";");
if(fileNames != null && fileNames. length >0)(
URL urls[] = new URL[fileNames.length];
for (int i=0;i<fileNames.length; i++){
try {
urls[i] =new URL(jarSite + "/"+ fileNames[i]);]catch (MalformedURLException e)(
throw new RuntimeException ( "bad url",e);
}
}
loader - new URLClassLoader (urls,
Thread.currentThread().getContextClassLoader()I;
}
return loader;
}

此外,我们在存放用户微服务JAR文件的 HTTP站点上生成一个JSON格式的文件version.json,里面定义了当前要加载的JAR文件的版本信息,当我们要升级某个微服务时,只要上传新的JAR文件到站点下,并修改 version.json里对应的JAR文件的版本信息,然后让每个节点上的IceBox重新启动,就可完成快速的升级过程,而一旦升级失败,通过还原version.json文件,还能快速、安全地回滚。下面的示意图给出了完整的实现思路,图中的HTTP Server就相当于Docker Hub。

如上所示示意图中的代码已被提交到GitHub开源,代码可以用于生产、开发,项目名为mycat-ice-framework,从GitHub的 MyCATApache/mycat-ice目录即可找到,由于代码比较多,所以这里不再贴出,需要使用此框架的读者可以自行下载和研究,也欢迎提交优质的开源代码。

下面这段来自 grid.xml中的LoadJarProps 配置给出了远程加载JAR文件相关的配置参数:

<properties id="LoadJarProps">
<property name="LoadJarsFromRemote.Enabled" value="true"/><property name="LoadJarsFromRemote.site"
value="http://localhost:8080/ice-app-lib"></property>
<property name="LoadJarsFromRemote.SharedJars" value="iceclient.properties">
</property>
<property name="LoadJarsFromRemote.AutoUpdate" value="true"></property>
</properties>

而GenIceBoxService类是IceBox.Service的一个通用实现类,可以在grid.xml里通过配置方式加载(本地或远程)指定的一个Ice Servant对象,如此一来,开发IceGrid微服务架构时,就不用再为每个Ice Servant对象都生成一个IceBox.Service适配类了,大大提高了开发效率。下面的配置信息表示加载com.my.demo.MyServiceImpl这个Ice Servant,对应的远程JAR文件的名称为helloservice-xxx.jar,其中xxx等版本信息则来源于HTTP站点里的version.json:

<service name="MyService" entry="io.mycat.ice.server.GenIceBoxService">
<properties>
<properties refid="LoadJarProps"/>
<property name="servantClassName" value="com.my.demo.MyServiceImpl"/>
<property name="myjars" value="helloservice"/>
<property name="jdbe_url" value="jdbe://mysql:localhost" /></properties>
</ service>

接着我们一起分析微服务架构下的第2个有代表性的问题,即在微服务架构下是否还需要Spring Framework?我们知道,Spring Framework最初是代替复杂的J2EE框架的一个开源项目,提供了一个非常灵活也没有侵入性的服务容器框架。通过Spring Framework,我们很容易实现复杂的服务依赖和服务装配,在传统单体架构下,它的确是无法被代替的一个最佳开发框架。但是,在微服务架构里,我们的每个微服务都聚集于一件事情,微服务之间的调用(依赖)不再是本地(一个JVM 内部)调用模式,而是需要通过负载均衡机制来实现分布式的调用能力,如此一来,采用Spring Framework 来开发微服务,其优势就基本上丧失殆尽,反而增加了微服务的响应延时和代码的复杂度。如果不用Spring Framework,那么与此紧密相关的另外一个问题也随之而来,即 MyBatis、Hibernate等数据映射框架是否还能继续用于微服务开发?这个问题的正确答案不是特别确定,但有不少人倾向于不再用这些框架开发微服务系统,理由如下。

  • 微服务系统通常很少是传统企业应用,多为互联网应用,可能系统中的表很多,但不是每个表都有CRUD的操作,更多的是查询操作,因此,MyBatis、Hibernate等框架的优势并不明显,考虑到性能问题、复杂的数据库读写分离问题、SQL优化问题,采用原生JDBC操作是最佳选择。
  • 微服务是系统中的重要骨干,开发速度相对于微服务的实现品质来说并不重要,而且微服务的开发任务通常是由公司里少量比较有经验的工程师来承接的,对于他们来说,采用原生JDBC来实现业务代码,更能体现其技术水平和经验。

笔者的建议也是不采用Spring Framework 及 MyBatis、Hibernate等框架,而是直接采用原生JDBC来实现微服务的业务代码。

本节最后,我们来看看微服务架构系统中无法绕开的一个基础问题——集中化的配置中心。由于一个微服务通常有很多实例部署在不同的机器上,而一个微服务系统又是由很多微服务组成的庞大系统,所以在这种情况下,如果服务的配置信息都是以本地文件的方式打包在部署包里发布的,那么对于运维工程师来说将是可怕的梦魇。

要解决集中化的配置问题其实并不难,而且已经有现成的开源利器ZooKeeper可以完美解决配置信息动态实时推送的复杂难题。但是在这方面并没有好的开源项目可以很方便地拿来使用,而且这些开源项目最好用起来就像我们非常熟悉的Properties对象。为了解决这个问题,在mycat-ice-framework里,笔者提供了一个简单的框架,如下所示。

通常我们会以一个微服务为单元,将其所有的配置项作为一个Group统一进行管理,这就是上图中的 PropertiesHolder对象。PropertiesHolder 内部采用Map来存储具体的配置项。为了解决部分配置项需要在运行期间改变的问题,这里采用了简单的回调思路,使用者实现一个PropUpdatedHandler接口来处理配置项改变时需要执行的业务逻辑,并在获取配置项的方法调用中传递这个接口,一旦此配置项发生改变,即可被通知到。下面是 PropUpdatedHandler的接口定义:

public interface PropUpdatedHandler {
public void valueChanged (String propName,String oldval,String newValue);
}

PropertiesHolder 的完整代码也很简单:

public class PropertiesHolder {
private Map<String,string> propMap = new HashMap<String,String> ();private Map<string,PropUpdatedHandler> propUpdatedHandlerMap = new
HashMap<string, PropUpdatedHandler>(;
public string getPropvalue(String propName){
return propMap.get (propName) ;
}
/★*
*对于运行期变化的配置变量,传入回调接口,在变量发生变化后,被及时通知修改赋值*/
public String getPropvalue(String propName,PropUpdatedHandler
updatedHandler) {
if (updatedHandler != null) {
synchronized (propUpdatedHandlerMap) {
propUpdatedHandlerMap. put (propName, updatedHandler) ;
}
}
return getPropvalue(propName);
}
}

PropertiesHolder则由具体的PropertiesStore 实现类所托管,PropertiesStore的接口如下:

public abstract class Propertiesstore {
/**
大获取某个资源所对应的配置变量集(PropertiesHolder),这里的资源主要指Ice服务,★通常一个服务对应一套PropertiesHolder.
大如果对应的PropertiesHolder不存在,则装载defaultphId对应的 PropertiesHolder,*如果还不存在,则返回空的PropertiesHolder
★
*@param phId
*param defaultphId*@return
*/
public abstract PropertiesHolder loadPropertiesHolder(String phId,String
defaultphld);
/**
*持久化保存配置变量
*
★Cparam ph*/
public abstract void updatePropertiesHolder (PropertiesHolder ph);
}

我们可以分别实现基于Properties文件、数据库、ZooKeeper 的各种 PropertiesStore实现,最终通过ConfigTool来实例化一个PropertiesStore,并提供静态方法供客户端程序获取指定的PropertiesHolder对象,ConfigTool的代码如下:

public class ConfigTool {
static final PropertiesStore propStore;static i
propStore = new LocalPropertiesStore();
}
public static PropertiesHolder getPropertiesHolder (String phld,string
defaultphrd) {
return propstore.loadPropertiesHolder(phld, defaultphId);
}:

客户端使用此框架就非常简单了,下面是全部的代码:

PropertiesHolder ph=ConfigTool.getPropertiesHolder ("MyHellowService" ,null);string paramxx=ph. getPropvalue("paramxxx");

此框架目前只提供了接口定义,还没有提供具体的实现代码,也希望高手能实现基于数据库与ZooKeeper的 PropertiesStore并贡献到开源项目中。

微服务架构概述

考虑到第8章才会完整介绍微服务相关的内容,所以这里先简单概述,以巩固上一节的实践成果。

说到微服务架构,我们先来看看与之“对立”的一种程序架构——单体架构,如下图所示,这个采用REST 接口定义了多个Service并且完全运行在一个Tomcat 里的应用就是我们很熟悉的单体架构。

单体架构的好处显而易见:通常只建立一个 Project 工程即可,当系统比较小时,开发、部署、测试等工作都更加简单快捷,容易实现项目上线的目标。但随着系统的快速迭代,就会产生一些难以调和的矛盾和发现先天的缺陷。

  • 过高耦合的风险:服务越来越多,不停地变化,由于都在一个进程中,所以一个服务的失败或移除,都将导致整个系统无法启动或正常运行的系统性风险越来越大。
  • 新语言与新技术引入的阻力:单体架构通常只使用一种开发语言,并且完全使用一种特定的框架,运行在一个进程内,从而导致新语言和新技术很难被引入。在互联网应用时代,多语言协作开发是主流,特别是对于复杂的大系统、大平台。各种新技术层出不穷,拒绝新技术就意味着技术上的落后,从而可能逐步被市场抛弃。
  • 水平扩展的问题:单体架构从一开始就没有考虑分布式问题,或者即使考虑了但仍然开发为单体架构,所以遇到单机性能问题时,通常难以水平扩展,往往需要推倒重来,代价比较大。
  • 难以可持续发展:随着业务范围的快速拓展,单体架构通常难以复用原有的服务,一个新业务的上线,通常需要重新开发新服务、新接口,整个团队长期被迫加班是必然的结果,老板则怀疑技术团队及Leader 的能力。

下图则是Amazon EC2 PaaS平台CloudFoundry 的创始人Chris Richardson在 Introduction toMicroservices中提到的一个典型单体架构案例,这是一款类似于滴滴打车的出租车调度软件(不妨称之为哥哥打车),整个系统(不含面向司机与乘客的手机客户端)被打包为一个大的单体架构,部署在Tomcat里。

而微服务架构的出现,恰恰就是为了弥补“单体架构”所带来的各种问题及先天的缺陷。最早提出微服务架构及实践微服务架构的公司有Google、Amazon、eBay和NetFlix等。在这种新的微服务架构下,整个系统被分解为独立的几个微服务,每个微服务都可以独立部署在多台机器上,前端应用可以通过负载均衡器(Load Balancer)来访问微服务,微服务之间也可以通过同样的接口进行远程通信。

如下所示为“哥哥打车”微服务化后的架构图(其中每个六边形都代表一个微服务)。

如下所示为Load Balancer实现微服务负载均衡的部署示意图(基于亚马逊公有云和Docker)。

一般而言,如果一个分布式系统具备如下特点,则我们可以称之为“微服务架构”。

任何一个服务都由多个独立的进程提供服务,这些进程可以分布在多台物理机上,任何进程宕机,都不会影响系统提供服务。

整个系统是由多个微服务有机组成的一个分布式系统,换而言之,不是一个巨大的单体架构。

如下所示为微服务架构的原理概念图,可以看出,微服务架构从某种意义上来说可以被看作服务治理框架的延伸。

微服务架构背后的思想和顶层设计无疑是一个巨大的进步。我们说了这么多年面向服务设计与建模的梦想,终于在分布式时代被第一次真正重视和彻底实现,而这一切都源自全新的微服务架构。

总结下来,当前主流的微服务架构可以分为以下三类。

  • 第1类:基于传统的高性能RPC技术和服务治理的微服务架构,这个领域的王者为ZeroC IceGrid。
  • 第2类:以HTTP REST为通信机制的通用性微服务架构,典型的为Spring Cloud。
  • 第3类:基于容器的技术,目标是部署在公有云平台上的微服务架构基础平台,这种微服务架构平台并没有提供特定的RPC通信机制,只保证TCP通信的可达性,所以理论上,任何分布式应用都可以运行在微服务架构平台上,言外之意就是要选择合适的通信协议,比如REST、Thrift、gRPC或者自定义的某种TCP通信协议。这个领域的王者为Google的 Kubernetes,本文后面会提到它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值