单体应用架构与微服务架构

204 阅读15分钟

翻译自:microservices.io/patterns/mo…

最近读了一系列文章,本来是讲解microservices微服务应用架构。但是想来还是需要先理解微服务架构之前的应用架构是怎样的,刚好又读到一篇简单易懂的文章。故此翻译,作为学习笔记记录

上下文

你正在开发一个服务器端的企业应用。作为服务器端应用,必须支持一堆不同的客服端包括pc桌面浏览器、移动端浏览器、还有原生移动端应用app。还有可能需要将API暴露给其他第三方用户使用。也可能和其他应用通过web服务、MB(message broker)进行集成。这个服务器端应用收到客户端requests(http 和message)后,通过执行业务逻辑、接入数据库、与其他系统交换信息等后。最后返回给客户端response(通常是HTML/JSON/XML形式)。在这个服务器端应用中根据不同功能区域有着不同的逻辑组件。

问题

这个服务器端应用的部署架构是怎样的?

需求

  1. 一个团队正在开发这个应用
  2. 新来的开发人员必须快速形成生产力
  3. 项目必须容易被理解和修改
  4. 想对该应用进行CD(持续部署)
  5. 想在多个机器上运行该应用的多个实例以满足扩展性和可用性需求
  6. 需要项目开发中集成多中技术(框架,编程语言,等等)

解决方案

单体应用结构偶(Monolithic Architecture)

用单体结构构建应用程序。举个例子:

  1. 单个的java war文件运行在severlet容器上
  2. 单个文件Rails或者NodeJS代码的文件目录(使用Phusion Passenger部署在Nginx/Apache上)
例子

问题描述:

我们假设你正在开发一个电子商务应用。这个应用接受用户下单,验证库存和余额是否够用,之后发送商品。很明显,这个应用包括几个组件:商店UI(实现用户接口),然后还有一些后端的服务:检查余额,维护库存和发送商品。

如何部署?

这个应用以单体结构形式被部署到服务器上。比如,一个Java Web应用就是单个的War文件运行在Web容器中(Tomcat)。当然为了提高扩展性和可用性,通常会运行多个应用的实例,然后通过一个负载平衡(比如Nginx)来将多个实例的运行细节隐藏起来,用户通过这个load balancer来访问服务。

img

影响

这种解决方案有几个益处:

  1. 简单的开发-----当前的开发工具和各种IDEs的目标,其实都是支持这种单体结构应用开发
  2. 部署简单------只用将单个的war文件(Java为例子)部署在正确的运行环境中就行了
  3. 容易扩展------通过运行单体应用的多个实例,然后通过load balancer来平衡请求,就可以简单的进行扩容

但是,当应用变得庞大而复杂,然后开发团队也庞大。这种方式的几个缺点就非常明显的显现出来了。

  1. 庞大的代码库会吓到开发者,特别是团队的新成员(完全不知道从哪里开始,到哪里结束)。应用项目会变得难以理解和修改。造成的结果就是开发速度变慢。同时,因为在各个模块之间并没有实打实的界限(都是在一个单体应用上,虽然设计了模块,但是还是可以互相调用),所以系统的模块性就会随着开发的进行慢慢分解。更甚至的是:因为是在在愈加难以理解的基础上进行更新和新增功能,代码的质量也会随之下降(屎山中改代码,不要求质量,只要求能运行)。
  2. 负载过重的IDE-----代码库越大,IDE就越慢,开发者的生产力就越低
  3. 负载过重的web容器------应用越大,web容器启动运行应用的时间就越长。开发者每次浪费在等待容器启动应用的时间,就会自然降低开发者的生产力(md,全是资本家套路)。同时也影响部署
  4. CD(continuous deployment)会变得非常困难----一个巨大的单体应用在又持续部署的需求时,也会成为一个大障碍。为啥?你想:更新单体应用的一个组件(模块),你还不是要重新部署整个应用。这样一来就会stop the world,意味着所有这个应用后台的任务都会被中断,不管这些任务的code是不是有更新,有没有被更新组件的修改所影响,会不会被新的更新造成bug。同样的那些没有修改的组件同样有可能不能正常的启动(配置不到位,容器参数不正确呀,版本呀等)。所以,重新部署的风险会上升,就会造成开发者越加不愿意频繁更新。这种时候就会特别影响前端用户接口的开发者,因为前端开发者一般都需要后端服务或应用的快速迭代和部署。
  5. 扩容会变得困难----一个单体应用架构只能从一个维度去扩容。什么意思?就是如果想要更大的业务吞吐量,就只能多运行几个程序的copies。一些云服务商甚至可以根据负载动态的调整应用的实例数量。但是,这个单体结构并不能根据数据的容量来扩容,因为每个应用的实例都会访问当所有的数据,这样就会造成你的缓存效率降低,增加你的内存消耗和I/O流量。还有就是,不同的应用组件(模块)有着不同的资源需求,有的可能是更需要CPU(重运算),有的可能是需要很多的IO资源。用单体应用架构的方式,我们是无法对各个组件进行独立扩容的
  6. 开发扩展变得困难----单体架构的应用同样在开发扩展上会造成障碍。当应用变大到一定的size,常用做法是将开发团队分成不同的小team,然后让每个小的team负责单一的功能领域。比如,我们可能有UI团队、会计团队、库存团队、等等。而单体架构恰恰会对各个团队的独立工作造成阻碍。这些团队必须和其他团队合作去开发和部署。而且导致团队在生产环境中去更新变得更加困难
  7. 要求对技术栈的固定长期使用----单体应用架构强迫你必须长期使用某一个技术栈(比如某一个版本的技术),而且这个技术栈是在开始开发的时候就确定的。这就意味着,在开发中无法逐渐的去适用新技术。比如,我们现在选择了JVM,那么你可以写Java、Groovy、Scala.但是你也限制了应用中的其他组件只能用基于JVM的语言。同时,如果应用开发的框架随后过时了,此时如果将应用迁移到新的框架会非常具有挑战性。甚至完全有可能:为了用某种新的框架,不得不重写整个应用项目,这是极具风险的。
相关的架构模式

微服务架构是另一种替代架构模式,它解决了单体架构模式的限制。

著名例子

一些著名的互联网服务,比如网飞,亚马逊,eBay 最初都采取的是单体架构模式。大多数web应用都曾经是单体架构模式

微服务架构(Microservice Architecture)

解决方案

定义一种架构,这种架构能够将应用视为由一堆松耦合,相互合作的服务组成的一个集合。这种架构(微服务架构)对应的是横向扩容(Y-axis of the scale cube)。每个服务都是:

  1. 便于维护和测试----让项目能够快速开发和部署
  2. 跟其他服务松耦合----能够让各个团队在大多数时间独立开发,而且能够不被其他服务的更新和修改影响,而自己的开发和修改也不会影响到其他服务
  3. 独立部署---能够让开发团队独立部署自己的服务,而不需要和其他团队合作
  4. 能够被小团队部署----对于高生产力的团队和重要,而且也能避免高额的沟通成本

服务之间通过同步协议进行通信,比如HTTP/REST;或者异步协议,比如AMQP(Advanced Message Queuing Protocol)。服务可以独立开发和部署。每个服务为了和其他服务解耦都需要自己的数据库。服务之间的数据一致性通过Saga模式来保证(也有其他模式)

例子

跟上面的例子描述相同

这个应用由多个服务组成

img

代码实例:eventuate.io/exampleapps…,这些例子里面展示了微服务架构的各方面

解决方案的影响

这种解决方案有以下益处:

  1. 使得大型复杂应用能够实现持续交付和持续部署。

    1. 提高了可维护性----每个服务都相对比较小,所以就会便于理解和改变
    2. 提高了可测试性----服务小,所以测试就会更快也会更方便
    3. 更好的部署性----服务可以独立的部署
    4. 使得项目团队可以分开成多个自治的开发团队。每个团队负责自己一个或者多个服务。每个团队都可以独立开发,测试,部署,扩容
  2. 每个微服务都比较小

    1. 开发者更容易理解
    2. IDE可以更快,从而提高开发者的生产力
    3. 应用启动得更快,从而提高开发者得生产力,也可以加速部署得速度
  3. 提高了故障的隔离性。比如说,如果在某一个服务上出现了内存泄漏,那么这个故障只会影响到这个一个服务。其他的服务会继续处理用户请求。作为对比,在单体架构应用中,一个组件出现异常行为很可能会使得整个应用都无法使用

  4. 消除了对某种技术栈的长期依赖。当开发新服务的时候,你可以选新的技术栈。同样的如果想对存在的服务做大的改变,你也可以用新的技术栈去重写它。

缺点

这个解决方案同样也有一些缺点:

  1. 开发者必须解决创建分布式系统的额外复杂度

    1. 开发者必须实现内部服务之间的通信,还有知道如何处理局部错误
    2. 实现跨多个服务的请求是更加困难的
    3. 测试服务之间的interaction是更加困难的
    4. 实现夸多个服务的请求是需要团队之间仔细地合作
    5. 普遍的开发工具都是支持单体架构应用的开发,而没有对分布式应用开发提供明显帮助
  2. 部署复杂度。在生产环境中,部署和管理由许多不同服务组成的系统也存在操作的复杂度。

  3. 增加的内存消耗。微服务架构用NXM个服务的实例,替换了单体应用的N个单体应用实例。如果每个服务都运行在自己的JVM中(为了保持服务之间的隔离性,通常时必须的),那么相对于单体应用就多个M倍JVM运行时的开销。此外,如果每个服务都在自己的VM上运行(比如EC2实例,Netflix),开销就会更高了。

其他问题

如果想要实践微服务架构,还有很多其他的问题需要解决

什么时候用微服务架构?

使用微服务架构的挑战之一就是决定何时用。通常当你开发应用的第一个版本时,你的应用并不存在微服务架构所解决的问题。除此之外,使用分布式合作架构会减慢开发进度,所以这个使用成本对于那些创业公司来说是一个主要问题。因为创业公司一般都要求应用程序能够根商业模型一起快速迭代。使用纵向扩容可能会使得快速迭代更难。还有就是,当你面对扩容挑战时,你需要将应用功能进行解耦,但是此时功能之间紊乱的依赖可能使得将单体应用解耦成服务集合任务的难度极具上升。

如何将一个应用解构成多个服务?
  1. 按照业务能力分解,定义每个业务对应的服务
  2. 按照领域举动设计模式,进行子领域分解
  3. 按照动词(行为)或者用例图来分解,一个服务就是负责一个行为或者用例的全程。比如说运送完整订单的运动服务。
  4. 按照名词(名称)或资源进行分解,比如某个类型的实体资源对应的所有操作都由一个服务来完成。比如负责用户账户的账户服务

理想条件是每个服务只负责一个小范围的责任。Bob Martin提倡的单一责任原则(SRP)中将类的责任定义为更改的原因,并且声明类应该只有一个更改的原因。将单一责任原则应用于服务设计也很很有意义。

另外一个可以帮助服务设计的就是Unix utilities: make each program do one thing well。

如何维护数据一致性?

为了对服务之间的解耦,每个服务都有自己的数据库。那么维护服务之间的数据一致性就成为了一个挑战,因为对于很多应用,2段的分布式事务提交都不是可实现的选项。所以应用必须用saga模式来保证服务之间的数据一致性:服务在数据改变之后发布一个事件,其他服务会监听到这个事件并且对数据进行更新。其中,也有几种方式来保证如何可靠的更新数据、发布时间,比如Event SourcingTransaction Log Tailing.

如何实现查询?

另外一个挑战是:如何实现跨多个服务的查询?

一般是依靠API 重组( API Composition)和CQRS模式(Command Query Responsibility Segregation )

相关的其他模式

微服务架构模式有着很多其他相关的模式。其中单体架构模式是替代模式,而其他相关的模式是用来解决在微服务架构模式下出现的问题

img

存在的案例

很多大型网站包括:Netflix,Amazon和eBay都是从单体架构模式进化到了微服务架构模式的。

Netflix的视频流媒体服务几乎占据了30%的互联王流量,而它的架构就是大型面向服务的架构。平均每天Netflix的视频流API会被全世界800多种不同客户端调用超过10亿次。平均每个视频流API会向后端服务发出6次调用

Amazon.com 最初是一个两层架构。 为了扩展,便迁移到由数百个后端服务组成的面向服务的架构。 很多应用程序调用这些服务,包括实现 Amazon.com 网站和 Web 服务 API 的应用程序。 Amazon.com 网站应用程序调用 100-150 个服务来获取用于构建网页的数据。

拍卖网站 ebay.com 也是从单体架构演变为面向服务架构的。 应用层由多个独立的应用组成。 每个应用程序实现特定功能区域的业务逻辑,例如购买或销售。 每个应用程序都使用 X 轴(横向)拆分,而某些应用程序(例如搜索)使用 Z 轴拆分。 Ebay.com 还将 X、Y 和 Z 样式的缩放组合应用于数据库层。

微服务架构项目实例

Chris Richardson has examples of microservices-based applications.

See also

See my Code Freeze 2018 keynote, which provides a good introduction to the microservice architecture.