第 4 章:事件驱动微服务:不只是请求/响应

本章系统阐释了请求/响应与事件驱动两种微服务编程模型的核心原理、实现方式及在云原生环境下的适用场景,帮助开发者理解如何选择和组合架构模式以提升系统弹性与可维护性。

本章要点

  • 如何使用请求/响应编程模型
  • 如何使用事件驱动的编程模型
  • 如何在云原生软件中使用这两种模型
  • 理解模型之间的相似性和差异性
  • 如何使用命令查询责任隔离(CQRS)模式

云原生软件的主要实现之一是微服务。合理拆分和组合微服务能提升开发效率和系统弹性,但也需避免组件间过度耦合,防止形成新的单体系统。本章将深入探讨微服务间的基本调用风格——请求/响应与事件驱动,并分析它们在云原生环境下的应用与挑战。

我们(通常)学习的是命令式编程

大多数开发者最初学习的是命令式编程语言(如 Python、Java、Golang),强调顺序执行和请求/响应思维。在单进程环境下,这种模型高效且易于理解,但在分布式系统中,服务间的请求/响应依赖会带来可用性和弹性挑战。

以 Netflix 为例,用户访问主页会触发大量下游微服务请求。如下图所示:

图 4.1 来自 Netflix 的 Scott Mansfield 的演示,显示了一个用户访问主页的请求,会导致向下游微服务扩散出大量的请求。
图 4.1 来自 Netflix 的 Scott Mansfield 的演示,显示了一个用户访问主页的请求,会导致向下游微服务扩散出大量的请求。

如果所有下游请求都必须成功,整体可用性会急剧下降。Netflix 通过缓存、重试等模式提升系统弹性,但请求/响应仍是主流范式。

重新介绍事件驱动的计算

事件驱动系统中,代码执行由事件触发,触发方无需等待响应。如下图对比:

图 4.2 对比请求 / 响应与事件驱动两种调用风格。
图 4.2 对比请求 / 响应与事件驱动两种调用风格。

左侧为请求/响应,服务间强依赖;右侧为事件驱动,服务间松耦合,事件与结果无直接关联。事件驱动模式提升了系统自治性和弹性。

我的全球食谱:两种架构实践

以“全球食谱”网站为例,目标是聚合关注对象的最新帖子。系统由“关系服务”、“帖子服务”和“相关帖子服务”三部分组成。

图 4.3 食谱聚合服务组件关系示意图
图 4.3 食谱聚合服务组件关系示意图

请求/响应实现

最自然的实现方式是请求/响应:前端请求“相关帖子服务”,后者依次调用“关系服务”和“帖子服务”,聚合结果后返回。

流程如下:

  1. 前端请求相关帖子服务,带用户标识。
  2. 相关帖子服务请求关系服务,获取关注列表。
  3. 关系服务返回关注对象列表。
  4. 相关帖子服务请求帖子服务,获取关注对象的帖子。
  5. 帖子服务返回帖子列表。
  6. 相关帖子服务聚合数据并响应前端。
图 4.4 根据一系列相互调用的请求和响应,来展示页面上的内容。
图 4.4 根据一系列相互调用的请求和响应,来展示页面上的内容。

代码实现采用 Spring Boot,三个微服务打包在同一个 JAR(实际生产不推荐)。相关帖子服务通过 RestTemplate 依次请求关系和帖子服务,聚合结果后返回。

// 相关帖子服务聚合逻辑(简化示例)
@RequestMapping(method = RequestMethod.GET, value = "/connectionsposts/{username}")
public Iterator<PostSummary> getByUsername(@PathVariable("username") String username, HttpServletResponse response) {
    // ...获取关注列表和帖子列表,聚合结果...
    return postSummaries;
}

该实现依赖所有下游服务可用,网络稳定,系统弹性较弱。

事件驱动实现

事件驱动架构下,关系服务和帖子服务在状态变更时主动发送事件,相关帖子服务订阅并处理事件,维护本地聚合结果。前端请求时,相关帖子服务直接查询本地数据库,无需依赖下游服务。

图 4.7 事件驱动服务间事件流示意图
图 4.7 事件驱动服务间事件流示意图

事件驱动实现流程:

  • 关系服务或帖子服务状态变更时,主动发送事件(如 HTTP POST)。
  • 相关帖子服务接收事件,更新本地数据库。
  • 前端请求时,相关帖子服务直接返回聚合结果。

代码实现:

// 相关帖子服务事件处理逻辑(简化示例)
@RequestMapping(method = RequestMethod.POST, value="/posts")
public void newPost(@RequestBody Post newPost, HttpServletResponse response) {
    // ...处理事件,更新本地数据库...
}

事件驱动架构提升了服务自治性和弹性,但事件传递需保证可靠性(如消息队列)。

命令查询责任隔离(CQRS)模式

CQRS 模式将读写逻辑分离,分别用不同模型和控制器实现。如下图所示:

图 4.16 将写逻辑与读逻辑分离,使得你可以用不同的模型来解决不同的问题。这会让代码更加优雅、更易于维护。
图 4.16 将写逻辑与读逻辑分离,使得你可以用不同的模型来解决不同的问题。这会让代码更加优雅、更易于维护。

读写分离有助于复杂业务场景下的代码维护和扩展,尤其在事件驱动架构中更为常见。命令端可采用异步或 FaaS 实现,查询端则适合 REST/HTTP 协议。

不同风格,相同挑战

请求/响应和事件驱动两种模式在理想环境下可实现相同功能,但在云原生分布式环境下,网络分区、服务波动等问题会影响系统表现。

  • 请求/响应模式常用重试机制应对网络分区。
  • 事件驱动模式则依赖消息队列(如 Kafka、RabbitMQ)保证事件可靠传递。

如下图展示两种模式在分布式环境下的补偿机制:

图 4.17 在请求 / 响应的微服务架构中,重试是补偿网络分区的一个关键模式。在事件驱动的系统中,事件存储是对网络不稳定性的一个关键补充。
图 4.17 在请求 / 响应的微服务架构中,重试是补偿网络分区的一个关键模式。在事件驱动的系统中,事件存储是对网络不稳定性的一个关键补充。

后续章节将深入探讨更多云原生架构模式及其在不同协议下的应用。

总结

  • 请求/响应与事件驱动均可用于微服务间通信,且可组合使用。
  • 理想环境下,两种模式可实现相同功能,但分布式云环境下表现差异显著。
  • 事件驱动架构提升系统自治性和弹性,适合高可用场景。
  • CQRS 模式有助于复杂业务的读写分离和代码维护。
  • 云原生架构需结合多种模式,灵活应对分布式系统的挑战。

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区