本文参考文献----《微服务架构与实践(第2版)》电子工业出版社出版 王磊 等著


微服务关键技术

服务划分原则

  • 单一职责原则:高内聚、低耦合
  • 服务依赖原则:避免循环依赖、根据业务重要性进行划分(核心服务于非核心服务),避免核心服务依赖非核心服务
  • 服务自治原则

服务划分策略

  • 根据业务功能垂直划分-
  • 根据数据模型划分
  • 根据界限上下文划分
  • 根据非功能性划分
  • 复用性
  • 资源等级一致性
  • 部署升级频率
  • 伸缩性

如何衡量服务划分的合理性

  • 服务是否能独立交付
  • 服务所属的团队规模:尽量8-10人的团队规模是否破坏服务依赖原则:核心服务的可靠性是否依赖于非核心服务?原子服务的修改次数是否多于组合服务?是否存在循环依赖?
  • 是否满足单一职责原则

服务实现

单服务内部结构如下图
微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
资源定义:请求端与服务端交互时,服务端响应对业务模型的表现形式。
业务逻辑:业务逻辑是对业务行为的抽象。
业务模型:对业务领域实体的抽象。业界已经存在一些成熟的方法论和准则,譬如面向对象设计、SOLID原则、领域驱动设计等,有效地帮助业务领域的实体抽象成业务模型。

对于业务逻辑较为简单的服务(例如提供对业务对象数据的增、删、改、查的服务),通常采用贫血模型,将简单的逻辑处理放置于业务层。贫血模型用来实现基本数据的存储,它的典型特征是模型只包含一些属性及对属性的get方法、set方法、不包含其他方法。因此采用贫血模型,业务逻辑通常位于服务层,模型只是起到数据维护的作用。

对于复杂业务,可以考虑采用领域驱动设计DDD的方法辅助分析。模型里面除了包含状态之外,也需要包含行为,这便需要应用到充血模型。使用充血模型的对象自治程度很高,表达力很强,可复用程度比较高。

充血模型遵循面向对象的本质,也就是模型中同时包含属性和行为。在这种情况下大部分业务逻辑都处于模型中,而业务层只是简单的封装及调度。

模型存储:屏蔽了数据存储和获取的实现细节,让调用者更关注接口而非实现。除此之外,我们可以使用对象关系映射(ORM)或更轻量级的数据映射器来简化数据持久化的工作。或者也可以将持久化的逻辑封装在模型存储中。

网管集成:网关集成部分负责与其他服务协作,通过访问其他服务暴露的接口,获取数据或者提交数据。

服务间的通信方式

  • 同步通信机制:RPC和REST

    • RPC:
      微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
      -REST
      微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
  • 基于消息的异步协作
    消息队列是一种处理节点之间异步通信的实现方式。发送消息的一端称为发布者,接收消息的一端称为消费者。消息队列的核心部分如下:

  • 持久性:消息可能保存在内存中,写入磁盘,或者提交到数据库。

  • 排队标准:消息队列会指定相应的标准和算法,保障消息进出队列的优先级。譬如常见的FIFO算法

  • 安全策略

  • 清理策略:消息队列会为处理过的消息提供清理策略,保障消息的有效清理机制

  • 处理通知:消息队列提供某种通知机制,帮助消息发布者知道何时部分或全部接受者收到消息

对于消息队列的访问,一般存在如下两种方式:

  • 拉模式(Pull Mode):拉模式要求消费者定期检查队列上的消息。如果找到一条匹配的消息,消费者会从队列中获取该消息,并进行处理。如果没有找到,则消费者会在指定的时间段内再次尝试。拉模式下一般存在一个发布者和一个消费者。
  • 推模式(Push Mode):推模式是每当发布者将消息添加到队列中时,会通过某种机制通知消费者。意味着消息处理更及时,但同时也意味着发布者、消费者以及队列必须依赖语言或者平台的某些特性,做更多的开发和配置工作。推模式下一般存在多个消费者,也称为订阅者。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    业界常用的消息队列有RabbitMQ、ActiveMQ、ZeroMO、Kafka

消息队列优点:

  • 服务间解耦
  • 异步通信
  • 消息持久化以及恢复支持

消息队列缺点:

  • 实现复杂度增加
  • 平台或者协议依赖
  • 维护成本高
  • 学习曲线陡

相对简单的异步场景中使用后台任务处理系统来解决,比如当用户点击注册按钮后,希望后台服务处理发送【欢迎】电子邮件,但同时要求该操作不会影响用户在页面上的其他操作。这种场景可以就可以使用后台任务系统解决

后台任务处理系统:有点类似windows的任务队列功能

  • 任务:任务是指后台任务处理系统中可执行的最小单元
  • 队列:队列主要存储任务,并提供任务执行失败后的错误处理机制,譬如失败重试、任务清理等,通常也称为任务队列。每一个任务队列都有一个全局唯一的名称作为标识。
  • 执行器:执行器主要负责从队列中获取并执行任务
  • 定时器:定时器主要设置执行器运行的周期,譬如每1分钟或者3分钟运行执行器并执行任务。

为了保持轻量级的通信机制,使不同的服务之间能通过后台任务系统执行异步通信,通常会保持任务的轻量级,不会在任务中实现过多的逻辑,而是尽量做到由任务回调具体的服务来完成交互,类似流程如下:
微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
1. 将服务A(请求者)与服务B(响应者)交互的部分,定义成任务
2. 服务A调用任务处理系统的API或者SDK,将任务存入队列
3. 执行器根据调度器设置的周期,定期去除任务并执行。
4. 任务执行时,回调服务B提供的接口。

微服务下已有的后台任务处理系统有:Resque、Sidekiq

服务设计模式

  • 链式模式:当某业务功能按照数据的流动,拆分成多个服务,并且服务之间形成链式的请求/响应关系的模式时,就称其为链式模式。它与设计模式中的责任链模式类似,请求经过每个服务时,都会被服务的相应逻辑处理。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    链式模式的适用场景:按照数据的流向进行服务拆分是链式模式的主要应用场景,使用时通过链式调用完成某业务功能。例如在订单服务中,对于订单总价的计算,需要调用计价服务,而计价服务需要进一步调用税务服务计算税费,订单服务、计价服务以及税务服务就形成了链式模式。

    链式模式的注意事项:

    1. 链式模式不适合服务之间所传递数据量太大的场景,以及对调用可靠性要求较高的场景。
    2. 由于整个调用链采用同步机制通信,调用完成前消费者会一直处于阻塞状态。当代用链较长时,会影响整体调用的性能和可靠性。另外,链式模式下只能串行调用,无法并行处理。
  • 聚合器模式:当某服务需要调用其他多个服务,进行业务功能的组装时,便形成了聚合器模式。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    聚合器模式的适用场景:

    1. 对业务集中控制:希望采用一个集中式的机制,对整个系统进行编排,驱动业务组装的流程。
    2. 服务间需要并发处理:当需要并发地调用多个服务时,可以采用聚合器模式,提高请求、响应的处理效率。

    聚合器模式不适合的场景:

    1. 不希望业务逻辑过度膨胀:采用聚合器模式时,聚合服务作为中心控制点,承担了太多职责,极易产生【上帝】服务,而它所编排的服务,通常会沦为【贫血】的基于CRUD的服务。这种情况不适合采用聚合器模式。
    2. 使用聚合器模式的注意事项:聚合器模式的优点是能够在聚合服务中清晰地看到业务逻辑的组装。不过,聚合器模式的缺点是难以保证数据一致性。譬如下单的过程需要经过库存查询、价格计算、订单提交、库存修改等环节,如果库存修改失败,而订单已经生产,则会一起数据的不一致。对于这种分布式事务,可以参考2PC、3PC、TCC、Sagsa,或者采用事件驱动的方式进行处理。另外,由于跨服务无法实现单体应用中的数据库联合查询,其可能会带来性能的损耗。
  • 基于事件的异步模式:上述的链式模式和聚合器模式,大多是采用同步的通信机制,因此会造成消费者的阻塞。采用基于事件驱动的方式,可以通过异步的机制实现服务聚合。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    如上图所示,消息是连接服务A和B、C之间的媒介。存在一个消息队列,服务B和服务C对消息队列里中A的事件进行订阅。当服务A的状态发生改变时,它发布事件到消息队列。当B和C收到事件时,分别更新各自的状态,同时可能发布新的事件,触发下一步操作。这就是典型的基于消息队列,进行异步事件处理的机制。

    异步模式的适用场景:

    1. 对用户体验有及时性要求,不希望被阻塞
    2. 待处理的任务较独立,可以同时运行。

    不适用的场景:

    1. 任务之间存在高度的顺序依赖关系。

    注意事项:在基于消息的异步模式中,服务间协作的关键点是消息。因此要保证消息发布与服务的状态保持一致。例如,订单服务生产订单,同时发布【订单生成】事件,者两操作需要保持原子化。如果订单服务在更新数据库后、发布事件前崩溃,则需要考虑使用回滚或者补偿的方式,使状态一致。

  • 事件溯源模式:采用以事件为中心的方法保存业务实体。 每当业务实体的状态发生改变时,发布新事件到事件仓库(Event Store)中,系统可以通过事件回放来重新生成实体的当前状态。鉴于事件保存是一个单一的操作,因此本质上它是原子的。事件长期保存在事件仓库(Event Store)中,通过 API 来添加和检索事件。可以看到,这里的事件仓库起到类似上文中提到的消息代理的作用。当服务订阅事件后,事件仓库通过 API 将所有事件传递到订阅者。事件仓库可以认为是数据库与消息代理的综合体,它是事件溯源模式的核心存储机制。事件溯源模式,如下图所示。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    好处:

    1. 准确的审计日志:在传统的实现方式中,审计功能常需要与业务功能一起来完成,这就需要保证业务数据更新与审计数据添加的原子化。在事件溯源的模式下,每一个状态的变化都对应着一个或多个事件,因此提供了准确的审计日志
    2. 支持系统状态重建:由于事件溯源模式以事件的方式保存了每个业务对象变更的完整历史,因此可以非常直观地实现时序查询,或者重建整个系统在过去任意时刻的状态。

    适合使用事件溯源模式的场景:

    1. 希望获取细粒度的审计日志
    2. 希望记录所发生的事件并能通过重播事件来重建系统

    不适合事件溯源模式的场景:

    1. 数据强一致性的系统:事件溯源用来记录事件而不是状态,遵循的最终一致性,对于需要强一致性和数据实时更新的系统,不适合采用该模式。
  • 物化视图模式:微服务架构中不同服务有各自的数据存储机制,这导致数据关联查询可能要经过多个服务,效率低。为了支持有效的关联查询,可以通过提前生成视图的方式,将所需的结果保存下来,这种模式称为物化视图模式。此外,物化视图模式及其包含的数据是一次性的,他可以完全从源数据中重建。由于物化视图模式不会被服务直接更新,因此可以看作是一个专门的缓存。当视图的源数据更改时,必须更新视图以同步信息。
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    适合物化视图模式的场景:

    1. 聚合服务需要从多个服务中请求数据,在面临性能问题时,可以提前创建物化视图
    2. 数据库连接不稳定时,物化视图可以作为缓存使用
    3. 处于安全或隐私的原因,无法提供访问源数据的特定子集的访问权限时,可以创建物化视图,将特定数据暴露给用户。

    以下场景不适合使用物化视图模式:

    1. 源数据变化频繁,视图更新的开销较大
    2. 对于一致性有较高要求的场景
  • CQRS(命令和查询职责分离)模式:首先明确「命令」和「查询」的概念。
    命令:通常来讲就是指对数据的更新。
    查询:通常来讲就是指对数据的获取。

    在单体应用中,特别是以数据库为中心的管理系统中,命令和查询往往是在同一个数据库的同一组实体上执行。这些实体一般就是关系数据库中表的记录。在增、删、改、查(CRUD)操作中,读写模型经常以同样的形式存在。例如,通过数据访问层(DAO)将用户所需要的数据传输对象(DTO)查询出来并在界面上展示,用户更新 DTO 的某些字段后再由 DAO 将 DTO 保存到数据存储中,传统的增删改查操作如下图:
    微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
    传统的CRUD方法存在以下的缺点:

    1. 在实际应用中,数据的读取和写入的格式之间不一定需要完全匹配。但在同样的读写模型下,二者必须是统一的数据格式。
    2. 当针对同一组数据实体的数据进行并发操作时,要么通过悲观锁来避免更新冲突,其代价是引起系统并发吞吐量的降低;要么通过乐观锁来进行并发更新,但需要处理其可能引起的冲突并回滚操作。这些成本都会随着系统的复杂性和吞吐量的增加而增大。
    3. 每个实体都受读写操作的限制,使得安全性和权限的管理变得更加复杂。

    CQRS的实现方案:命令和查询职责分离(CQRS)是一种读写数据分离的服务协作模式。通过区分命令和查询的操作,将数据的读写分离。用命令和查询职责分离的模式具有一些明显的优势:

    1. 与传统 CRUD 中使用的单一数据模型相比,CQRS 使用单独的查询和更新模型,能够简化设计和实现,增强灵活性。
    2. 查询模型和更新模型可以访问不同的物理存储。通过将数据分成不同的物理存储,能显著地提高性能、可扩展性和安全性,命令和查询职责分离如下图所示。
      微服务架构与实践笔记(二)微服务关键技术之服务划分与实现
      CQRS模式的适用场景:
    3. 数据读取与数据写入的性能需要分开进行优化。例如在许多电商网站里,对于商品信息的读操作次数是写操作的许多倍,在这种情况下可以考虑扩展读模型,而只在一个或几个实例上运行写模型。
    4. 需要跨团队开发协作时。例如一个团队可以专注于写模型的场景,另一个团队可以专注于读模型和用户界面。
    5. 与其他系统集成,特别是与事件溯源相结合时,某个子系统的临时故障不会影响其他系统的可用性。

    以下场景不适合使用 CQRS 模式:

    1. 领域或业务规则简单。CQRS 实现需要较高成本,通常可以将 CQRS 只应用于系统中最有价值的部分。
    2. 简单的 CRUD 的逻辑以及相关的数据访问操作。

上一篇:微服务架构与实践笔记(一)微服务基础
下一篇:微服务架构与实践笔记(三)微服务关键技术之服务接入

相关文章: