Java Code Geeks有一篇文章Microservices, Monoliths, and NoOps,分析了单体应用与微服务的优缺点,并建议使用微服务重构现有的应用程序。
单体应用
通俗地讲,“单体应用(monolithapplication)”就是将应用程序的所有功能都打包成一个独立的单元,可以是JAR、WAR、EAR或其它归档格式。
单体应用优点
单体应用有如下优点:
- 为人所熟知:现有的大部分工具、应用服务器、框架和脚本都是这种应用程序;
- IDE 友好:像NetBeans、Eclipse、IntelliJ这些开发环境都是针对开发、部署、调试这样的单个应用而设计的;
- 便于共享:单个归档文件包含所有功能,便于在团队之间以及不同的部署阶段之间共享;
- 易于测试:单体应用一旦部署,所有的服务或特性就都可以使用了,这简化了测试过程,因为没有额外的依赖,每项测试都可以在部署完成后立刻开始;
- 容易部署:只需将单个归档文件复制到单个目录下。
单体应用缺点
目前为止,单体应用已经很好地服务了我们,未来无疑还会继续发挥重要作用。但是,不管如何模块化,单体应用最终都会因为团队壮大、成员变动、应用范围扩展等出现问题。下面是单体应用的一些不足:
- 不够灵活:对应用程序做任何细微的修改都需要将整个应用程序重新构建、重新部署。开发人员需要等到整个应用程序部署完成后才能看到变化。如果多个开发人员共同开发一个应用程序,那么还要等待其他开发人员完成了各自的开发。这降低了团队的灵活性和功能交付频率;
- 妨碍持续交付:单体应用可能会比较大,构建和部署时间也相应地比较长,不利于频繁部署,阻碍持续交付。在移动应用开发中,这个问题会显得尤为严重;
- 受技术栈限制:对于这类应用,技术是在开发之前经过慎重评估后选定的,每个团队成员都必须使用相同的开发语言、持久化存储及消息系统,而且要使用类似的工具,无法根据具体的场景做出其它选择;
- 技术债务:“不坏不修(Not broken,don’tfix)”,这在软件开发中非常常见,单体应用尤其如此。系统设计或写好的代码难以修改,因为应用程序的其它部分可能会以意料之外的方式使用它。随着时间推移、人员更迭,这必然会增加应用程序的技术债务。
什么是微服务?
而随着业务需求的快速发展变化,敏捷性、灵活性和可扩展性需求不断增长,迫切需要一种更加快速高效的软件交付方式。微服务就是一种可以满足这种需求的软件架构风格。单体应用被分解成多个更小的服务,每个服务有自己的归档文件,单独部署,然后共同组成一个应用程序。这里的“微”不是针对代码行数而言,而是说服务的范围限定到单个功能。
微服务的特征
微服务有如下特征:
- 领域驱动设计:应用程序功能分解可以通过Eric Evans在《领域驱动设计》中明确定义的规则实现,领域驱动设计不是分解应用程序的唯一方法,但肯定是很常用的一种;每个团队负责与一个领域或业务功能相关的全部开发;团队遵循全栈开发方法拥有全系列的开发人员,具备用户界面、业务逻辑和持久化存储等方面的开发技能;
- 单一职责原则:每个服务应该负责该功能的一个单独的部分,这是SOLID原则之一,Unix工具程序很好地证明这一原则的重要性;
- 明确发布接口:每个服务都会发布一个定义明确的接口,而且保持不变;服务消费者只关心接口,而对于被消费的服务没有任何运行依赖;服务之间就业务模型、API、负载或其他契约达成一致并使用符合契约的方式进行通信。接口可能会产生新版本,但接口的老版本可以继续使用,且新服务保持后续兼容。不可以通过改变契约破坏兼容性。
- 独立部署、升级、扩展和替换:每个服务都可以单独部署及重新部署而不影响整个系统。这使得服务很容易升级,例如增加更多的功能点。每个服务都可以沿着《Art of Scalability》一书定义的X轴(水平复制)和Z轴(面向查询的分割,数据分区)进行独立扩展;由于其他服务仅依赖发布的接口,只要发布相同的契约,服务实现甚至是底层技术栈都可以修改;
- 可以异构/采用多种语言:每个服务的实现细节都与其它服务无关,这使得服务之间能够解耦,团队可以针对每个服务选择最合适的开发语言、持久化存储、工具和方法;一个需要在关系型数据库存储数据的服务可以选择MySQL,另一个需要存储文档的服务可以选择MongoDB。不同的团队可以根据自己的需求选择JavaEE、NodeJS、Python、Vert.x或其他对本团队最有效的技术;
- 轻量级通信:服务通信使用轻量级的通信协议,例如在HTTP上承载的REST。由于REST本质是同步的,可能会有某些潜在的瓶颈。另一个可选机制是使用支持异步消息的发布/订阅机制。任何符合需求的消息协议,例如AMQP、STOMP、MQTT或WebSocket,都可以使用。简单消息实现,例如ActiveMQ,提供了可靠的异步组构尤其适用于这种用途。每个开发团队可以根据服务的具体需求对同步还是异步消息做适宜的选择,当然也可以混用。
Netflix是微服务的一个典型代表,这里有几篇文章介绍他们对微服务的应用。
- Netflix’s Viewing Data: How We Know Where You Are in House of Cards
- A Microscope on Microservices
- Scalable Microservices at Netflix. Challenges and Tools of the Trade
- Adopting Microservices at Netflix: Lessons for Architectural Design
很多增强Netflix微服务架构的工具可在netflix.github.io获得。
微服务的优点
微服务具有如下优点:
- 易于开发、理解和维护:微服务中的代码仅限于业务的某一功能,因此更易于理解。IDE可以很轻松加载小的代码库,且使开发者保持高效。
- 比单体应用启动快:微服务的范围比单体应用小得多,应此会有较小的打包文件。其结果就是,更快的部署和启动使开发者保持高效。
- 局部修改很容易部署:每个服务独立于其他服务进行部署。服务的任何局部修改,例如更改底层实现使服务性能获得提升,无需同同其他组进行协调。其结果就是,保持了微服务敏捷性,同时也有利于持续集成和持续交付。
- 可独立扩展:每个服务可以根据需求给予X轴(克隆)和Z轴(分区)进行独立扩展。对于单体应用而言,这一点很难做到,且扩展必须一起部署。
- 故障隔离:一个应为异常的服务,例如内存溢出或数据库连接没有关闭,仅影响所提供的服务而不是整个应用,增强了故障隔离能力。
- 不会受限于任何技术栈:开发者可以自由选择对所开发服务最适合的开发语言和技术栈。
微服务看上去像一枚银弹,可以解决许多软件开发方面的问题。这看上去很美好,但并不易于实现。微服务会极大地增加运维工作量,InfoWorld在一篇文章中明确指出:
使用微服务,一些技术债务势必从开发转到运维,因此,你最好有一个一流的开发运维团队。
微服务和NoOps
微服务对基础设施提出了一些额外的需求。通常,我们将它们总称为NoOps,本质上讲,就是一组服务,提供一个更好的应用程序部署流程并确保其运行。
- 服务复制:每个服务都需要复制,一般使用X轴克隆或Z轴分区。
- 服务发现:可能需要多个服务协作提供一个应用的功能。这需要服务能够发现其他服务。在云环境下尤其棘手,因为其上的服务都是短暂的,很有可能扩展或缩减。服务解析是所有其他服务都需要的基础功能。服务需要向中央注册中心注册,其他服务需要查询注册来解析依赖关系。NetflixEureka、Etcd、 Zookeeper等都是这一领域的可选方案。
- 服务恢复:不管测试工作做得多努力,软件故障终会发生。关键问题不是如何避免故障而是如何解决故障,对于微服务这样的分布式服务尤其突出。对于服务很重要的一点是能够自动纠正故障,确保用户体验不受影响。
- 服务监控:分布系统最重要的一个方面就是服务监控和日志。
重构成微服务
不过,即使具备了上述条件,也并不是说就要抛弃现有的应用程序,在大多数情况下,我们无法做到。因此,我们要构建一种方法,依据它使用微服务重构现有的应用程序。虽然重构过程并不简单,但长远来看,重构一个单体应用可以一次性偿还所有的技术债务。
读后感
我基本赞同这篇文章所介绍的微服务特征及其优点,但还是有下列担忧:
- 无论微服务与单体应用相比有多少优点,构建一个新的业务系统或重构一个老业务系统总归是有投入的。怎么选择一个投入/产出比阀值来确定启动微服务是否合理是一个很重要的问题。随着RESTful,云计算、Docker、持续交付等概念的深入人心,很多公司都想将业务迁移到云平台上去以适应今后的客户需求,微服务逐渐成为系统架构的一个代名词。我个人不觉得所有业务都应该一股脑地迁移到云平台上去,重构成微服务。还是要对每个业务进行定性/定量分析吧。
- 将一个单体应用分拆成微服务,微服务颗粒度的把握也是一个关键问题。颗粒度太粗的话,可能阻碍后继业务扩展,从而需要再次拆分。而过度的拆分服务,又会影响性能,例如原来一个JVM内部的调用就有可能变成微服务之间跨系统的通信,一个服务链上服务交互太多的话,响应会延迟很大。单体应用中仅某些关键点采用缓存,而微服务架构模式下每个服务都有可能采用缓存,对缓存的普遍使用跟分拆带来的性能下降有一定关系。
- 微服务架构模式下,监控相比单体应用也更加困难。服务的分散使得调用路径有时候会变得比较复杂,如何从业务维度对链路实行跟踪回溯,如何度量整体业务的吞吐,如何快速发现业务的瓶颈,是使用微服务必须解决的问题。
- 微服务采用多种开发语言,怎么进行代码复用?此外采用无论哪种架构,最关键的的还是能够胜任开发工作的人力资源。微服务可以异构/采用多种语言,说起来很容易。要一个团队负责的多个微服务采用异构+多种开发语言,招聘起来就不容易,人员补充也不容易。就算没有招聘问题,一个开发者工作变动,开始负责另外一个微服务,还需要学习另一种系统或开发语言,也不是很容易立即上手。
其他阅读
Martin Fowler: Microservices Microservices - Not A Free Lunch!