作者简介:Chris Richardson,世界著名的软件架构师,经典著作《POJOS IN ACTION》的作者,cloudfoundry.com 的创始人
微服务目前正受到大量的关注,成为文章、博客、会议讨论的热点。与此同时,也有人质疑微服务并非新事物,只是SOA(Service Oriented Architecure)的二度封装。无论是追捧还是质疑,微服务架构拥有巨大的优势,尤其是让敏捷开发和复杂的企业应用支付成为可能。
本系列包含7篇文章,介绍了微服务架构的各个因素,了解微服务模型的优劣,以此来指导微服务是否符合您的项目,如何应用等。
Chris Richardson 微服务系列翻译全7篇链接:
- 微服务介绍(本文)
- 构建微服务之使用API网关
- 构建微服务之微服务架构的进程通讯
- 微服务架构中的服务发现
- 微服务之事件驱动的数据管理
- 微服务部署
- 重构单体应用为微服务
原文链接:Introduction to Microservices
构建单体应用
假设我们要开发一个全新的与 Uber 竞争的打车软件。在需求整理后,需要创建一个新项目,这个应用可应该有如下六边形的架构模块:
应用的核心是业务逻辑:它定义了服务、领域对象和事件模块。各种适配器围绕核心与外部交互,适配器包括了数据库访问组件、生产与消费信息的消息组件、以及API或web UI组件。
尽管按模块化进行设计,整个应用仍需要整体打包、部署。实际格式与选择的编程语言和框架相关,例如:Java 应用会打成 War 包部署到 Tomcat 或 Jetty 等服务器;还有一部分会打成 Jar 包;Rails 和 Node.js 应用直接以目录结构的形式部署。
这种单一应用可以通过 IDE 工具来方便的构建,也易于部署与测试,扩展应用时只需要添加负载均衡。在项目早起,这样做是很有效的。
迈向单体的地狱
很不幸,这种简单的方法存在着局限性:
1)一个成功的应用会随着时间而变的的越来越大。在每个敏捷 Sprint 期间,开发团队会实现更多的功能,添加新的代码。几年之后,当初简单的小应用会复杂到任何一个开发者都无法完全理解,修复 bug 和开发新功能也因此耗时颇多。并且这是一个恶性循环,代码越难理解,正确的修改就越难。最后开发团队则会饱受折磨,苦苦挣扎与敏捷开发和交付中。
2)应用程序越大,启动时间就越长。例如在最近的调查中,不少开发者指出启动时长达12分钟。如果开发过程中频繁的重启应用,那么就会浪费大量的时间,效率自然就低下。
3)庞大复杂的单体应用另一问题就是难以持续交付。现在SaaS应用的宗旨是如果有改动,能够每天在生产环境部署多次。然而要让复杂的单体应用达到这个水平却很困难。如果更新应用的某个部分,必须重新部署整个应用,启动一次的时间就很漫长,而且不能完全预期修改的影响,不得不进行大量的人工测试。结果就是,持续部署变的不可能。
4)单体应用在多个模块对资源需求有冲突时很难扩展。例如:模块1实现了 CPU密集型的图像处理逻辑,最适合部署到 Amazon EC2 Compute Optimized instances;而模块2需要内存数据库,更适合部署到 EC2 Memory-optimized instances,这两个模块一起部署时,不得不在硬件方面进行妥协。
5)单体应用的另一问题就是可靠性。所有模块运行在同一进程中,任何模块的一个bug(例如:内存泄露)都可能拖垮整个应用。
6)单体应用很难拥抱新的框架和编程语言。例如:你有200万行代码性 XYZ 框架,如果需要使用 ABC 框架重写,将会耗费大量的时间和人力。这就在尝试新技术时候存在巨大的阻碍。
最后总结一下,从一个业务清晰,几个程序员就能理解的小程序,逐步成长为一个臃肿、无法理解的庞然大物。使用过时、效率低下的技术来实现(毕竟技术在进步),招聘都变的困难。整个应用扩展性、可靠性差,敏捷开发和持续交付几乎成为不可能。
面对这些,该何去何从?
微服务-处理这些复杂问题
很多公司,例如Amazon、eBay、Netflix,都已经通过拥抱微服务来解决以上问题了,他们不再是构建一个可怕的单体应用,而是通过微服务架构将应用拆分为更小的、相互连接的服务。
一个微服务一般完成某个特定的功能,例如:订单管理、客户管理等。每个微服务都是一个小应用,有自身的逻辑以及适配器来构成六边形架构。有的微服务会暴露 API 供其他微服务或客户使用,有的微服务会实现 Web UI。运行时,每个实例通常是一个虚拟云主机或 Docker 容器。下面是对上述老架构的拆分:
应用的每个功能都由自身微服务实现。整个应用被拆分为一系列更小的 Web应用(例如:乘客管理、司机管理)。拆分后更方便为特定用户、设备或案例而单独部署。
每个后端服务暴露 REST API,也会调用其他服务提供的 API。例如:司机管理服务会使用 通知服务 来告诉司机的行程;UI服务调用其他服务来呈现页面。服务之间也可能使用异步的消息通信。
部分 REST API 也会提供给司机和乘客的移动 APP 使用,这些应用不能直接访问后端服务器,而是通过 API网关 来协调访问。API网关的职责有:负载均衡、缓存、访问控制、API计费、监控等。
上图是 Scale Cube 的 3D 模型,来自《The Art of Scalability》一书,应用一般以3个维度进行扩展:
- X轴 :水平扩展,通过克隆的方式扩展。一般是负载均衡后运行多个应用副本,达到某个服务的高吞吐和高可用性。
- Y轴 :功能拆分,哦通过拆分不同的事务进行扩展。微服务对应着 Y 轴,将单体应用拆分为微服务。
- Z轴 :数据分区,通过分隔相同的事务进行扩展,例如:数据库分库分表。
下图展示了行程管理服务采用 Docker镜像部署到 AWS EC2上:
行程管理服务由多个实例组成,每个实例就是一个 Docker 容器。为了达到高可用,容器会在多个虚拟云主机上。实例前是 Nginx 负载均衡,将请求分发到全部实例,也处理缓存、访问控制、API测量和监控等。
微服务架构也影响应用和数据库之间的关系。每个服务都有自身的数据库,而不与其他服务共享同一个数据库。这样一来,数据模型会比较奇怪,也会出现部分数据冗余。然而,要想从微服务中受益,这样做还是很有必要的,因为微服务提倡的就是松耦合。下图展示了微服务架构下应用的数据架构:
此外,每个服务还可以选用符合自己特性需求的数据库,例如:司机需要查找附近的乘客,那司机管理服务就需要使用能高效支持地理位置查询的数据库。
表面上看,微服务和 SOA 非常类似,这两种架构都有一系列服务。然而,微服务可以看成没有 web service规范和 EBS套件 约束的 SOA。微服务更青睐采用 REST 这样简单、轻量级的协议,而不是老旧的 web service。微服务也会去避免使用笨重的 EBS 套件而喜欢使用实现 EBS 部分功能的轻量级工具。微服务也避免 SOA 诸如canonical schema 的定义。
微服务的优势
微服务架构有很多好处:
1)通过将巨大的单体应用拆分为多个服务,解决了单体复杂度问题。拆分后整体功能没有改变,但应用变成了多个方便管理的小应用。每个服务通过 RPC 或 消息驱动的 API定义清晰的服务边界。拆分后的服务能更快的部署,更容易理解、开发和维护。
2)拆分后的服务可由更专注的开发团队来维护。程序员可在 API 约定下自由的选择合适的技术。更重要的是,每个服务拆分的很小,使用现有技术重写老的服务也不是很困难的事。
3)微服务架构使得独立部署成为可能。开发者不需要协调其他服务部署对本服务的影响(单体应用,该一部分可能对其他部分产生影响,某个更改可能涉及多个模块的协调),这种改变可以加快部署,快速迭代而不用等整个应用部署。微服务使可持续交付成为可能。
4)微服务使得每个服务独立扩展。可以针对某些有容量和可用性要求的微服务进行扩展,部署多个服务而不是多个单体应用去获得性能提升。可以针对服务需求使用合适的硬件资源,例如:在EC2 Compute Optimized instances 部署 CPU密集型的图片处理服务,在 EC2 memory-optimized instances 上部署有内存数据库需求的服务。
微服务的不足
正如Fred Brooks 30年前所说:『没有银弹』,微服务也有其不足和挑战:
1)劣势之一就是它的名字,微服务过分强调了服务的大小,实际上有开发者号召大家写10-100行代码的微服务。然而微服务更想表达的是一种工具和途径,并不是最终目的(为微服务而微服务)。微服务是为了便利敏捷开发和部署而去有效的拆分应用。
2)由单体应用拆分为分布式应用带来的复杂。开发者需要基于消息或 RPC 的方式进行进程间的通信,还需要写额外的代码去处理请求超时或不可用导致的局部故障。
3)分区的数据库架构。一个事务中更新多个业务记录是常见的,单体应用实现事务比较简单,毕竟是共用同一个数据库。而微服务架构中,就需要更新多个服务的多个数据库,一般不使用分布式事务,不仅仅是因为CAP 理论,还因为一些流行的 NoSQL 和 MQ 并不支持这一需求。最终还得使用最终一致性方案,而这对开发者提出了更高的挑战。
4)测试微服务的应用也更加复杂。例如,采用了 Spring Boot 这种框架的单体应用,测试它的 REST API比较容易。而在微服务中,需要启动或 mock 其依赖的服务才能完成。
5)跨服务的改动。例如:假设你完成一个需求,需要修改A、B、C服务,而A 依赖 B,B 依赖 C。单体应用中可以简单的修改对应的模块,然后一起部署。而微服务架构下,你需要小心翼翼的计划和协调每个服务的改动和发布:先更新C,再更新B,最后更新A。
6)部署微服务应用也更加复杂。单体应用都是相同的,拷贝部署到负载均衡后面就行了。而微服务应用由大量的服务组成,例如:NetFlix 有超过 600 个服务。就有很多部分需要去配置、部署、扩展和监控。此外还需要实现服务发现机制,用来让服务找到它需要通信的服务的地址。最终,成功部署一个微服务应用需要开发者有足够的部署方法并实现高水平的自动化。自动化的方法之一就是使用Cloud Foundry 这样的 PaaS 服务,让开发者无需纠结于购买和配置 IT 资源。另一种方法是开发自己的 PaaS平台,通常起步方式是使用Mesos 或Kubernetes 这样的集群管理方案,配合 Docker 的容器技术使用。
总结
构建复杂的应用本身就是困难的事情,单体架构在针对简单、轻量级的应用时是好的。但运用在复杂的应用上会变得痛苦不堪。尽管微服务架构有诸多的缺点和挑战,但对于复杂的、演进的应用来讲是一个更好的选择。
后续文章中将介绍微服务的几个方面,讨论一些诸如服务发现、服务部署和重构单体应用到微服务的话题。