关于架构,笔者认为并不是越复杂越好,而是相反,简单就是硬道理也提现在这里。这也是微服务能够流行的原因,看看市场上曾经出现的服务架构:EJB、SCA、Dubbo等等,都比微服务先进,都比微服务功能完善,但它们都没有微服务这么深入民心,就是因为他们过于复杂。简单就是高科技,苹果手机据说专门有个团队研究如何能让用户更加简单的操作。大公司都是由小公司发展起来的,如果小公司在开始技术选型时感觉某个框架费时费力就不会选择,而小公司发展到大公司的过程,一般也伴随着系统不断优化的过程,而不断优化往往不会重新选择开发技术和框架,而是在原来基础改进,这也许就是简单框架流行的本质。
假设我们需要为超高业务量的保险代理企业设计一个“互联网+”保险平台。假设这家保险代理企业网上保险注册用户规模为2千万,门店及加盟商销售人员2万,年保单量2亿单(中国平安总用户规模达1.67亿,拥有超过79.8万名寿险销售人员和约24.6万名正式雇员。截至2015年6月30日,集团总资产达4.63万亿元,归属母公司股东权益为3,311.90亿元。而目前互联网保险领头羊众安保险,经营以小额贷款为主,由于背靠阿里巴巴,日保单销售量可达1亿,不过别人很难复制众安保险的模式)。因此我们取大型互联网企业和众安保险的折衷来考虑这个保险O2O平台。
参考保险业务相关文档(文档不全),获得如下核心需求矩阵(因为涉及功能太多,只取大的功能点)。
分类 |
功能 |
质量 |
约束 |
电子商务(B2C) |
产品展示(搜索、详情展示等) |
及时响应、安全性、健壮性、易用性 |
多种险种,处理方式可能不同 |
|
产品购买(提交订单、支付) |
及时响应、安全性、健壮性、易用性 |
多种险种,处理方式可能不同 |
|
用户中心(我的保单、我的理赔等) |
及时响应、安全性、健壮性、易用性 |
多种险种,处理方式可能不同 |
代理人管理(加盟商管理) |
车险投保(询价、录单、缴费) |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
|
非车险投保(询价、录单、缴费) |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
|
保单查询 |
及时响应、健壮性、可扩展性 |
|
|
单证管理 |
及时响应、健壮性、可扩展性 |
|
|
我的账户(我的保单、佣金结算等) |
及时响应、安全性、可靠性、易用性 |
多种险种,处理方式可能不同 |
案卷管理 |
案卷录入 |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
|
索赔资料收取 |
及时响应、健壮性、可扩展性 |
|
|
案卷交接 |
及时响应、健壮性、可扩展性 |
|
|
案卷跟踪 |
及时响应、健壮性、可扩展性 |
|
客户管理 |
客户信息维护 |
及时响应、健壮性、可扩展性 |
上传大量文件 |
|
客户活动管理 |
及时响应、健壮性、可扩展性 |
|
|
商机管理 |
及时响应、健壮性、可扩展性 |
|
|
我的工作台(消息、活动、商机) |
及时响应、健壮性、可扩展性 |
|
保险公估 |
车险定损过程跟踪协助 |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
|
人伤出险协助 |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
|
法律援助服务 |
及时响应、健壮性、可扩展性 |
多种险种,处理方式可能不同 |
从O2O的概念来看:“O2O即Online To Offline,也即将线下商务的机会与互联网结合在了一起,让互联网成为线下交易的前台。这样线下服务就可以用线上来揽客,消费者可以用线上来筛选服务,还有成交可以在线结算,很快达到规模。该模式最重要的特点是:推广效果可查,每笔交易可跟踪(百度百科)”,不是每一种服务都适合O2O。商品类的销售不适合O2O,因为你直接从网店就可以购买根本不需要线下店。但保险类服务的确需要线下和线上结合,如果是纯线上将不能满足客户的服务需求。O2O的线下服务可以是加盟商、代理人,也可以是直营店。
上面的需求是按照用户角度提出的,虽然使用“系统”一词,但这里的系统是一个抽象概念,可能包括软件系统以及人事、制度等在内。上面的需求可以分为三大类,一类是针对终端用户纯线上服务:电子商务网站(B2C);一类是专门针对代理人服务的:代理人系统;剩下的是一些公共服务,有可能电子商务网站和代理人系统都会用到的一些服务。另外保险业务最大的一个问题是多个险种,不同险种处理方式有很大不同,这跟普通电子商务网站区别很大,比如天猫,所有商品都是同样的下单方式,同样售后服务(主要就是快递这块),而保险产品即使是线上B2C网站下单操作,短险、寿险、车险等也是不同的,更何况保险有一大堆售后服务,这些售后服务更加不相同。传统保险公司在处理这方面时,一般会做多个系统,比如寿险系统,车险系统等等。
安装之前提到的业务规模我们分析(假设)出一些比较重要的性能需求:
a)产品方面:继续上线当前没有的保险产品
b)B2C网站日访问量:5000万PV
c)B2C产品购买并发高峰:2000 TPS
d)运维系统同时在线:1万(共有2万销售员或代理人)
e)运维系统并发高峰:2000 TPS
f)短险订单:每年1.85亿单
g)长险订单:每年500万
h)车险订单:每年1000万
i)案卷信息:每年新增100万单
日访问量5000万和产品并发2000 TPS是我们假设的,客户信息和案卷信息是随订单数据量变化而变化,在前面我们虽然假设了总共每年产生2亿个订单,但是根据保险种类,短险(旅游险、伤残险)明显产生了90%的订单量,这一点需要特殊处理。除此之外车险和长险(主要指寿险等)无论是投保还是售后服务都有明显不同,所以也需要特殊处理。
那么我们按照上面的需求,进行系统分析,首先按大的职责将职责相同的划分为一个服务。并且有了上面这个性能需求,所有功能需求都需要增加一项“质量”特性,那就是“高并发”,高并发会影响到所有设计。另外如果要将互联网保险平台质量特性排个序,最重要的是可扩展性、安全性,因为保险的种类多而且处理方式不同,除此之外,高并发和可靠性也会直接影响功能的实现,但并没有可扩展性影响大。深入分析职责后把每一种功能的实现关键技术列出,如下:
需求分类 |
实现需求 |
实现子系统及服务 |
软硬件实现技术 |
客户端 |
B2C电子商务网站 |
B2C Web客户端 |
集群部署、高速缓存、分布式缓存、搜索引擎技术、静态化 |
B2C电子商务网站手机客户端 |
B2C App客户端 |
|
|
代理人管理 |
代理人Web客户端 |
集群部署、高速缓存、分布式缓存、搜索引擎技术、静态化 |
|
代理人管理手机客户端 |
代理人App客户端 |
|
|
案卷处理管理 |
案卷处理Web客户端 |
集群部署、分布式缓存 |
|
客户管理管理 |
客户管理Web客户端 |
集群部署、分布式缓存 |
|
保险公估管理 |
保险公估Web客户端 |
集群部署、分布式缓存 |
|
运维产品管理 |
产品管理Web客户端 |
集群部署、分布式缓存 |
|
报表及财务统计 |
报表及财务统计Web客户端 |
集群部署、分布式缓存 |
|
公共服务 |
运维产品管理、Web前端产品访问 |
产品服务 |
集群部署、分布式缓存 |
电子商务或代理人订单管理 |
订单服务 |
集群部署、分布式缓存 |
|
电子商务或代理人等涉及财务操作 |
总账服务 |
集群部署、分布式缓存 |
|
报表及财务统计 |
报表服务 |
集群部署、分布式缓存 |
|
业务服务 |
B2C电子商务网站及手机客户端个人账户 |
B2C个人账户服务 |
集群部署、分布式缓存 |
代理人管理 |
代理人管理服务 |
集群部署、分布式缓存 |
|
案卷处理管理 |
案卷处理管理服务 |
集群部署、分布式缓存 |
|
客户管理 |
客户管理服务 |
集群部署、分布式缓存 |
|
保险公估管理 |
保险公估管理服务 |
集群部署、分布式缓存 |
|
短险开放式接入 |
开放式接入平台服务 |
集群部署、分布式缓存 |
|
工具性服务 |
保险公司产品对接 |
产品对接服务 |
集群部署、消息队列 |
在线支付 |
第三方支付服务 |
集群部署 |
|
短信邮件通知 |
通知服务 |
集群部署、消息队列 |
|
性能监控 |
日志采集服务 |
集群部署、消息队列 |
|
文件服务器 |
文件服务 |
集群部署、消息队列 |
|
服务授权与审计 |
服务授权与审计服务 |
集群部署 |
|
分布式事务管理 |
分布式事务管理服务 |
集群部署 |
|
定时任务管理 |
定时任务服务 |
集群部署 |
各个子系统及模块的关系如下图。
其中订单服务、产品服务、财务服务、工具服务为基础服务,其它各个业务模块的服务会调用这些基础服务。各个业务模块的服务都是根据业务领域进行划分的,同一业务领域下实现技术不同会被划分为两个服务,比如产品展示和订单原本属于同一个大的领域,但其因为实现技术和质量要求不同需要划分为两个服务。因为短险接入量大,而且大部分是跟第三方合作接入,因此设计短险接入公共接口服务平台处理大量短险订单。
对于大型的高并发系统来讲,最重要的当属数据的架构。我们在前面也提到过,web系统业务处理模块本身就可以集群部署,当用户出现高并发时最先遇到的瓶颈就是数据库访问的瓶颈。这也是我们说数据架构最为重要的原因。其实web系统是典型的“计算机信息系统”,也就是说一切以数据(信息)为基础,所有的功能都是围绕着数据来的。这也是我们在这里说数据是web系统最重要的,很多公司在做少用户量web系统时直接设计好数据库就可以开发了。
按照上面划分的业务领域我们设计多个数据库,技术选项包括“是否读写分离”、“是否水平切分”及“路由键”。其中路由键是指在进行水平切分后,我们使用那个“标识”去查询数据库,一般来说会使用该业务领域聚合根对象的主键作为路由键。
数据库 |
是否读写分离 |
是否水平切分 |
水平切分路由键 |
产品数据库 |
是 |
|
|
订单数据库 |
|
是 |
客户ID |
公共数据库(元数据、公共数据) |
是 |
|
|
客户管理数据库 |
|
是 |
客户ID |
案卷管理数据库 |
|
是 |
客户ID |
代理人服务数据库 |
|
是 |
代理人ID |
B2C电子商务个人账户数据库 |
|
是 |
客户ID |
保险公估数据库 |
|
是 |
客户ID |
工具数据库(短信、支付、文件) |
|
是 |
客户ID |
报表数据库 |
是 |
|
|
日志采集服务数据库 |
|
是 |
日志ID |
除工具数据库外,其它的数据库的划分很容易理解。工具数据库的数据也大都跟客户或说用户有关,比如“产品对接服务”,产品对接服务是指用户在购买了保单后,系统会自动对接到具体的保险公司接口去上传保单信息和下载保单,所以水平切分数据库时可以采用用户ID作为路由键。“在线支付”和“通知服务”也是类似,都是保存用户相关的数据,在线支付服务保存的是用户在线支付的流水,通知服务保存的是发送给某用户的短信或邮件。
另外在数据架构中我们也可以看到一个规律,就是使用了水平分库的存储结构就不能再使用读写分离,原因是防止存储单元过度泛滥,因为使用了水平分库之后,本身也要为数据库建立多个备份库,这个时候如果再用读写分离需要建立一套只读库,数据库的数量将增加一倍。使用了分库后我们可以把热点数据存储在分布式缓存中以起到读写分离类似的作用。
另外缓存也是存储技术的一种,而且非常重要。从日常生活中我们也可以看到这一点。比如你要去购买一台电脑,你会发现二级缓存和内存大的价格高出很多,如果你的显卡显存巨大,那将是顶级配置,发烧级配置。手机也是一样,内存大的手机速度明显快,价格也高,如果内存和持久化存储都高,那也是顶级配置。对于web系统也是一样,有些大型web系统只要缓存处理的好,数据库不需要分库就可以承载亿级的用户,比如维基百科、新浪微博等。也因此,不管是电子商务网站还是互联网+大型应用,都会在它们的架构中看到缓存大量使用的情况。
从整个web系统的架构来看,缓存在两个层面大量使用,分别是展示层和逻辑层,展示层通常使用高速的页面缓存,逻辑层通常使用高并发的分布式缓存。当然有些分布式缓存工具既可以在逻辑层使用也可以在显示层使用。
系统 |
是否使用CDN |
是否使用高速缓存服务器(varnish) |
是否使用分布式缓存(redis) |
B2C电子商务网站 |
是 |
是 |
|
报表及财务统计Web端 |
|
是 |
|
B2C个人账户服务 |
|
|
是 |
代理人管理服务 |
|
|
是 |
案卷处理管理服务 |
|
|
是 |
客户管理服务 |
|
|
是 |
保险公估管理服务 |
|
|
是 |
B2C个人账户服务 |
|
|
是 |
短险公共平台服务 |
|
|
是 |
在给出系统总体的逻辑架构前,我们先看看系统前端和服务层直接的关系。前端是客户端,可以有多个客户端,也可以有多种客户端,比如手机端(APP、WAP、微信)等。客户端和服务之间的架构是典型的MVC架构,V是客户端,C就是spring mvc或servlet开发的控制层,对于Web应用V和C在开发角度是一个工程,剩下的M就是服务层。这是对于web系统来说的,对于app或者使用html直接实现的客户端,或者是.net实现的桌面客户端,由于这些客户端是单独开发的,没有控制层,因此需要增加一层“API 网关”作为这些客户端的控制层。
图中的服务组件就是指各个业务服务,这些服务可以看成是组件的概念。通常前端界面一个界面中需要的数据通常不仅仅来自于同一个服务,即使是来自于同一个服务,那也来自于很多不同的接口,servlet或api 网关作为控制层,所起到的作用就是组合接口、组合数据,并处理接口间的事务性。另外服务组件也是分层的,图中并没有展现,一般可以分为3层,从低到高依次是工具性服务组件、基础业务层服务组件、业务层服务组件。前端界面的请求按照从高到底向下传递和处理请求。
按照职责、通用性、技术特性综合考虑和计量,逻辑架构设计如下图:
十几个子系统分别分布在服务层、控制层、表现层(典型的三层架构)。实体层和接口访问层虽然属于“层”,但它们并不单独发布,而是使用Jar包类库的方式提供给其它服务调用,是逻辑上的层。服务组件的构成大都是按照业务领域划分的,只有一个除外,就是“B2C网站”,B2C网站由于是面向终端用户的高并发电子商务网站,为了处理高并发,我们将其拆分为两个业务领域(也可以拆分成多个业务领域,看实际并发量),分别是用户账户和产品。用户浏览产品并购买,这是电子商务网站最基本的两个领域。其中产品浏览等功能由更底层的产品服务提供,用户账户功能由会员服务提供。还有一些功能,比如推荐商品可能是由其它服务提供,这些可以在控制层直接组合这些服务。
服务框架采用典型的“服务注册表”模式,注册表使用redis。服务组件在启动时将自己注册进服务注册表,web服务器或api 网关在访问服务时查询服务注册表得到服务的uri,然后调用某服务接口。
淘宝的dubbo使用的是zookeeper作为服务注册表,之所以使用zookeeper,主要是使用它的负载均衡的功能。笔者认为restful接口没有必要使用zookeeper做负载均衡,可以使用nginx(负载均衡服务器都可以),所以没必要选用动态的zookeeper作为注册表,而是使用“redis+心跳监测”机制(redis也可以换为LDAP等)来完成服务注册和监控失效服务的功能。这个方案至少比dubbo简单几个数量级,简单就是硬道理。
在注册服务时只需要注册nginx服务器的IP以及服务描述信息即可。反观dubbo还要注册接口进注册表,笔者认为这个没必要,因为调用一个服务接口的充分必要条件就是知道服务器的IP即可。至于调用什么服务接口,肯定在代码里已经写死,目标服务器必定存在这个服务接口。将服务接口注册进服务注册表如果是为了监控审计服务的使用情况,那这个功能使用访问日志来实现能做的更好。
总体的分布式拓补结构如下图:
对于服务集群来讲,看上去像是一个大哥(nginx)带有众多小弟的结构。访问某服务群组只需要访问其nginx服务器即可,这里nginx均采用高可用方案,防止单台nginx出现问题。至于高层服务调用底层服务也是直接访问其服务器组的nginx。这个执行的流程其实和日常生活中的概念很像,如公司内部的执行流也是如此:老板分配任务给部门经理,部门经理分配任务给主管,主管分配任务给具体的个人(某单台服务器)。领导们起到的作用也是负载均衡和监控,负载均衡就是把任务可以分配给多个人同时执行(并且某个执行失败自动切换),监控就不必说了就是监控任务完成情况。在我们的方案里,会专门有个服务去监控所有服务器的执行情况,很简单的服务就可以做到。
如果研究一下EJB就会发现,EJB和微服务的架构基本相同,甚至所有面向服务(SCA、SOA等等)的架构都相差不大。很多人反对EJB,并不是EJB不够强大,而是它不够简单,它给项目带来的复杂性甚至超过了项目本身(这也是笔者不建议使用dubbo框架的原因)。曾有人说过:你要么把事情做的尽可能简单,让人挑不出毛病;要么把事情做的尽可能复杂,让人找不出毛病,EJB就是后者。EJB分布式事务机制实现的很好,可惜的是这种“一刀切”的事务机制,大大降低了web系统的性能,所以几乎所有面向互联网的应用都极少使用分布式事务,也就不会采用EJB。互联网应用本身事务性操作并不多,一些新闻、博客之类的网站甚至都不需要事务。另外一个方面,即使出现一个或两个分布式事务应用场景,也可以通过其它手段解决,比如事件机制等等。
在之前的文章中我们也提到过对于分布式事务最佳的策略是尽量避免。如果避免不了将按以下方式实现。
1) 将分布式事务性操作封装在一个服务中,这个服务使用XA或链式分布式事务管理。
2) 可以使用事件机制协调事务管理(具体做法就是事务失败后发失败事件到可持久化的消息队列,然后需回滚操作的接口监控此事件并执行回滚)。
3) 使用自定义分布式事务管理器管理分布式事务。
笔者设想的自定义分布式事务管理器主要是封装了流程及分布式事务相关功能,笔者将在其它文章专门讨论。如图所示,假设有一个事务需要依次调用ABCDE五个接口,我们首先调用分布式事务管理接口创建这条“流程”的实例,实例中五个接口分别对应五个状态,调用成功后将该接口对应的状态设置为“成功”,反之就是“失败”,流程处理结束后,分布式事务检查状态,然后按照一定的策略调用失败接口的反向操作接口去回滚数据(前提条件也是参与分布式事务操作的接口要开发反向操作接口)。这既是一个简单的流程引擎,也是一个分布式事务协调处理装置,具体是否有必要做的复杂(比如处理并行流程),还要看实际环境下分布式事务的情况,但笔者认为互联网前端应用使用简单流程应该足以应付。
系统所需的工程,“[ ]”里面表示工程的名称。
从图中不难看出,我们将运维相关前端界面合并为一个前端系统,总体来讲前端只有3种,B2C前端、APP前端、运维前端。把运维前端合并为一个项目有利于加快前期的开发、部署的效率,在后期如果某子系统功能界面太多,可以将前端系统独立,比如公估系统、客户管理系统等,独立后的系统需要使用单点登录,这样就可以在各个系统之间免登陆切换。
开发环境:
编码:UTF-8
工具:Myeclipse 10
SVN:Site-1.8.22
Web服务器:Tomcat7
JDK: JDK1.7、 Java EE 5
开发环境:Maven 3
开发技术选型:
表现层:Bootstrap+Html+Jquery
MVC框架:Spring MVC 3.2
安全框架:Spring security 3.2
Rest接口实现:Spring MVC Rest
持久层:Mybatis3.2
分布式缓存:Redis
数据库:MySql 5.6
【拓展】使用容器技术来建立一个微服务架构
在之前的博文中,我讲解了Linux容器技术的相关实现,比如如何使用Docker来建立流线型的开发和测试体验。因为可以实现跨不同类型基础设施的兼容(比如,在AWS上,容器也可以如实体服务器上一样轻松的运行),容器让代码的部署异常便捷。在实际工作中,测试和开发环境的细微不同很可能会导致应用程序的部署失败;因此在这种情况下,对于开发和测试工作,容器技术可以让开发者豁免很多预想之外的工作和相互推脱。
在本篇文章中,我们将讨论是什么特性让容器技术如此适应开发和测试工作,同样适用于在AWS平台上构建一个基于微服务的架构。对于Web应用程序来说,微服务架构可以让应用程序的代码库更加敏捷,并且容易管理。下面,我们将介绍这个架构为何可以大幅提升开发者生产效率的原因,并了解它能够快速迭代和扩充一个代码库的原理。对于快速发展中的创业公司来说,微服务架构可以让开发团队在研发过程中更加的敏捷和灵活。
首先,我们先简洁地回顾下20年内基于Web开发的历史,它可以让我们知悉微服务架构为什么可以在Web开发领域如此的盛行,同时也顺便了解这个架构可以解决的问题。
在Web应用程序开发的早期,应用程序通常使用Common Gateway Interface(CGI)建立,这个接口为网络服务器提供了处理浏览器发来的HTTP请求时执行脚本(通常情况下用Perl编写)的能力。CGI的扩展性非常很好,因为它需要为每个输入请求都建立一个Perl进程。为了解决这个问题,那个时代的网络服务器通常都会添加模块化的支持。Apache,现下最为流行的网络服务器之一,增加了“mod_perl”让Perl代码可以在内部运行,这样一来,CGI脚本就可以在更少的时间内执行。
即使对比传统的CGI类似mod_perl这些技术有了很大的提升,但仍然存在问题。也就是说,负责视图层(比如,在HTML页面上执行一个动态模块)的代码通常会被混入应用程序逻辑代码中。这就意味着,完成一个简单的任务,比如在HTML列表中增加一列,或者在form中增加一个元素,通常需要修改一个低等级的应用程序代码。因此,Web程序开发技术下一个阶段中衍生了“server pages”,它允许在HTML嵌入执行代码。这样一来,应用程序逻辑代码与视图代码被很好的分离。在Java开发领域,一个被称为“Model 2”的设计模式得以快速演变,在这里,应用程序代码放到Java servlets中,数据则通过Java Beans进行,视图层逻辑则使用了Java server pages,详见下图:
图1:Model 2设计模型
随后,在Java领域,“Model 2”模式在很短的时间就演化成了“Model-View-Controller(MVC)”框架,比如Apache Struts。而在其他领域,Ruby on Rails则非常盛行。在MVC模式中,控制器类会定义方法,通过“route”类映射成URL模式被调用。 控制器方法会利用“model”类封核心应用程序实体的业务逻辑和数据。最后,每个控制方法都会渲染一个“view”用于显示,并修改相应模式类的方法。在这种模式下,业务、应用程序、视图逻辑被很好的分离,如图2:
图2:MVC设计模型
就在MVC被广泛接受并成为网络开发途径的同时,进程间通信(IPC)也开始利用上了基于文本的序列化格式,比如XML和JSON。而在类似SOAP这些协议实现跨HTTP IPC的不久后,网络开发已不再限制于给浏览器建立交付内容的应用程序,为其他程序执行操作和交付数据的网络服务也逐渐走上历史的舞台。这种基于服务的架构拥有非常强大的功能,因为它消除了代码库共享的依赖性,从而开发者可以更进一步的解耦应用程序组件。而SOAP协议和相关的WS-*标准也变得越来越复杂,并更加依赖于应用程序服务器的实现,至此开发者开始投身更为轻量级的REST协议。同时,随着移动设备的剧增,Web UX development逐渐走向AJAX和JavaScript框架,应用程序开发者开始广泛使用REST在客户端设备和网络服务器之间做数据传输。
后来人们发现,MVC框架同样非常适合开发REST端点。REST面向资源的特性被很好的映射成了控制器和模型理念,如图3所示:
图3:MVC的REST端点
因此,曾今由模型、视图层、控制器组成,主要用于给应用程序交付HTML内容的MVC应用程序发生了本质上的变化——它们不仅可以支撑传统的HTML,也可以通过REST端点来支撑JSON。应用程序被部署为一个单一的文件(比如Java)或者同一个目录下的文件合集(比如Rails)。然而不容忽视的是,所有应用程序代码都运行在相同的进程中。因此在缩放过程中,开发者需要将应用程序代码的多个副本部署到多个所需的服务器上。下图解析了Monolithic架构:
图片4:Monolithic架构
在Monolithic架构中存在着很多的问题。首先,随着应用程序的功能和服务越来越多,代码将变得越来越复杂。对于新的开发者来说,这一点非常头疼。新型集成开发环境在加载、编译整个应用程序代码时也可能存在问题,同时这个过程的耗时也可能非常长。此外,因为所有程序代码都运行在服务器上的相同进程中,导致应用程序某个组成的扩展也非常难。如果某个服务是内存密集型的,而另一个是CPU密集型的,那么服务器必须有足够的内存和CPU来满足每个服务的需求。因此,鉴于每个服务器都使用非常高的CPU和内存,基础设施的整体花费可能会非常高,特别是在纵向扩展策略下。最后非常微妙的是,应用程序的组成通常也会映射到研发团队的结构上。UX工程师负责UI组件的建立,中间层开发者通常负责建立服务器端点,而数据库工程师和DBA们则负责数据访问组件和数据库。如果某个UX工程师期望给增加一些显示,他往往需要来自中间层和数据库工程师的配合。就像水一样,人们通常期望以最少的阻力执行,每个工程师也都期望为其负责的应用程序嵌入尽可能多的逻辑。鉴于这些问题,随着时间的推移,代码将越来越难以管理。
微服务架构的发明就是用来解决这些问题。定义在Monolithic架构应用程序中的服务将拆分成独立的服务,它们在不同的主机上进行独立的部署。
图片5:微服务架构
每个微服务都对应了一个独立的业务功能,也只定义了该功必须的一些操作。这听起来比较类似面向服务架构(SOA),事实上,微服务架构和面向服务的架构确实有很多共同的特性。两个架构都使用服务的模式组织代码,两种架构在不同的服务间都建立了非常明确的边界。然而,面向服务的架构起源于Monolithic应用程序交互的需求,通常彼此都会提供一个API(基于SOAP)。在面向服务架构中,集成重度依赖于中间件,特别在企业服务总线(EBS)中。微服务架构通常会利用一个消息总线,但是无论任何情况在消息层都不会存在逻辑——它纯粹的被用于服务之间的交互。这与ESB有着非常显著的差别,ESB包含了大量逻辑——用于消息路由、模式验证、消息翻译和业务规则。因此,对比传统的面向服务架构,微服务架构往往更为简单,不会包含用于定义服务间接口的同级别控制和规范化数据建模。通过使用微服务,开发将非常快速,服务的衍变也只需匹配业务的需求。
微服务架构的另一个核心优势就是服务可以基于资源的需求进行独立扩展。取代运行包含大量CPU和内存的大服务器,微服务可以被部署在更小的主机上,这些主机只需要满足其部署服务的需求。同时,开发者可以根据业务的需求选择开发语言,比如:一个图像处理服务可以使用类似C++这样的高性能语言实现,一个执行数学或者静态操作的服务可以使用Python实现,对资源进行增删查改的基础操作则往往通过Ruby进行。在微服务中,开发者并不需要考虑Monolithic架构中使用的“一刀切”模型——比如只使用MVC框架和单一的编程语言。
然而,不容忽视的是,微服务同样存在一些劣势。因为服务通常部署在多个主机上,很难持续跟踪指定服务究竟运行在某台主机上。同时,因为微服务架构使用的主机容量往往小于Monolithic架构,随着微服务架构不停的横向扩展,主机数量将以一个非常恐怖的速度增长。在AWS环境中,微服务架构中独立服务需要的资源往往会小于最小的EC2实例类型。从而造成了超量配置并浪费开销。此外,如果服务使用不同的编程语言将开发,这就意味着每个服务的部署都需要完全不同的库和框架,从而服务的部署非常复杂。
Linux容器技术的使用可以很大程度上缓解微服务架构所带来的问题。Linux容器技术使用了类似cnames和namespaces这样的内核接口,它允许不同容器共享相同的内核,同时容器之间还进行了完全的隔离。Docker执行环境使用了一个被称为libcontainer的模块,它标准化了这些接口。Docker同样为容器镜像提供了一个类GitHub的资源库DockerHub,让容器的共享和发布非常简单,也正是这种相同主机上的容器隔离简易了不同语言开发的微服务代码部署。使用Docker,我们可以创建一个DockerFile来描述所有用到的语言、框架和服务间库的依赖性。举个例子,下面代码中的DockerFile可以用来定义一个微服务的Docker镜像,它使用了Ruby和Sinatra框架:
使用这个镜像建立的容器可以便捷地被部署到一个主机上,这个主机同时还运行了另一个使用Java和DropWizard 定义的Docker镜像所建立的容器。容器执行缓解隔离了主机上运行的不同容器,因此不存在使用不同语言、库和框架容器所造成的冲突问题。
同时值得高兴的是,近期发布的Amazon EC2 Container Service(Amazon ECS)可以帮你搞定所有这些工作。使用Amazon ECS,你可以定义一个被称为“cluster”的计算资源池,一个cluster由一个或以上的EC2实例组成。Amazon ECS负责管理集群中所有基于容器的应用程序,提供 telemetry和logging,并管理集群的容量优化,进行高效的任务调度。Amazon ECS提供了一个“任务内容(task definition)”的理念,它可以定义组成一个应用程序的一组容器。task definition中的每个容器都指定了该容器所需的资源,而Amazon ECS将基于集群中的可用资源来调度这个任务的执行。
微服务可以非常便捷地被定义为一个任务,它可以由两个容器组成——一个负责运行服务终端代码,另一个负责运行数据库。Amazon ECS可以管理这些容器之间的依赖性,同时也可以跨集群进行资源平衡。同时,Amazon ECS还可以无缝的访问多个AWS重点服务,比如Elastic Load Balancing、Amazon EBS、Elastic Network Interface和Auto Scaling。通过Amazon ECS,使用 Amazon EC2部署应用程序的所有基本特征都对基于容器的应用程序可用。
此外,类似Amazon ECS 这样的容器解决方案还可以简化“service discovery(服务搜寻)”这样的实现。因为微服务往往会跨多个主机部署,并根据负载进行缩放,service discovery更有利于服务之间的定位。在最简单的情况下,可以使用负载均衡器来进行,但是在更为复杂的环境中,一个真正的分布式配置服务非常有必要,比如Apache Zookeeper。使用Amazon ECS API,与类似Zookeeper这样的第三方工具整合将非常容易。配置了Zookeeper的容器可以被添加到一个task definition中,并可以通过Amazon ECS在集群中的Amazon EC2调度执行。
从许多方面来看,使用容器技术实施微服务架构转变都与过去20年Web开发的衍变非常类似。许多这些衍变都是为了更好的利用计算资源,以及更方便的维护越来越复杂的Web应用程序。如我们所见,使用Linux容器技术来实现微服务架构完全匹配了这两个需求。在本文中,我们简单地接触了使用Amazon ECS来定义一个微服务架构,但是容器在分布式系统中的使用已经远超过了微服务。在分布式系统中,越来越多的容器成为了first class citizens,而在未来的报告中,我将讨论为什么 Amazon ECS对管理给予容器的计算是至关重要的。
相关参考文章:一个更好的开发/测试体验:在AWS上运行Docker