微服务架构设计模式(一)逃离单体地狱

逃离单体地狱

第一节 为什么要使用微服务架构

1.1 单体架构的优点

  • 应用开发简单:只须构建一个项目。
  • 方便进行大规模修改
  • 测试简单直观:测试只须启动程序,调用REST API,使用如Selenium等测试工具。
  • 部署简单:复制war包到Tomcat。
  • 横向扩展容易:只须部署多个实例并进行负载均衡。

1.2 单体地狱

  • 程序代码规模不断壮大,复杂性越来越高。
  • 开发速度缓慢,编译、构建、运行都十分耗时。
  • 代码提交到实际部署周期长,且容易发生问题:代码太过复杂,一个更改所造成的影响难以预测,导致即使是微小的修改也需要测试人员全面的测试。
  • 难以扩展:不同模块需求如内存、CPU等需要同时被满足。
  • 缺乏可靠性:应用庞大难以进行全面的测试,错误的代码会进入生产环境;应用缺乏故障隔离,一处内存泄漏会导致应用所有实例崩溃。
  • 长期依赖过时的技术栈:单体架构难以更新框架和语言,因为应用要被重写。

架构并不影响功能性需求(业务),主要影响的是非功能性需求,也叫质量属性或其他能力。最常见的就是影响软件交付速度的可维护性可扩展性可测试性

1.3 扩展立方体

  • X轴扩展:水平复制,通过克隆实例的方式来扩展。
    • 将用户请求通过负载均衡算法路由到几个相同的应用实例。
    • 可以提供吞吐量和可用性。
  • Y轴扩展:功能性分解,通过分解不同的功能模块的方式来扩展。
    • 将应用程序拆分为一组服务,每个服务负责特定的功能,每个服务可以进行X轴或Z轴扩展
    • 解决了开发问题和应用复杂性。
  • Z轴扩展:数据分区,通过类似用户ID切分相似的数据分区的方式来扩展。
    • 每个应用实例仅负载数据的一个子集,路由器根据请求的UserId来决定路由到哪个实例。
    • 可以提供吞吐量和可用性,适用于增长的事务和数据量。

1.4 什么是微服务?

针对微服务的定义有很多,比如强调尺寸的如服务的代码应该在100行内,强调开发周期的如两周内;微服务可以定义为面向服务的架构,由松耦合和具有边界上下文的元素组成

容易理解的概括:微服务是把应用程序功能性分解为一组服务的架构风格,是一种模块化的形式

在单体应用中,模块由一组编程语言所提供的结构(如Java的 Package 或 Jar 这样的构建制品)定义。微服务则使用服务作为模块化的单元,服务的API即自身的边界,只能通过API来访问服务内部的类。

微服务架构的关键特性是每个服务之间都是松耦合的,只通过API进行通信。实现这种松耦合,需要每个服务都有自己的私有数据库

1.5 微服务与SOA的异同

SOA 微服务
服务间通信 智能管道如ESB,往往采用重量级协议,如SOAP或其他WS*标准 使用哑管道,如消息代理,或服务之间点对点通信,如REST或gRPC等轻量级协议
数据管理 全局数据模型并共享数据库 每个服务都有自己的数据模型和数据库
典型服务的规模 较大的单体应用(几个) 较小的服务(几百个)

1.6 微服务架构的优缺点

优点:

  • 使大型复杂应用程序可以持续交付和持续部署:持续交付和持续部署是DevOps的一部分,

    • 三种实现方式:

      • 拥有可测试性:自动化测试是持续交付和持续部署的重要环节,每个服务都很小,所以易于编写和执行自动化测试。
      • 拥有可部署性:每个服务都可以独立于其他服务进行部署,而不需要互相协调。
      • 使开发团队可以自主且松散耦合:开发团队可以分为一个个小团队,每个团队负责一个或多个相关服务的开发与部署。
    • 三个作用:

      • 缩短产品或新功能的上市时间,更快响应客户的反馈。
      • 提供客户所期望的可靠服务。
      • 提高员工满意度,因为他们不用再此处担任救火队员。
  • 每个服务都相对较小容易维护

    • 较小的代码规模更有利于开发者理解。
    • IDE等开发工具使用起来更快速,提高开发效率。
    • 服务启动速度快很多,提高了研发、调试、部署等效率。
  • 服务可以独立部署

    • 每次修改或新增功能后,不需要再将整个应用重新部署。
  • 服务可以独立扩展

    • 服务可以再进行X轴、Z轴扩展。
    • 服务可以部署在适合的硬件上,需要高内存的和需要高CPU的可以分别部署。
  • 实现团队的自治

    • 小规模团队可以自行治理,专注于解决服务内问题。
  • 更易实验和接纳新技术

    • 每一个新服务都可以自由选择适宜的语言和框架,现实往往公司会有很多限制和规范,但至少有了选择的权利。
    • 服务更小也使用新技术重写一个服务变得可能,如果实验失败也可以承担这部分损失而不至于毁掉整个项目。
  • 更好的容错性

  • 可以实现更好的故障隔离,一个服务的问题不会影响其他服务。

缺点:

  • 服务的拆分和定义是一项挑战:
    • 没有一个具体的、优质的算法来完成服务拆分。
    • 一旦拆分失误,可能会构建出一个分布式的单体应用,一大堆紧耦合的服务又必须部署在一起的分布式系统。
  • 分布式系统带来各种复杂性,使开发、测试和部署变得更困难:
    • 服务间必须使用进程间通信机制。
    • 必须处理局部故障和远程服务不可用或高延迟的情况。
    • 每个服务都有自己的数据库,跨服务的事务和查询比较复杂。
    • 运维难度增高,需要高度自动化的技术:
      • 自动化部署工具:如 Netflix Spinnaker。
      • 产品化的PaaS平台:如 Pivotal Cloud Foundry 或 Red Hat OpenShift。
      • Docker容器编排平台:如 Docker Swarm 或 Kubernates。
  • 当部署跨越多个服务的功能时需要谨慎的协调更多开发团队:
    • 需要制定一个发布计划,按照服务的依赖关系排序。
  • 开发者需要思考到底应该在应用的什么阶段使用微服务架构:
    • 项目初创阶段往往并不需要微服务架构来解决问题,此时单体架构是更高效的选择。
    • 微服务是一把双刃剑,不过面向消费者的Web应用或SaaS类复杂程序往往适用于微服务架构。

第二节 模式语言

2.1 为什么需要模式?

1986年,《人月神话》作者曾说过:软件工程的世界没有银弹。任何技术都有其利弊,但开发人员还是会坚信新的技术会是万能钥匙,人的大脑中情绪化的部分会进行大部分的决策。所以我们需要一种客观的工具,来克服情绪化的本能,这种工具即模式(Pattern)。

2.2 什么是模式和模式语言?

模式是针对上下文中发生的问题的可重用解决方案模式语言是解决特定领域内问题的相关模式的集合。1994年,《设计模式:可复用面向对象软件的基础》采用模式语言来解决设计和架构问题,在开发人员中普及了模式的概念。

模式需要描述其适用的上下文环境,结构:

  • 需求(Forces)
  • 结果上下文(Resulting context)
  • 相关模式(Related patterns)

2.2.1 需求-必须解决的问题

需求需要按优先级排序,重要性取决于上下文环境。比如代码要求可读性和好的性能在一些情况下是冲突的,只能尽量满足其中一种。

2.2.2 结果上下文-采用模式后可能带来的后果

包含三个部分:

  • 好处:这个模式的好处和它解决了什么需求。
  • 弊端:这个模式的弊端和它未解决什么需求。
  • 问题:使用这个模式引入的新问题。

2.2.3 相关模式-5种不同类型的关系

相关模式描述该模式和其他模式间的关系:

  • 前导(Predecessor):催生这个模式的需求的模式
    • 如微服务架构模式是除了单体架构模式以外整个模式语言中所有模式的前导模式。
  • 后续(Successor):指用来解决当前模式引入新问题的模式
    • 如引入微服务模式后需要解决如服务发现、断路器等微服务带来的新问题。
  • 替代(Alternative):当前模式的替代模式,提供了另外的解决方案
    • 如单体模式和微服务模式互为替代模式。
  • 泛化(Generalization):针对一个问题的一般性解决方案
    • 如“每主机单个服务”这个模式存在多种不同的技术实现。
  • 特化(Specialization):针对特定模式的具体解决方案
    • 如将服务部署为容器模式是针对“每主机单个服务”的具体解决方案。

通过这些关系可以将相关的模式集合形成所谓的模式语言,共同解决特定领域的问题。

2.3 微服务架构的模式语言

左侧是应用程序架构模式组,应用相关模式组解决引入微服务架构后带来的新问题。

2.3.1 服务拆分的相关模式

根据业务或子域切分服务:

2.3.2 通信的相关模式

分布式系统中进程间通信是重要组成部分,通信模式包括:

  • 通信风格:使用哪一类进程间通信机制?
  • 服务发现:客户端如何获得服务具体事例(如HTTP请求)的IP地址?
  • 可靠性:在服务不可用的情况下,如何确保服务之间的可靠通信?
  • 事务性消息:如何将消息发送、事件发布这样的动作与更新业务数据的数据库事务集成?
  • 外部API:应用程序的客户端如何与服务通信?

2.3.3 实现事务管理的数据一致性的相关模式

为什么常用的两步式提交(2PC)分布式事务机制在微服务架构场景不再适用?而是需要使用Saga来确保数据一致性?

2.3.4 微服务架构查询数据的相关模式

传统SQL JOIN在微服务场景需要从多个服务的数据源获取数据,服务的数据只能通过API访问,所以不能直接针对服务的数据库执行分布式查询:

API组合模式,逐一调用服务的API再把所有返回聚合在一起,即命令查询职责隔离方式(CQRS)。

2.3.5 服务部署的相关模式

微服务架构需要高度自动化部署的基础设施,即一个部署平台,至少有一个简单的界面来部署和管理服务。

2.3.6 可观测性的相关模式

理解和诊断微服务架构下的问题是一项复杂的工作,在返回结果给客户端前,请求往往会在多个服务之间跳转,只看一个日志文件是不够的。

可以通过以下模式设计具备可观测性的服务:

  • 健康检查API:可以返回服务健康状态的API。
  • 日志聚合:把服务产生的日志写入一个集中式的日志服务器,这个服务器可以提供日志搜索,也可以根据日志情况触发报警。
  • 分布式追踪:为每一个外部请求分配一个唯一的ID,用于在各个服务之间追踪外部请求。
  • 异常跟踪:把程序异常发送到异常跟踪服务,这个服务会排除重复异常,给开发者发送告警并跟踪每一个异常的解决。
  • 应用指标:供维护使用的指标,例如计数器等,导出到指标服务器。
  • 审计日志:记录用户的行为。

2.3.7 服务自动化测试的相关模式

单一的服务测试变得更容易,但还要测试不同的服务是否协同工作,避免使用复杂、缓慢和脆弱的端到端测试来测试多个服务。

  • 消费端驱动的契约测试:验证服务满足客户端所期望的功能。
  • 消费端契约测试:验证服务的客户端可以正常与服务通信。
  • 服务组件测试:在隔离的环境中测试服务。

2.3.8 解决基础设施和边界问题的相关模式

每个服务都需要实现与基础设施相关的功能,如可观测模式和服务发现模式。还必须实现外部化配置模式,在运行时向服务提供数据库凭据等配置参数。

2.3.9 安全的相关模式

微服务架构中,用户身份验证的工作由API Gateway完成,它必须将用户信息传递给需要的服务。常见的解决方案是应用访问令牌模式(JWT),将Token传递给服务,服务验证Token获得用户信息。

第三节 流程和组织

架构、流程和组织的关系:

《人月神话》提到沟通成本随着团队规模增加呈O(N^2^)速度上升,解决办法是大团队切分为一个个小团队,每个团队都是跨职能的,可以独立完成开发、测试和部署等任务,而不需要频繁和其他团队沟通或协调。

微服务架构适用于 Scrum 或 Kanban 这类敏捷开发和部署实践,需要积极实践持续交付和持续部署。

持续交付能够以可持续的方式安全、快速的将所有类型的更改(包括新功能、配置更改、错误修复和实验)交付到生产环境或用户手中。

持续交付依赖于高水平的自动化,包括自动化测试。持续部署有效的支持持续交付,每天多次部署到生产环境,很少发生生产中断,并且可以从任意事件中快速恢复。

持续交付和持续部署的四个有用指标:

  • 部署频率:软件部署到生产环境的频率。
  • 交付时间:从开发人员提交变更到变更被部署的时间。
  • 平均恢复时间:从生产环境问题中恢复的时间。
  • 变更失败率:导致生产环境问题的变更提交百分比。

参考:

🔗 《微服务架构设计模式》