微服务架构基础知识(架构设计)(优先看)

架构的演变:单体架构 👉 分层架构 👉 SOA架构 👉 微服务架构

单体架构

单体应用的全部功能集成在一起作为一个单一的单元,比如:

  • Java Web应用程序WAR文件
  • golang交付形态为单一可执行文件

通信方式

  • 进程间通信

  • 方法间调用

  • 无需网络调用

事务管理

  • 单一数据库

  • 所有事务单一上下文

  • 事务提交和回滚简单

  • 一个方法集中处理

优点

  • 易于开发:一个应用(不复杂时)
  • 修改容易
  • 测试方式简单
  • 易于部署(只需复制交付文件)
  • 易于伸缩:多个实例负载均衡

缺点

  • 过度复杂性,代码行过多,修复bug和实现新功能时容易出错
  • 开发缓慢
  • 部署周期长且容易出错,只要有微小变更就要提交到同一代码库
  • 难以扩展,可靠性差
  • 过期技术栈

分层架构

对复杂系统进行抽象和分层、结构化设计

垂直架构

  • 表现层、业务层、数据层

  • 关注点分离原则

    • 责任分离
    • 高内聚低耦合
  • SOLID原则

    • 单一职责原则
    • 开闭原则
    • 里氏替换原则
    • 接口隔离原则
    • 依赖倒置原则

示例:

SOA架构

SOA即面向服务架构,是一个分布式组件的集合,这些组件为其他组件提供服务,或者消费其他组件的服务

SOA还需要ESB(企业服务总线)的支持,其为服务间的相互调用提供支持环境,路由服务间的消息,并对消息和数据进行必要转换

另一个角色是服务编排引擎,它根据预先定义的脚本对服务消费者和服务提供者之间的交互进行指挥

优点

  • 服务本身是高内聚,服务之间是松耦合的,最小化开发维护中的相互影响
  • 良好的互操作性
  • 模组化,高可重用
  • 服务动态识别、注册、调用

缺点

  • 系统复杂度提高
  • 难以测试验证
  • 各独立服务的演化不可控
  • 中间件容易成为性能瓶颈

示例:

SOA实现原则

  • 服务解耦:服务之间的关系最小化
  • 服务契约:服务按照文档约定行事
  • 服务封装:对外隐藏实现逻辑
  • 服务重用:将逻辑分配在不同服务中以重用
  • 服务组合:服务协调工作,组合起来满足业务需求
  • 服务自治:服务对自己封装的逻辑有控制权
  • 服务无状态:服务将一个活动所需保存的资讯最小化

微服务架构

微服务架构是把应用程序功能性分解为一组服务的架构风格,每一个服务都是由一组专注、内聚的功能职责完成。

微服务架构将单体应用拆分为细粒度的服务,并使其运行在独立进程中,服务之间采用轻量级通信机制(如HTTP RESTful API)进行交互的架构风格。这些服务围绕系统的业务能力构建,且可以通过全自动的部署机制进行独立部署。服务可以进行分布式管理,从而支持不同的编程语言进行开发和不同的数据存储技术进行存储。

微服务架构示例1:

微服务架构示例2:

特点

通过服务组件化

  • 组件:指可以独立替换和升级的软件单元
  • 进程内组件:
    • 类、对象或库
    • 一般直接调用
  • 进程外组件
    • 独立服务
    • web服务请求或RPC通信
    • 轻量级消息传递
    • 明确的组件发布接口

围绕业务能力组织

  • 采用围绕业务能力的划分方法
  • 采用产品开发模式:开发团队负责软件的整个产品周期,持续关注软件如何帮助用户提升业务能力,实现价值交付

内聚和解耦

  • 内聚:单一职责
  • 解耦:微服务间尽量减少直接依赖;要能澄清边界,每个微服务有独立业务能力

去中心化

  • 去中心化治理:微服务有自己的技术栈选择;只需约定接口;运维只需要了解服务部署规范
  • 去中心化数据存储:每个微服务管理自己的数据库
  • 去中心化数据管理:强调服务间无事务协作,最终一致性

基础设施自动化

  • 依赖自动化的基础设施,降低开发和运维微服务的操作复杂度
  • 持续部署和交付
  • 自动化测试

服务设计和演进

  • 高可用设计:
    • 容忍服务失败但客户端要做出响应
    • 需要完善的日志与监控
    • 尽早修复错误
  • 演进式设计:传统架构软件变更难以预测且改造成本高;微服务架构合理设计实现频繁、快速且控制良好的增量变更和演化

总结

对比SOA和微服务架构

微服务设计核心模式

考虑迁移动机、挑战问题、模式和模式语言、微服务拆分设计和其他核心模式

迁移动机

随着时间的推移,系统的复杂度逐渐提高,被废弃的风险逐渐增加,且需要权衡的质量属性也越来越多

仅采用单体架构存在“单体地狱”的风险:

  • 过高的复杂度会吓退开发者
  • 代码到实际部署的周期长,容易出问题
  • 难以扩展、难以实现可靠交付
  • 可能需要长时间依赖过时技术栈

同时在现代开发中,流程逐渐转向DevOps、持续交付持续部署。

微服务架构直接支持DevOps流程:

  • 拥有DevOps所需的可测试性(服务较小)、可部署性(独立部署)
  • 开发团队松耦合

挑战问题

  1. 服务如何拆分与定义?结果如何?👉 粒度问题
  2. 分布式系统带来的复杂性,比如:
    • 进程间通信复杂度高于方法调用
    • 跨服务事务和查询
    • 运维复杂

模式和模式语言

模式是针对特定上下文(问题场景)中发生的问题可重用解决方案

模式语言则帮助解决微服务架构设计问题,例如:拆分问题、数据问题、通信问题、部署问题、运维问题等

模式语言往往指一组模式,例如:

  • 应用相关模式组——拆分模式、数据模式
  • 应用基础设施相关模式组——通信模式
  • 基础设施相关模式组——部署模式

微服务拆分设计(拆分模式)(优先看)

问题来自于如何将应用拆分为合适粒度的微服务?如何划分边界?

我们希望:高内聚、低耦合、SOLID原则…

关键步骤

  1. 定义系统操作:将需求提炼为系统必须处理的关键请求
  2. 定义微服务:围绕业务而非技术
  3. 定义服务API和协作方式

定义系统操作

输入:需求,用户故事或场景等

步骤1:创建领域模型

分析用户故事中频繁出现的名词,创建领域模型(可以是抽象的)

示例:标红的即为故事中频繁出现的名词,为它们创建领域模型

关键类及关系如下:

步骤2:确定系统操作

分析用户故事或场景中的动词,确定系统操作。比如增删改查等命令型操作、前端向后端发出的请求、后端处理数据等。

系统操作要规范,比如命令需要说清参数、返回值,类的行为要有前置和后置条件。

示例:

根据业务能力拆分

业务能力是一种业务架构建模的术语,它的核心如下:

  • 能为企业产生价值的商业活动。这是最核心的一点,业务能力代表了企业为了实现其商业目标而执行的关键活动,直接或间接地带来收入。

  • 相对稳定。企业的核心业务能力通常不会频繁变动。例如:

    • 保险公司的核心业务始终是承保和理赔
    • 在线商店的核心业务始终是销售商品和处理订单

    技术的进步可能改变这些能力的实现方式,但能力本身是持久的。

通常一个业务能力会对应特定的业务对象,比如:

  • “订单管理”能力对应“订单”这个业务对象。
  • “库存管理”能力对应“商品库存”这个业务对象。
  • “消费者管理”能力对应“消费者”这个业务对象。

能力可以分解为子能力,比如“订单获取和履行”可以进一步被分解为:

  • 创建和管理订单
  • 餐馆管理订单生产过程
  • 送餐
  • 送餐员实时状态管理
  • 配送管理

完整的业务能力示例:

接下来就是从业务能力到服务

  • 顶级能力或子能力可以被映射到服务,例如:
    • 顶级能力映射:用户(消费者)服务、会计记账服务
    • 子能力分解映射:供应商管理提供2个服务(两个不同的供应商),订单获取和履行提供3个服务(三个阶段)
  • 这当然有不小的主观性,且分解方式会根据通信效率、变更频率等灵活变化。例如分离变与不变

优点

  • 架构稳定
  • 开发团队跨职能、自治,围绕交付业务的价值进行组织
  • 高内聚低耦合

问题

如何分析业务能力?可能的出发点是分析组织的目的、结构、业务流程

根据子域拆分

子域是领域驱动设计方法论的核心

领域驱动设计(DDD)

  • 解决复杂软件业务领域范围或业务边界划分的问题
  • 从业务出发,以面向对象和领域模型为核心

领域

  • 描述问题域,一种特定范围,比如电商、外卖、保险
  • 子域是领域的细分,比如电商 👉 订单、商品、物流等
  • 领域分为核心域、通用域、支撑域

核心思想和流程

  1. 将问题域逐渐细分,降低单步的实现复杂度
  2. 从业务需求中提炼统一语言
  3. 战略设计:
    • 构建领域模型,识别限界上下文,确定边界
    • 上下文映射建立领域间关系
    • 划分微服务边界
  4. 战术设计:指导设计、编码、重构

通用语言

定义领域内相关团队词汇表,需要统一、简单清晰准确地描述业务

领域模型

  • 包含一个领域的知识,为了解决具体问题
  • 每一个子域都有单独的领域模型

限界上下文

  • 领域模型的边界
  • 包括实现模型的代码集合
  • 对应微服务架构中的一个或一组服务

优点

  • 高内聚低耦合
  • 子域和限界上下文与微服务边界匹配
  • 架构稳定
  • 领域模型是团队独立开发的,很好地支持团队自治
  • 子域使用自己的领域模型,避免上帝类

问题

如何识别子域?一个思路是分析业务及其组织结构,并识别不同专业领域

根据动静态调用关系拆分

  1. 收集单体应用动静态调用信息
    • 静态:用例分析、API接口
    • 动态:调用链路、数据流图、控制流图
  2. 构建有向带权图,比如根据调用频率、变更频率作为权重
  3. 根据聚类算法拆分

优点

自动化效率高

缺点

受限于遗留系统的分析和数据收集难度

API定义与实现

步骤一:将系统操作分配给服务

  1. 确定请求的初始入口服务
  2. 映射不清晰的服务:将操作分配给需要操作提供信息的服务
    • 例如下面例子中的noteUpdatedLocation()操作
  3. 其他情况把操作分配给具有处理它所需信息的服务
    • 如下例中的noteOrderReadyForPickUp()操作

示例:

步骤二:确定支持协作的API

  • 某些操作可以由单个服务处理
    • verifyConsumerDetails()
  • 某些操作跨多个服务(数据分散)
    • createOrder()调用多个服务:验证前置条件并使后置条件成立
    • acceptOrder()调用配送服务安排送餐员、同时交付订单
  • 进程间通信

重构策略

策略一:将新功能实现为微服务

  • API网关负责把新功能的请求路由到新服务,旧系统的请求仍然路由到原来旧的单体服务
  • 集成胶水代码:结合微服务与单体,访问单体所有数据,并能够调用单体实现的功能

策略二:提取业务能力到服务中

以Delivery Service为例:

  1. 拆分源码,转换为模块
  2. 拆分数据库
  3. 部署Delivery服务
  4. 调用Delivery服务
  5. 删除单体应用中的遗留代码

其他核心模式(通信模式)(优先看)

服务注册与发现

问题背景:基于RPI的客户端如何在网络上发现服务实例的位置?

应用层服务发现模式

  1. 自注册:服务实例调用注册API来注册其网络位置
  2. 客户端发现:客户端查询服务注册表来获取服务实例

优点:处理多平台部署的问题

缺点:需要为使用的每种编程语言提供服务实现库,且开发者需要额外设置和管理注册表

平台层服务发现模式

  1. 第三方注册:由第三方(注册服务器)处理注册,而不是服务本身向服务注册表注册自己
  2. 服务的发现:客户端无需查询服务注册表,而是向DNS名称发出请求,该请求被解析到路由器,路由器查询服务注册表并对请求进行负载均衡

优点:完全交给部署平台,服务端和客户端减负;多语言支持度高

缺点:存在平台约束

API网关

问题背景:如何处理外部客户端与服务之间的通讯?

我们需要支持多种客户端的API:因为不同客户端需要不同的数据,性能要求也有区别

API网关模式实现一个服务以及一些外部API,外部API是客户端进入基于微服务应用的入口点;针对不同客户端提供不同的API

优点

  • 封装应用程序内部结构,减少交互次数
  • 确保客户端不受服务端实例位置影响

缺点

性能、可扩展性受影响,局部故障的影响大

断路器(服务熔断)

问题背景:同步通信中如何避免由于服务故障或网络中断引起的故障蔓延到其他服务?

处于这种需求的考量,我们引入熔断器,它有三种状态:

  • 断开:不熔断服务
  • 闭合:熔断服务
  • 半断开:尝试状态,如果服务调用失败则回到闭合状态,否则断开熔断器

优点

  • 防止不断重复尝试造成的失败恶化已崩溃的服务实例
  • 使程序能够诊断错误是否修正(半断开状态)

微服务架构实现

技术选型示例:轻量级

  • 开发服务:Spring Boot
  • 封装服务:Docker
  • 部署服务:Jenkins
  • 注册服务:ZooKeeper
  • 调用服务:Node.js

微服务架构示例:

还有个微服务部署模式。。。😅