原文地址:https://microservices.io/patterns/monolithic.html

场景描述

假设你正在开发一个大型服务端企业应用,有如下需求:

  • 必须支持多种客户端,包括:WEB 端浏览器、WAP 端浏览器以及原生移动 APP。
  • 对外暴露公共 API 用于调用
  • 处理 HTTP 请求,或者消息,执行对应的业务逻辑。
  • 访问数据库,缓存或者持久化响应的数据
  • 与其他系统进行通信,交换所需的信息
  • 返回 HTTP 响应,指定好特定的序列化方式,例如 JSON、 XML 等等
  • 根据业务逻辑与功能,设计并划分出不同逻辑模块

这样的一个应用,你会如何设计架构并部署呢?

考虑因素

  • 这是一个团队开发的项目,有一个独立团队负责
  • 团队成员会发生变化,新加入的成员必须快速上手项目
  • 应用程序必须易于理解并修改
  • 期望能实现应用的持续集成与部署
  • 必须可以多实例部署应用程序,以满足可伸缩性和可用性要求。
  • 想用比较新的技术(框架、编程语言等)

解决方案

使用单体架构,例如:

  • 一个 Java WAR 文件启动的程序
  • 一个单目录 Rails 或者 NodeJS 程序

举例

假设现在正在设计一个电商应用,功能包括接收来自客户的订单(StoreFrontUI),验证并维护库存余额(Inventory Service),验证并维护用户可用余额(Accounting Service),下单成功并发货(Shipping Service)。这个应用被设计成一个单体架构应用,例如:JavaWeb 应用程序由运行在Web容器(如 Tomcat )上的单个 WAR 文件组成。Rails 应用程序由部署在 Nginx 或 Tomcat 上的 JRuby 或 Nginx 上的单一目录层次结构组成。可以在负载均衡器后面部署多个实例,以扩展和提高可用性。

image

分析

这种解决方案的好处有:

  • 开发简单,当前的 IDE 基本都是按照开发单体应用程序开发的。
  • 部署简单,只要把一个文件或者目录部署到 Web 容器里即可。
  • 扩容简单,通过在负载均衡器后面部署多个实例就能实现扩容。

但是,随着产品不断迭代,这个单体应用程序将会变得越来越大,团队的规模也越来越大,这种单体设计就会有一些缺点,并且这些缺点会变得越来越严重:

  • 单体应用代码在同一个代码库,这个代码库会越来越大,使开发人员感觉会很头大,特别是那些刚加入团队的开发人员。应用程序将很难理解和修改,因此,开发速度通常会被减缓。另外,由于没有明确的模块边界,代码内部的模块化会随着时间的推移而越来越模糊。此外,由于很难理解如何正确实现更改,并且可能还需要兼容老版本的错误,因此代码的质量会随着时间的推移而下降,慢慢堆积成为屎山。
  • IDE 的压力会很大。代码库越大,IDE 会更慢,IDE 一般为了智能补全代码的功能,会对代码做索引并加载到内存中。臃肿的代码会拖慢 IDE,降低开发效率。
  • Web 容器压力变大。程序越臃肿,启动时间会被拖长,导致代码调试变慢,同时部署时间也会变长。
  • 持续集成部署难度越来越大。为了更新一个组件,您必须重新部署整个应用程序。这会导致所有业务,不管是否有更新,都被影响或者中断。同时,如果出现问题,回滚时间也会增长。因此,这限制了程序不能持续频繁更新。
  • 不能灵活扩展。不同业务模块可能压力不同,以及压力大的时间段可能也不同,但是每次扩容,都需要所有模块一块扩容,造成了浪费。
  • 故障扩散。如果有一个模块出了问题导致内存泄漏,那么整个业务都会受到影响。
  • 团队分工的障碍。例如,我们可能希望有UI团队、会计团队、库存团队等等。单块应用程序的问题在于它阻止了团队独立工作。小组必须协调他们的开发工作和重新部署。对于一个团队来说,进行更改和更新生产要困难得多。
  • 需要长期使用同一个技术栈。一种单一的体系结构迫使您与您在开发开始时所选择的技术堆栈(在某些情况下,与该技术的特定版本)结合在一起。有了单体应用程序,就很难逐步采用一种较新的技术。比如你使用的框架停止更新,或者过时了,在单体应用下很难逐步采用一个新的框架实现。