使用 Kafka 设计容错微服务

2025 年 5 月 15 日 | 阅读 11 分钟

容错微服务旨在确保系统在部分故障的情况下仍能保持可用。其核心思想是,故障是不可避免的,但其对系统整体功能的影响可以被最小化。

为什么需要容错?

考虑一个音乐流媒体平台,歌曲推荐依赖于一系列服务,例如:

  1. 数据摄入:捕获用户交互。
  2. 分析:处理用户数据以提供推荐。
  3. 交付:向用户提供推荐。

如果其中任何一个服务发生故障,整个推荐系统可能会崩溃。容错可确保单个组件的故障得到隔离,不会导致整个系统宕机。

Kafka 的作用

Kafka 提供以下功能:

  • 异步通信:微服务无需相互等待。
  • 解耦:生产者和消费者通过 Kafka 主题进行交互。
  • 持久性:事件被持久存储。
  • 弹性:副本机制确保即使在 Broker 发生故障时数据也可用。

Kafka 中用于容错的关键组件

理解 Kafka 的构建模块对于设计容错系统至关重要。

a. 主题 (Topics) 和分区 (Partitions)

主题是消息发布的逻辑通道。Kafka 中的每个主题都分为更小的部分,称为分区。这有助于跨多个工作进程同时处理数据,并能防止故障。

示例程序:创建带副本的主题

b. 生产者 (Producers) 和消费者 (Consumers)

生产者将数据发送到主题,消费者从中检索数据。两者都需要处理错误以确保可靠性。

生产者示例:可靠消息传递

输出

Designing Fault-Tolerant Microservices with Kafka

消费者示例:可靠消费

输出

Designing Fault-Tolerant Microservices with Kafka

c. Kafka Broker

Kafka Broker 负责存储和提供主题数据。集群中的多个 Broker 可确保高可用性。

程序:Broker 配置示例

在 server.properties 中

启动 Broker

设计容错微服务

为了理解使用 Kafka 设计容错微服务,我们将通过完整的程序详细探讨以下概念:

  1. 使用 Kafka 解耦服务
  2. 事件溯源 (Event Sourcing) 和 CQRS
  3. 幂等性

每个概念都将通过用例和完整的 Java 实现进行详细阐述。

1. 使用 Kafka 解耦服务

解耦是微服务架构中的一个关键原则。Kafka充当服务之间的中间人,允许它们异步通信。这意味着如果一个服务宕机,其他服务可以继续运行而不会中断。

用例:订单处理

  • 订单服务:创建新订单时会产生一个事件。
  • 库存服务:消费该事件并更新库存。

实施

订单服务:生成事件

每当下新订单时,OrderService 都会向 orders Kafka 主题发布一个事件。

输出

Designing Fault-Tolerant Microservices with Kafka

库存服务:消费事件

InventoryService 订阅 orders 主题,处理消息并更新库存。

输出

Designing Fault-Tolerant Microservices with Kafka

流程

  1. OrderService 向 orders Kafka 主题生成事件。
  2. InventoryService 消费这些事件并更新库存。

2. 事件溯源 (Event Sourcing) 和 CQRS

事件溯源将系统中的每个更改存储为事件,从而能够实现状态转换的完整历史记录。CQRS(命令查询职责分离)将读写操作分开以实现可扩展性。

用例:用户账户管理

  • 命令服务:捕获用户操作并将它们存储为事件。
  • 查询服务:读取聚合数据用于报告。

实施

命令服务:生成事件

CommandService 将所有用户操作发布到 user-events 主题。

输出

Designing Fault-Tolerant Microservices with Kafka

查询服务:读取聚合数据

QueryService 聚合用户操作以进行分析,例如识别登录趋势。

输出

Designing Fault-Tolerant Microservices with Kafka

流程

  1. CommandService 捕获用户操作并将其存储为事件。
  2. QueryService 消费这些事件以进行分析。

3. 幂等性 (Idempotency)

幂等性确保多次处理同一消息会产生相同的结果。这对于涉及重试的容错至关重要。

用例:支付处理

  • 即使支付事件被多次接收,支付也必须只处理一次。

实施

带唯一 ID 的生产者

为每个支付事件生成一个唯一 ID,以确保幂等性。

带幂等性检查的消费者

消费者维护一个已处理支付 ID 的记录。

输出 1

Designing Fault-Tolerant Microservices with Kafka

输出 2

Designing Fault-Tolerant Microservices with Kafka

输出3

Designing Fault-Tolerant Microservices with Kafka

流程

  1. 为支付消息分配唯一 ID。
  2. 跟踪已处理的支付 ID 以避免重复。

Kafka Streams 中容错的关键机制

  1. 状态存储 (State Stores) 和变更日志 (Changelogs)
    • Kafka Streams 使用本地状态存储进行有状态操作。
    • 这些状态存储由 Kafka 中的变更日志支持,从而可以在故障后进行恢复。
  2. 检查点 (Checkpointing) 和偏移量跟踪 (Offset Tracking)
    • Kafka Streams 定期将偏移量提交给 Kafka,允许它从最后一个提交的点恢复处理。
  3. 任务重新平衡 (Task Rebalancing)
    • 在发生故障时,Kafka Streams 会将任务重新分配给健康的实例。
  4. Exactly-Once 语义
    • Kafka Streams 使用幂等生产者和...事务性写入.
  5. 复制的变更日志
    • Kafka 会跨 Broker 复制变更日志以确保持久性。

Kafka Streams 中容错的示例程序

1. 设置简单的 Kafka Streams 应用程序

此程序演示了一个基本的单词计数应用程序,并支持容错。

流配置

输出

Designing Fault-Tolerant Microservices with Kafka

容错的关键特性

  1. 状态存储 (word-count-store)
    • 由 Kafka 变更日志支持,确保在故障期间状态持久化。
  2. Exactly-Once 处理 (StreamsConfig.EXACTLY_ONCE_V2)
    • 保证不丢失或重复数据。
  3. 状态目录 (StreamsConfig.STATE_DIR_CONFIG)
    • 存储本地状态以加快恢复速度。

2. 从变更日志恢复状态

当 Kafka Streams 应用程序在故障后重新启动时,它会从 Kafka 变更日志主题恢复其状态。此行为可确保不丢失中间状态。

示例:重启时恢复状态

  1. 模拟应用程序故障
    停止 Kafka Streams 应用程序。
  2. 重启应用程序
    重启后,Kafka Streams 会...
    • 从变更日志主题恢复状态。
    • 从最后一个提交的偏移量恢复处理。

3. 处理任务故障

当某个实例发生故障时,Kafka Streams 会动态地将任务重新分配给健康的实例。这种机制可确保持续处理。

示例:扩展 Kafka Streams 应用程序

1. 在多个实例上运行相同的应用程序

2. 停止一个实例

Kafka Streams 会自动在剩余实例之间重新平衡任务。

4. 实现优雅关机

为了确保容错,Kafka Streams 允许应用程序优雅地关闭,在退出前提交偏移量并持久化状态。

示例:优雅关机钩子

5. Join 操作的容错

Kafka Streams 支持 Join 等有状态操作,由于变更日志,这些操作对故障具有弹性。

示例:流-流 Join

  • 变更日志:为窗口 Join 操作提供状态备份,确保故障后的恢复。
  • 偏移量:在恢复期间从提交的偏移量重新同步。

使用混沌工程在 Kafka 中测试容错

混沌工程是一种分布式系统方法,用于测试其对故障的弹性。在 Kafka 中,测试容错涉及模拟 Broker 崩溃、网络中断和消费者组重新平衡等场景,以确保系统在不利条件下按预期运行。

Kafka 中的容错场景

  1. Broker 崩溃
    • 模拟 Broker 离线,以验证 Leader 选举和数据复制。
  2. 网络问题
    • 引入网络延迟或断开连接,以测试 Kafka 维持操作的能力。
  3. 消费者组重新平衡
    • 通过添加或删除消费者来触发重新平衡,以评估平滑的任务重新分配。

设置环境

集群配置

  • Kafka 集群包含 3 个 Broker:broker1、broker2、broker3。

生产者代码

输出

Designing Fault-Tolerant Microservices with Kafka

消费者代码

输出

Designing Fault-Tolerant Microservices with Kafka

使用混沌工程测试容错

1. Broker 崩溃模拟

场景

通过停止集群中的一个 Broker 来模拟 Broker 故障。

步骤:

a. 启动包含 3 个 Broker 的 Kafka 集群。

b. 停止一个 Broker

c. 验证分区的 Leader 选举

d. 观察生产者和消费者的行为

  • 生产者应继续写入剩余的 Broker。
  • 消费者应无缝地从新 Leader 读取数据。

2. 网络分区模拟

场景

引入网络延迟或模拟 Broker 无法访问。

步骤:

a. 使用网络仿真工具(如 tcLinux 流量控制))引入延迟

b. 临时断开一个 Broker 的连接

c. 监控 Kafka 的行为

  • 检查生产者和消费者是否重试并连接到其他 Broker。
  • 确保没有数据丢失或重复。

d. 恢复网络

3. 消费者组重新平衡模拟

场景

通过在组中添加或删除消费者来模拟任务重新平衡。

步骤:

a. 启动第一个消费者实例

b. 启动其他消费者

java -jar ChaosConsumer.jar &

c. 观察日志中的重新平衡

  • 消费者之间重新分配分区。

d. 移除一个消费者实例

e. 验证剩余消费者能否无缝接管工作负载。

验证 Kafka 中的容错

1. 数据一致性

为确保数据一致性,请验证 Kafka 的复制机制是否已保存所有消息而没有丢失或重复。通过生产已知数量的消息(`acks` 设置为 "all"),模拟 Broker 故障,然后消费所有消息来测试这一点。消耗的消息数量应与生产的消息数量匹配,并且不应有重复或缺失的数据。

2. 偏移量管理

偏移量管理可确保消费者在故障后从正确的位置恢复。要进行验证,请检查偏移量是否已定期提交到 Kafka(通过消费者日志或 Kafka 的偏移量存储)。在消费者发生故障并重启后,它应从上次中断的地方继续处理,仅处理未消费的记录。

3. 分区 Leader 选举

分区 Leader 选举可确保在 Broker 故障期间的连续性。验证在 Broker 发生故障后,其分区的 Leader 是否已转移到同步副本 (ISR)。使用 Kafka CLI 或管理工具检查分区 Leader 的变更,确保所有分区都有活跃的 Leader,且停机时间最少。

4. 消费者负载均衡

消费者重新平衡可确保在活跃消费者之间平滑地重新分配分区。模拟消费者组的变更(通过添加或删除消费者)并监控系统日志。验证所有分区是否已重新分配给活跃消费者,并且没有处理中断或显著延迟。