
在2014年Sam NewmanMartin Fowler在ThoughtWorks的一位同事出版了一本新书《Building Microservices》。该书描述了如何按照Microservice架构模式设计及搭建一个具有良好扩展性并可持续开发的系统。除此之外该书还将基于该模式的系统演化流程与Continuous Delivery等当前甚为流行的开发流程结合在了一起使得Microservice架构模式看起来非常具有吸引力。基于这些原因该架构模式迅速被业界所熟知并在多个产品中被尝试着使用。这其中就包含了我们公司的产品vRA。在这一年多的时间里我们不但真正地体会到了Microservice所具有的一系列优点也犯过一系列错误。因此在这篇文章里我会对Microservice架构模式进行简单地介绍并将我们所得到的经验和教训介绍给大家。Monolith网上对Microservice进行介绍的文章常常以Monolith作为开头我也不会例外。原因是知道了Monolith的不便之后才能更容易地理解Microservice架构模式所具有的各种优点。首先请回想一下我们所开发的服务是什么样子的。通常情况下这个服务所对应的代码由多个项目所组成各个项目会根据自身所提供功能的不同具有一个明确的边界。在编译时这些项目将被打包成为一个个JAR包并最终合并在一起形成一个WAR包。接下来我们需要将该WAR包上传到Web容器中解压该WAR包并重新启动服务器。在执行完这一系列操作之后我们对服务的编译及部署就已经完成了这种将所有的代码及功能都包含在一个WAR包中的项目组织方式被称为Monolith。在项目较小的情况下这种代码组织方式还是可以接受的更改完代码后软件开发人员可以趁着编译器编译代码的时候冲杯咖啡并在回到座位后花费一分钟部署刚刚编译出来的WAR包以便测试自己刚刚所做的更改。但随着项目的逐渐变大整个开发流程的时间也会变得很长即使在仅仅更改了一行代码的情况下软件开发人员需要花费几十分钟甚至超过一个小时的时间对所有代码进行编译并接下来花费大量的时间重新部署刚刚生成的产品以验证自己的更改是否正确。如果应用的部署非常麻烦那么为了对自己的更改进行测试软件开发人员还需要在部署前进行大量的环境设置进而使得软件开发人员的工作变得繁杂而无趣从上面的示意图中可以看到在应用变大之后软件开发人员花在编译及部署的时间明显增多甚至超过了他对代码进行更改并测试的时间效率已经变得十分低下。在变得越来越大的同时我们的应用所使用的技术也会变得越来越多。这些技术有些是不兼容的就比如在一个项目中大范围地混合使用C和Java几乎是不可能的事情。在这种情况下我们就需要抛弃对某些不兼容技术的使用而选择一种不是那么适合的技术来实现特定的功能。除此之外由于按照Monolith组织的代码将只产生一个包含了所有功能的WAR包因此在对服务的容量进行扩展的时候我们只能选择重复地部署这些WAR包来扩展服务能力而不是仅仅扩展出现系统瓶颈的组成但是这种扩展方式极大地浪费了资源。就以上图所展示的情况为例在一个服务中某个组成的负载已经达到了90%也就是到了不得不对服务能力进行扩容的时候了。而同一服务的其它三个组成的负载还没有到其处理能力的20%。由于Monolith服务中的各个组成是打包在同一个WAR包中的因此通过添加一个额外的服务实例虽然可以将需要扩容的组成的负载降低到了45%但是也使得其它各组成的利用率更为低下。可以说所有的不便都是由于Monolith服务中一个WAR包包含了该服务的所有功能所导致的。而解决该问题的方法就是Microservice架构模式。Microservice架构模式简单地说Microservice架构模式就是将整个Web应用组织为一系列小的Web服务。这些小的Web服务可以独立地编译及部署并通过各自暴露的API接口相互通讯。它们彼此相互协作作为一个整体为用户提供功能却可以独立地进行扩容。就以下图所示的WikiPedia服务架构为例从上图中可以看到WikiPedia包含了一系列服务如数据访问服务Databases搜索服务Search等。这些服务都包含了数量不等的服务实例以确保能在不同负载的情况下为用户提供优质的服务。在用户的请求到达时它们将协同工作一起完成对用户请求的响应。在使用Microservice架构模式的情况下软件开发人员可以通过编译并重新部署单个子服务的方式来验证自己的更改而不再需要重新编译整个应用从而节省了大量的时间。同时由于每个子服务是独立的因此各个服务内部可以自行决定最为合适的实现技术使得这些子服务的开发变得更为容易。最后如果当前系统的容量不够了那么我们只需要找到成为系统瓶颈的子服务并扩展该子服务的容量即可Microservice经验谈以上就是对Miscroservice架构模式的介绍是不是很简单实际上这是一个正在发展的架构模式。在众多讨论中关于该模式的标准实现以及最佳实践等众多话题并没有完全达成一致。因此我在这里介绍的是各个论坛讨论中基本达成一致意见的一系列经验。而各位在实现自己的Microservice架构模式时一方面可以借鉴这些经验另一方面也可以根据项目本身需求调整Microservice架构模式的实现方法。转变你的视角无论是在编写一个服务还是在编写一个桌面应用我们常常会首先尝试将需要实现的功能分割为一系列组件并围绕着这些组件设计完成业务逻辑所需要的工作流及数据流。这种设计方法将导致实现业务逻辑的所有组件都运行在同一个进程之内并且各个业务逻辑的实现也在同一个进程之内运行但是在Microservice架构模式中我们需要更高一层的分割在尝试将需要实现的功能分割成为一系列组件之前我们首先需要考虑如何将需要实现的功能交由彼此相互独立的一系列服务来完成。例如在一个电子商务网站中对用户购买商品这一业务流程的支持就可以交由三个服务来完成在用户浏览商品的时候其使用的是商品浏览服务在用户将商品添加到购物车并生成订单的时候其使用的是订单服务而在用户进行网上支付的时候其使用的则是付款服务。根据这种分割思路我们的应用将运行在三个独立的进程之中同时这三种服务各自的侧重点并不相同商品浏览服务中对数据库的读操作比写操作多得多因此对读操作进行优化将非常显著地提高服务的运行性能而订单服务则是写操作居多因此我们需要对订单的写入性能进行优化付款服务涉及到用户的财产因此其对安全要求会偏高一些。这种差异可能导致最适合实现这三个服务的技术各不相同。由于这些服务是完全独立的因此我们完全可以根据子服务的需求来决定所需要使用的技术而不再需要考虑这些类库是否与已有系统兼容。使用最合适的技术所带来的优点就是服务的代码会变得非常清晰明了甚至在有些情况下可以达到简洁优雅的程度。在一些讨论中有些人甚至建议一个服务只需要10到100行代码他们常用简写LoC即Lines of Code。再加上服务已经独立出来而不再与其它服务混合在一起因此正确地使用Microservice架构模式大大提高了代码的维护性以及新人上手的速度也有助于技术人员在日常工作中进行技术集的更新及转换。但是这种对于服务的分割和组件之间的分割并不相同。最重要的一点就是在各个服务之间进行通讯的消耗相对于在同一个进程中而言是非常大的。在设计一个组件的时候我们需要考虑该组件所给出的接口能够尽可能地满足当前及今后的一系列可以预见的需求这便要求该组件所提供的API具有一定的前向兼容性并拥有一系列其它特性如灵活性扩展性等等。这常常导致该组件所提供的API具有较细的粒度。在程序运行时对该组件所提供的API的调用就在当前进程中进行速度非常快因此频繁地对该细粒度API进行调用并没有太大的问题。但是一个跨服务调用所需要的时间则比进程内调用的时间长很多。如果在处理一个请求的时候需要太多的跨服务调用那么整个应用的性能将变得无法忍受。因此我们在执行服务分割时定义的API需要是粗粒度的API。就让我们以一个电子商务网站为例。在为用户生成订单时电子商务网站常常需要列出各个商品的主要信息商品的价格优惠幅度并通过库存系统检验该商品的库存从而得到整个订单的内容。如果每次与其它服务沟通都需要100毫秒而且整个订单包含了20件货物那么系统准备订单的时间就会达到8秒100ms * 4次调用 * 20件商品。这从用户的角度来说是不可以接受的性能。而且随着订单中所包含商品数量的增多系统准备订单的时间会线性增长进而使得系统的性能更加不可忍受。究其原因实际上还是因为准备订单所调用的API的粒度太细了。如果订单系统能够一次性地把一件商品的主要信息价格优惠幅度以及库存信息从商品服务中取回来那么其效率就将提高四倍。如果订单系统不需要为每件商品依次发送请求而是可以通过一次性地服务间调用就能取回所有需要的信息那么系统准备订单的时间将不会再随着订单的增大而增长。因此在Microservice架构模式中各个服务应该提供可以被灵活使用的粗粒度API以减少各种跨服务调用的消耗。除了各个服务所提供的API的粒度服务分割的粒度也是在服务分割过程中需要考虑的因素。如果一个服务的粒度太小那么它所提供的API的粒度也不会高。一个较为普遍的看法是在Microservice架构模式中一个服务需要能够独立地完成特定的业务逻辑至少是某个独立资源的CRUD操作。例如在电子商务网站中我们需要一个服务能够独立地完成对商品相关信息的读取如商品的主要信息商品的价格参与的优惠活动等。这里有一个例外那就是公共功能的处理。试想在一个应用中我们常常需要一个权限管理组件来管理用户所具有的各个权限。权限管理组件常常实现了一种公用的安全模型Security Model如ACLAccess control listRBACRole-based access control等。在每次访问一个子服务的时候这些服务都需要检查用户所具有的权限发现问题了么是的每次对一个产品系统及订单系统的调用都需要从权限系统中得到当前用户的权限才能决定用户是否能够访问特定信息。如果这样的公共服务很多那么该系统的性能将会变得非常差。解决该问题的一种方法就是在各个系统中将下次还能够使用的信息缓存起来也就是在这些系统中为用户创建一个会话。由于每个系统可能由多个服务实例所组成为了能够重复利用会话中所储存的信息减少向公共服务发送请求的次数我们需要通过负载平衡技术让系统中的同一个服务实例处理同一个用户的请求。有关如何实现该功能请见我的另一篇文章《企业级负载平衡简介》。除了性能问题之外公共服务还会与各个服务产生一种逻辑上的依赖关系。让我们继续通过权限系统这个例子进行讨论。当权限管理的组成存在于各个服务中的时候我们可以直接通过传入用户的信息以及需要访问的资源就能判断出到底用户是否能够访问特定资源。也就是说从权限管理组成所返回的实际上就是一个布尔类型的数据。但如果权限管理不再是一个组成而是一个服务那么为了避免每次都调用权限管理服务我们需要在用户的会话中记录用户所具有的权限。在用户下次访问该服务的时候我们可以通过直接检查该用户所具有的所有权限就能决定其是否能够访问特定资源了。这些在用户会话中记录的权限常常具有其特定的表现方式例如特定形式的字符串而这种字符串表示需要同时被服务和权限管理服务所理解从而造成了这两个服务之间的耦合但是这种方式为子服务增强了对权限系统的依赖性。和组件之间的耦合一样增大的耦合性会导致服务的重用性下降。所以说如何对服务进行分割实际上是Microservice架构模式中最需要技巧的事情。在分割过程中服务的总体性能是至关重要的而各个服务的独立性也是大家所最为关心的特性。当然Microservice架构模式仍在逐渐发展中因此相信会有越来越多的实践经验被大家所发掘出来进而指导我们更好地对服务进行分割。共享服务在前面一节中我们已经提到了公共服务。实际上这是Microservice架构模式中最需要技巧的一部分。实际上Microservice架构模式实现中常常需要一系列公有服务以辅助整个应用的运行。除了我们刚刚提到的权限管理服务我们还需要能够监控各个服务实例的服务状态服务实例的添加删除升级管理等等。这些服务在各个子服务的服务实例之间共享甚至可以在其它应用中被重用。只是很多人拥有一个这样的误区那就是Microservice架构模式可以让服务的开发变得更容易。而实际情况则恰好相反。在刚开始使用Microservice架构模式开发应用的时候其效率是明显低于通过Monolith进行开发的从上图中可以看到在刚开始的阶段使用Microservice架构模式开发应用的效率明显低于Monolith。但是随着应用规模的增大基于Microservice架构模式的开发效率将明显上升而基于Monolith模式开发的效率将逐步下降。为什么呢这是因为Microservice是一个架构模式而不是一个特定的技术解决方案。其并不会将开发中的各个难点全部转移而只是允许通过更为合适的技术来适当简化单个子服务的开发或者绕过开发中可能遇到的部分难点。但是为了支持各个子服务的运行我们还需要创建一系列公共服务。这些公共服务需要在编写第一个子服务的同时进行。这是导致Microservice架构模式在开发初期会具有较低效率的一个原因。然而使用特定技术并不会绕过开发中所能遇到的所有难点。由于在Microservice架构中各个子服务都集中精力处理本身的业务逻辑而所有的公共功能都交由公共服务来完成因此公共服务在保持和各个子服务的松耦合性的同时还需要提供一个足够通用的能够在一定程度上满足所有当前和未来子服务要求的解决方案。而这也是导致Microservice架构模式在开发初期会具有较低效率的另外一个原因。而在开发的后期随着Monolith模式中应用的功能逐渐变大增加一个新的功能会影响到该应用中的很多地方因此其开发效率会越来越差。反过来由于Microservice架构模式中的各个子服务所依赖的公共服务已经完成而且子服务本身可以选择适合自己的实现技术因此子服务的实现通常只需要关注自身的业务逻辑即可。这也是Microservice架构模式在后期具有较高效率的原因。当我们再次通过Microservice架构模式搭建应用的时候其在开发时的效率劣势也将消失原因就是因为在前一次基于Microservice架构模式开发的时候我们已经创建过一次公共服务因此在这个新的应用中我们将这些公共服务拿来并稍事改动即可从上图中可以看到虽然我们仍然需要花一些时间来对公共服务进行一些修改但是此时所导致的效率下降已经不再那么明显了。也就是说就算是在前期我们已经拥有了较高的开发效率。而且随着Microservice架构模式的不断流行在网络上会有越来越多的用户共享自己的公共服务解决方案。那么第一次按照Microservice架构模式编写应用所导致的性能下降也会逐渐变得越来越小。模型匹配OK。在介绍了共享服务之后我们就可以讨论Microservice架构模式中的另外一个问题模型匹配了。在Microservice中各个服务是彼此独立的而且是关注于自身业务逻辑的。因此在看待一个事物的时候Microservice可能拥有不同的视角进而造成了各个子服务中的对应模型并不匹配。例如在一个IaaS云中一个用户所具有的角色可能会根据他所拥有的职责来划分云上拥有一系列用于监控的用户用来完成对云的整体运行监控等工作不包含查看用户数据这是个安全问题。同时云上的用户又可以分为帐号管理员Tenant管理员资源管理员以及普通用户等。而在其上运行的应用即服务AaaSApplication as a Service中其用户的职责划分可能是另一个样子在AaaS上定义应用的是应用架构师负责应用部署及维护的则是运维人员。在应用架构师设计一个应用的时候其并需要拥有IaaS云上访问资源的权限却并不需要分配资源的权限但是运维人员需要拥有该权限以对应用进行部署和维护。也就是说IaaS云中的权限划定和AaaS服务中的权限划定并不一样。通常情况下我们常常在业务集成时执行一次对权限的匹配从上图中可以看出由于AaaS服务是运行在IaaS之上的因此为了能够操作IaaS中所包含的各个资源AaaS服务需要将自己的用户角色匹配到IaaS所定义的角色上。例如应用架构师需要能够在定义应用的时候需要知道IaaS上所具有的资源并需要能够指定到底哪些人可以使用这些应用因此其需要拥有IaaS的Tenant管理员资源管理员及普通用户三种角色。而AaaS上的运维人员则只需要在部署和维护时察看IaaS上所拥有的资源因此其只需要资源管理员及普通用户两种角色。但是这么做有两点不好的地方如果Microservice中只包含了几个服务而且这种服务之间的依赖关系并不是很多那么这种服务匹配还能够解决但是如果整个系统之间各个子服务的沟通很多那么在各个子服务之间进行角色匹配将变成一个噩梦