2

Apache Kafka是一个开放源代码的分布式消息传递平台,高吞吐量和低延迟交付数据。在规模上,它每天可以处理数万亿条记录,同时还提供容错、复制和自动灾难恢复功能。
尽管Kafka有许多不同的使用场景,但最常见的是作为应用程序之间的消息broker。Kafka可以从多个上游源头接收、处理和重新分配消息到多个下游使用者,而无需重新配置应用程序。这就可以流式传输大量数据,同时保持应用程序的松散耦合,支持诸如分布式计算、日志记录和监控、网站活动跟踪以及物联网(IoT)设备通信之类的场景。

由于Kafka提供了应用程序之间的关键管道,因此可靠性至关重要。我们需要计划来减轻几种可能的故障模式,包括:

  • 消息broker中断和其他不正常的群集状况
  • Apache ZooKeeper失效,这是Kafka的关键依赖项
  • 上游和下游应用程序中的故障

不必等待这些故障在预生产或生产中发生,我们就能通过混沌工程主动对其进行测试,以便制定适当的策略减轻影响。本文,我们将演示混沌工程如何帮助提高Kafka部署的可靠性。为此,我们将使用企业SaaS混沌工程平台Gremlin创建并运行四个混沌实验。通过阅读本文,了解Kafka群集可能发生故障的不同方式,如何设计混沌实验来测试这些故障模式,以及如何使用观测结果提高其可靠性。

本文中,我们将在Confluent平台上演示混乱的实验,该平台是Kafka最初的创建者提供的企业事件流平台。Confluent平台基于Kafka构建并添加企业功能(例如基于Web的GUI,全面的安全控制以及轻松部署多区域集群的功能)。但是,本文中的实验将适用于任何Kafka集群。

Apache Kafka架构概述

为了了解Kafka如何从混沌工程中受益,我们应该首先研究一下Kafka的体系架构设计。

Kafka使用发布者/订阅者(或pub/sub)消息传递模型传输数据。上游应用程序(在Kafka中称为发布者或生产者)会生成发送到Kafka服务器(称为broker)的消息。然后,下游应用程序(在Kafka中称为订阅者或使用者)可以从broker获取这些消息。消息被组织在主题的类别中,消费者可以订阅一个或多个主题以使用其消息。通过充当生产者和消费者之间的中间人,Kafka使我们能够彼此独立地管理上游和下游应用程序。

Kafka将每个主题细分为多个分区。分区可以跨多个broker镜像以提供复制。这还允许多个使用者(更具体地说,是使用者组)同时处理一个主题。为了防止多个生产者写入单个分区,每个分区都有一个充当领导者的broker,以及没有或多个充当追随者的broker。新消息将被写入领导者,而追随者则将其复制。追随者被完全复制后,称为同步副本(ISR)。

该过程由Apache ZooKeeper协调,ZooKeeper管理有关Kafka集群的元数据,例如哪些分区分配给了哪些broker。ZooKeeper是Kafka的必要依赖项(编者注:2.8版本就可以不需要ZooKeeper了),但在其自己的集群上作为完全独立的服务运行。改善Kafka集群的可靠性必然涉及提高其关联的ZooKeeper集群的可靠性。

Kafka和Confluent平台还有其他组件,但是在提高可靠性时,这些是最重要的考虑因素。当本文介绍其他组件时,我们会对其进行更详细的说明。

为何要对Kafka进行混沌工程?

混沌工程是一种主动测试系统故障的方法,目的是使它们更具韧性。我们通过向系统中注入少量受控故障,观察影响并解决观察到的问题。这使我们能够在系统出现问题之前,为用户找到解决问题的方法,同时还教会了我们更多关于系统在各种条件下的行为信息。

由于有数不清的配置选项,灵活的生产者和消费者的部署方式以及许多其他因素,使得像Kafka这样的分布式系统难以高效管理和运维。仅仅使我们的broker和ZooKeeper节点不失效是不够的,我们需要考虑在应用程序、副本和其他基础架构组件中可能发生的更加细微和难以预测的问题。这些可能会以意想不到的方式影响整个部署,如果在生产中发生,则可能需要进行大量的排障开销。

使用混沌工程,我们可以主动测试这些类型的故障,并在部署到生产之前解决这些故障,从而降低停机和紧急事件的风险。

在Kafka上运行混沌实验

在本节中,我们将逐步介绍在Confluent平台上部署执行四个不同的混沌实验。混沌实验是一个有计划的过程,将故障注入系统以了解其响应方式。在系统上运行任何实验之前,应充分考虑并开发要运行的实验。

创建实验时:

  • 第一步,设定假说要回答的问题以及预期结果是什么。例如,如果实验是测试承受broker中断的能力,则假说可能会指出:“如果broker节点发生故障,则消息会自动路由到其他broker,而不会丢失数据。”
  • 第二步,定义爆炸半径,受实验影响的基础架构组件。减小爆炸半径限制了实验可能对基础架构造成的潜在危害,同时还可以将精力集中在特定系统上。我们强烈建议从尽可能小的爆炸半径开始,然后随着对进行混沌实验的适应性提高,将其增大。另外,还应该定义幅度,即注入攻击的规模或影响力。例如,低幅度的实验可能是在生产者和broker之间的网络流量中增加20毫秒的延迟。大幅度的实验可能是增加500毫秒的延迟,因为这将对性能和吞吐量产生显着的影响。与爆炸半径一样,从低幅度开始,然后逐渐增大。
  • 第三步,监控基础架构。确定哪些指标将助力得出有关假说的结论,在测试之前进行观测以建立基线,并在整个测试过程中记录这些指标,以便可以监测预期和意外的变化。借助Confluent平台,我们可以使用Control Center从Web浏览器实时直观地观察群集的性能。
  • 第四步,运行实验。Gremlin允许以简单、安全和可靠的方式在应用程序和基础架构上运行实验。我们通过运行注入攻击来做到这一点,它提供了各种向系统中注入故障的方法。我们还要定义中止条件,这是我们应该停止测试以避免意外损坏的条件。使用Gremlin,我们可以将状态检查定义为场景的一部分。通过状态检查,我们可以在进行注入攻击时验证服务状态。如果基础架构运行不正常,并且状态检查失败,将自动停止实验。另外,我们可以使用内置的暂停按钮立即停止实验。
  • 第五步,从观察结果得出结论。它是否证实或反驳了原先的假设?使用收集的结果来修改基础架构,然后围绕这些改进设计新的实验。随着时间的推移,重复此过程将有助于使Kafka部署更具韧性。本文中介绍的实验绝不是详尽无遗的,而应将其作为在系统上进行实验的起点。请记住,尽管我们正在Confluent平台上运行这些实验,但它们可以在任何Kafka集群上执行。

请注意,我们使用的是基于Kafka 2.5.0构建的Confluent 平台 5.5.0。屏幕截图和配置详细信息可能会因版本而异。

实验1: Broker负载对处理延迟的影响

资源利用率可能会对消息吞吐量产生显着影响。如果broker正在经历较高的CPU、内存或磁盘I/O利用率,则处理消息的能力将受到限制。由于Kafka的效率依赖于最慢的组件,因此延迟可能会在整个管道中产生级联效应,并导致故障条件,例如生产者备份和复制延迟。高负载还会影响集群操作,例如broker运行状况检查,分区重新分配和领导者选举,从而使整个集群处于不正常状态。

优化Kafka时要考虑的两个最重要的指标是网络延迟和磁盘I/O。Broker不断在本地存储中读写数据,随着消息速率和群集大小的增加,带宽使用可能成为限制因素。在确定集群大小时,我们应该确定资源利用率在哪一点上会对性能和稳定性产生不利影响。

为了确定这一点,我们将进行一个混沌实验,逐步提高broker之间的磁盘I/O利用率,并观察其对吞吐量的影响。在运行此实验时,我们将使用Kafka Music演示应用程序发送连续的数据流。该应用程序将消息发送到分布在所有三个broker中的多个主题,并使用Kafka Streams聚合和处理消息。

使用IO Gremlin生成Broker负载

在本实验中,我们将使用IO Gremlin在broker节点上生成大量磁盘I/O请求。我们将创建一个方案,并在四个阶段中逐步增加注入攻击的强度。每次注入攻击将运行三分钟,中间会间隔一分钟,因此我们可以轻松地将I/O利用率的变化与吞吐量的变化联系起来。

此外,我们将创建一个状态检查,该状态检查使用Kafka Monitoring API在每个阶段之间检查broker的运行状况。状态检查是通过Gremlin发送自动HTTP请求到我们选定的端点,在这种情况下,该端点是我们集群的REST API服务器。我们将使用主题的端点来检索broker的状态,并解析JSON响应以确定它们当前是否处于同步状态。如果任何broker不同步,我们将立即停止实验并将其标记为失败。在场景运行期间,我们还将使用Confluent Control Center监视吞吐量和延迟。

image.png

  • 假设:磁盘I/O的增加将导致吞吐量的相应下降。
  • 结论:即使将磁盘I/O增加到150 MB/s以上,技术攻击也不会对吞吐量或延迟产生明显影响。两项指标均保持稳定,我们的broker均未失去同步或复制不足,也没有消息丢失或损坏。

image.png

目前,这给我们留下了很多开销,但是随着应用范围的扩大,对吞吐量的要求可能会增加。我们应该密切关注磁盘I/O利用率,以确保有足够的扩展空间。如果开始注意到磁盘I/O增加和吞吐量减少,则应考虑:

  • 使用更快的存储设备,例如更高的RPM磁盘或固态存储
  • 使用更有效的压缩算法,例如Snappy或LZ4

实验2: 消息丢失造成数据丢失的风险

为了确保成功传递消息,生产者和broker使用确认机制。当broker将消息提交到其本地日志时,它会与响应生产者进行确认,表明已成功接收到该消息,并且生产者可以发送下一条消息。这样可以减少生产者和broker之间丢失消息的风险,但是不能防止broker之间丢失消息。

例如,假设我们有一个broker领导,刚从生产者那里收到消息并发送了确认。Broker的每个订阅者现在都应该获取消息并将其提交到他们自己的本地日志中。但是,broker在其任何订阅者获取最新消息之前意外失败。跟随者没有一个知道生产者发送了一条消息,但是生产者已经收到了确认,因此它已移至下一条消息。除非我们可以恢复发生故障的broker,或者找到其他重新发送消息的方法,否则消息实际上已经丢失了。

我们如何确定在集群上发生这种情况的风险?借助混沌工程,我们可以模拟broker领导的故障并监控消息流,以确定潜在的数据丢失。

使用黑洞Gremlin模拟Broker领导中断

在此实验中,我们将使用黑洞Gremlin删除往返于broker领导的所有网络流量。此实验很大程度上取决于时间安排,因为我们希望在broker收到消息之后,但在其订阅者可以复制消息之前,引起broker的失败。这可通过两种方式执行此操作:

  • 比追随者低的时间间隔,发送连续的消息流,启动实验,并寻找使用者输出中的空白(replica.fetch.wait.max.ms)。
  • 发送消息后,立即使用Gremlin API从生产者应用程序触发混沌实验。

在本实验中,我们将使用第一种方式。应用程序每100毫秒产生一条新消息。消息流的输出记录为JSON列表,并对其分析以查找任何差距或时序上的不一致。我们将对其注入攻击30秒,这将生成300条消息(每100毫秒一条消息)。

  • 假设:由于领导者失败,我们将丢失一些消息,但是Kafka将迅速选出新的领导者,并再次成功复制消息。
  • 结果:尽管领导者突然失败,消息列表仍显示所有成功通过的消息。由于实验前后记录了额外的消息,因此我们的管道总共产生了336个事件,每个消息在上一个事件之后大约有100毫秒的时间戳。消息没有按时间顺序显示,但这很好,因为Kafka不保证分区之间消息的顺序。这是输出示例:

image.png

如果想保证所有消息都已保存,则可以在生产者配置中设置acks = all。这告诉生产者在将消息复制到broker领导及其所有订阅者之前,不要发送新消息。这是最安全的选择,但是它将吞吐量限制为最慢broker的速度,因此可能会对性能和延迟产生重大影响。

实验3: 避免脑裂

Kafka、ZooKeeper和类似的分布式系统容易受到称为“脑裂”问题的影响。在脑裂中,同一群集内的两个节点失去同步并分区,从而产生群集中两个单独且可能不兼容的视图。这会导致数据不一致,数据损坏,甚至形成第二个群集。

这是怎么发生的?在Kafka中,为单个broker节点分配了控制器角色。控制器负责检测群集状态的更改,例如失败的broker、领导者选举和分区分配。每个集群只有一个且只有一个控制器,以维护集群的单个一致性视图。尽管这使控制器成为单点故障,但Kafka拥有处理此类故障的过程。

所有broker都会定期向ZooKeeper登记,以证明自己的健康。如果broker的响应时间超过zookeeper.session.timeout.ms设置(默认为18,000毫秒),则ZooKeeper会将broker标记为不正常。如果该broker是控制者,则触发控制者选举,副本ISR成为新的控制者。这个新的控制器被分配了一个称为控制器纪元的编号,该编号跟踪最新的控制器选举。如果发生故障的控制器重新联机,它将比较自己的控制器纪元与ZooKeeper中存储的纪元,识别出新选的控制器,然后退回为普通broker。

这个过程可以防止少数broker失败,但如果大部分broker都发生了重大故障,那该怎么办呢?我们可以在不产生脑裂的情况下重新启动它们吗?我们可以使用混沌工程对此进行验证。

image.png

使用关机Gremlin重启大多数broker节点

此实验中,我们将使用关机Gremlin重新启动集群中的三个broker节点中的两个。由于此实验可能会对集群稳定性造成潜在风险(例如,我们不想意外关闭所有ZooKeeper节点),因此想确保在运行该broker之前,所有三个broker都应处于健康状态。我们将创建一个状态检查,从Kafka Monitoring API中获取健康broker的列表,以验证所有三个broker均已启动并正在运行。

这是我们完全配置的场景,显示状态检查和关机Gremlin:

image.png

  • 假设:Kafka的吞吐量将暂时停止,但是两个broker节点都将重新加入群集,而不会出现问题。
  • 结果:控制中心仍列出了三个broker,但显示其中两个不同步且分区复制不足。这是预料之中的,因为节点已经失去了与其余broker和ZooKeeper的联系。

image.png

当先前的控制器(broker1)脱机时,ZooKeeper立即选举剩余的broker(broker3)为新的控制器。由于这两个broker在不超过ZooKeeper会话超时的情况下重新启动,因此,从broker正常运行时间的图表,可以看出它们一直处于联机状态。但是,当我们的消息管道转移到broker3时,查看吞吐量和副本的图表,就会发现这明显地影响了吞吐量和分区运行状况。

image.png

image.png

尽管如此,broker毫无意外地重新加入了集群。由此可以得出结论,我们的集群可以承受暂时的多数失败。性能将显着下降,集群将需要选举新的领导者,重新分配分区,并在其余broker之间复制数据,但是不会陷入脑裂的局面。如果花更长的时间来恢复broker,则此结果可能会有所不同,因此,我们希望确定在重大生产中断的情况下,已制定了事件响应计划。

实验4: ZooKeeper中断

ZooKeeper是Kafka的基本依赖项。它负责诸如识别broker、选举领导者以及跟踪broker之间的分区分布等活动。ZooKeeper中断不一定会导致Kafka发生故障,但是如果解决时间越长,它可能会导致意外问题。

在一个示例中,HubSpot遇到了ZooKeeper故障,原因是短时产生大量备份的请求。ZooKeeper在几分钟内无法恢复,这又导致Kafka节点开始崩溃。最终导致数据损坏,团队不得不手动将备份数据还原到服务器。虽然这是HubSpot解决的一个不寻常的情况,但它强调了将ZooKeeper和Kafka作为单独服务和整体服务进行测试的重要性。

https://blog.hubspot.com/cust...

使用黑洞Gremlin模拟ZooKeeper中断

此实验中,我们要验证Kafka集群可以在ZooKeeper意外中断时能幸免于难。我们将使用黑洞Gremlin丢弃往返ZooKeeper节点的所有流量。我们将在控制中心中监视群集状态的同时运行注入攻击五分钟。

image.png

  • 假设:Kafka可以忍受短期的ZooKeeper中断,而不会崩溃,丢失数据或损坏数据。但是,直到ZooKeeper重新联机之前,对群集状态的任何更改都将无法解决。
  • 结果:运行实验对消息吞吐量或broker可用性没有任何结果。正如我们所假设的,可以继续产生和使用消息而不会出现意外问题。

如果其中一个broker失败,则在ZooKeeper重新联机之前,broker无法重新加入群集。仅此一项并不太可能导致失败,但是可能导致另一个问题:级联失败。例如,broker失败将导致生产者将负担转移到其余broker上。如果这些broker接近生产极限,就会依次崩溃。即使我们使失败的broker重新联机,也将无法加入集群,直到ZooKeeper再次可用。

该实验表明,我们可以容忍ZooKeeper暂时失败,但是仍然应该迅速工作以使其恢复在线状态。还应该寻找减轻完全中断风险的方法,例如在多个区域之间分布ZooKeeper以实现冗余。尽管这将增加运营成本并增加延迟,但与使整个集群出现生产故障相比,这是一个很小的成本。

进一步提高Kafka的可靠性

Kafka是一个复杂的平台,具有大量相互依赖的组件和流程。要使Kafka可靠地运行,就需要进行计划、持续的监控和主动的故障测试。这不仅适用于我们的Kafka和ZooKeeper集群,还适用于使用Kafka的应用程序。混沌工程使我们能够安全有效地发现Kafka部署中的可靠性问题。为今天的故障做准备可以防止或减少将来发生故障的风险和影响,从而节省组织的时间、工作量以及挽救客户的信任。

现在,我们已经展示了Kafka的四个不同的混沌实验,请尝试注册Gremlin Free帐户来运行这些实验。创建实验时,请考虑Kafka部署中的潜在故障点(例如,broker与使用者之间的连接),并观察它们如何应对不同的注入攻击。如果发现问题,请实施修复程序并重复实验以验证它可以解决问题。随着系统变得更加可靠,逐渐增加幅度(实验的强度)和爆炸半径(受实验影响的系统数量),以便全面地测试整个部署。

来源:混沌工程实践 作者:李大山

原文作者:Andree Newman 原文来源:Gremlin.com

原文标题:The first 4 chaos experiments to run on Apache Kafka

声明:文章获得作者授权在IDCF社区公众号(devopshub)转发。优质内容共享给思否平台的技术同伴,如原作者有其他考虑请联系小编删除,致谢。

7月每周四晚8点,【冬哥有话说】研发效能工具专场,公众号留言“研发效能”可获取地址

  • 7月8日,周文洋《微软DevOps工具链的 "爱恨情仇"(Azure DevOps)》
  • 7月15日,陈逊《复杂型研发协作模式下的效能提升实践》
  • 7月22日,张扬《基础设施即代码的⾃动化测试探索》
  • 7月29日,胡贤彬《自动化测试,如何做到「攻防兼备」?》

用户bPcN1SC
149 声望55 粉丝