《kafka权威指南》阅读笔记

最近翻阅《kafka权威指南》,对于书中的某些片段印象深刻,因此特意摘抄了一些内容。

作为Coder,每个中间件会用可能很简单;但是真正静下心思考、了解其原理很难;

学习要广,某些点学习更要深;

使用场景

多样的使用场景意味着多样的需求:

  • 是否每个消息都很重要?
  • 是否允许丢失一小部分消息?
  • 偶尔出现重复消息是否可以接受?
  • 是否有严格的延迟和吞吐量要求?

每种需求,实现的成本都有所不同,kafka性能上也有所不同,需要权衡和取舍。

硬件方面

影响kafa整体性能的因素:

  • 硬盘吞吐量(固态硬盘\机械硬盘的选择):生产者客户端的性能直接受到服务器端磁盘吞叶量的影响。生产者生成的消息必须被提交到服务器保存,大多数客户端在发送消息之后会一直等待,直到至少有一个服务器确认消息已经成功提交为止。也就是说,磁盘写人速度越快,生成消息的延迟就越低。
  • 内存:除了磁盘性能外,服务器端可用的内存容量是影响客户端性能的主要因素。磁盘性能影响生产者,而内存影响消费者。消费者一般从分区尾部读取消息,如果有生产者存在,就紧跟在生产者后面。在这种情况下,消费者读取的消息会直接存放在系统的页面缓存里,这比从磁盘上重新读取要快得多。

如何选定主题分区数量

为主题选定分区数量并不是一件可有可无的事情,在进行数量选择时,需要考虑如下几个因素。

  • 主题需要达到多大的吞叶量? 例如,是希望每秒钟写入 100KB 还是 1GB ?
  • 从单个分区读取数据的最大吞吐量是多少?每个分区一般都会有一个消费者,如果你知道消费者将数据写入数据库的速度不会超过每秒 50MB,那么你也该知道,从一个分区读取数据的吞叶量不需要超过每秒 50MB。
  • 可以通过类似的方法估算生产者向单个分区写人数据的吞吐量,不过生产者的速度一般比消费者快得多,所以最好为生产者多估算一些吞叶量。
  • 每个 broker 包含的分区个数、可用的磁盘空间和网终带宽如果消息是按照不同的键来写入分区的,那么为已有的主题新增分区就会很困难。
  • 单个 broker 对分区个数是有限制的,因为分区越多,占用的内存越多,完成首领选举需要的时间也越长。
  • 如果要使用键来映射分区,那么最好在创建主题的时候就把分区规划好,而且永远不要增加新分区。(键有两个用途:可以作为消息的附加信息,也可以用来决定消息该被写到主题的哪个分区。拥有相同键的消息将被写到同一个分区。)

消费者和消费者群组

假设我们有一个应用程序需要从一个 Kafka 主题读取消息并验证这些消息,然后再把它们保存起来。应用程序需要创建一个消费者对象,订阅主题并开始接收消息,然后验证消息并保存结果。过了一阵子,生产者往主题写入消息的速度超过了应用程序验证数据的速度,这个时候该怎么办?如果只使用单个消费者处理消息,应用程序会远跟不上消息生成的速度。显然,此时很有必要对消费者进行横向伸缩。就像多人生产者可以向相同的主题写人消息一样,我们也可以使用多人消费者从同一个主题读取消息,对消息进行分流。

Kafka 消费者从属于消费者群组。一个群组里的消费者订阅的是同一个主题,每人消费者接收主题一部分分区的消息。

如果在群组 G1 里新增一个消费者 C2,那么每个消费者将分别从两个分区接收消息。我们假设消费者 C1 接收分区 0和分区 2的消息,消费者 C2 接收分区 1和分区 3 的消息,如图4-2 所示。

如果我们往群组里添加更多的消费者,超过主题的分区数量,那么有一部分消费者就会被闲置,不会接收到任何消息,如图 4-4 所示。

往群组里增加消费者是横向伸缩消费能力的主要方式。Kafka 消费者经常会做一些高延迟的操作,比如把数据写到数据库或 HDFS,或者使用数据进行比较耗时的计算。在这些情况下,单个消费者无法跟上数据生成的速度,所以可以增加更多的消费者,让它们分担负载,每人消费者只处理部分分区的消息,这就是横向伸缩的主要手段。我们有必要为主题创建大量的分区,在负载增长时可以加人更多的消费者。不过要注意,不要让消费者的数量超过主题分区的数量,多余的消费者只会被闲置。

在上面的例子里,如果新增一个只包含一个消费者的群组 G2,那么这个消费者将从主题T1 上接收所有的消息,与群组 G1 之间互不影响。群组 G2 可以增加更多的消费者,每个消费者可以消费若千个分区,就像群组 G1 那样,如图 4-5 所示。总的来说,群组 G2 还是会接收到所有消息,不管有没有其他群组存在。

简而言之,为每一个需要获取一个或多个主题全部消息的应用程序创建一个消费者群组然后往群组里添加消费者来伸缩读取能力和处理能力。每个消费者群组可以获取到所有的消息,而不只是其中的一部分。

消费者的线程安全

在同一个群组里,我们无法让一个线程运行多个消费者,也无法让多个线程安全地共享一个消费者。按照规则,一个消费者使用一个线程。如果要在同一个消费者群组里运行多个消费者,需要让每个消费者运行在自己的线程里。最好是把消费者的逻辑封装在自己的对象里,然后使用 Java的 ExecutorService 启动多个线程,使每个消费者运行在自己的线程上.Confluent 的博客(https://www.confluent.io/blog/) 上有一个教程介绍如何处理这种情况。

broker请求处理

broker处理生产请求

broker 会在它所监听的每一个端口上运行一个 Acceptor 线程,这个线程会创建一个连接并把它交给 Processor 线程去处理。Processor 线程 (也被叫“网络线程”) 的数量是配置的。网络线程负责从客户端获取请求消息,把它们放进请求队列,然后从响应队列获取响应消息,把它们发送给客户端。图 5-1 为 Kafka 处理请求的内部流程。

生产请求和获取请求都必须发送给分区的首领副本。如果 broker 收到一个针对特定分区的请求,而该分区的首领在另一个 broker 上,那么发送请求的客户端会收到一个“非分区首领”的错误响应。当针对特定分区的获取请求被发送到一个不含有该分区首领的 brokel上,也会出现同样的错误。Kafka 客户端要自己负责把生产请求和获取请求发送到正确的broker 。

缓存元数据

那么客户端怎么知道该往哪里发送请求呢?客户端使用了另一种请求类型,也就是元数据请求。这种请求包含了客户端感兴趣的主题列表。服务器端的响应消息里指明了这些主题所包含的分区、每个分区都有哪些副本,以及哪个副本是首领。元数据请求可以发送给任意一个 broker,因为所有 broker 都缓存了这些信息。

一般情况下,客户端会把这些信息缓存起来,并直接往目标 broker 上发送生产请求和获取请求。它们需要时不时地通过发送元数据请求来刷新这些信息 (刷新的时间间隔通过 metadata.max.age.ms 参数来配置),从而知道元数据是否发生了变更一一比如,在新broker 加人集群时,部分副本会被移动到新的 broker 上 (如图 5-2 所示)。另外,如果客户端收到“非首领”错误,它会在尝试重发请求之前先刷新元数据,因为这个错误说明了客户端正在使用过期的元数据信息,之前的请求被发到了错误的 broker 上。

broker处理获取请求(消费请求)

broker 处理获取请求的方式与处理生产请求的方式很相似。客户端发送请求,向 broker 请求主题分区里具有特定偏移量的消息,好像在说:“请把主题 Test 分区 0 偏移量从 53 开始的消息以及主题 Test分区 3 偏移量从 64 开始的消息发给我。”客户端还可以指定 broker 最多可以从一个分区里返回多少数据。这个限制是非常重要的,因为客户端需要为 broker 返回的数据分配足够的内存。如果没有这人限制,broker 返回的大量数据有可能耗尽客户端的内存。

客户端除了可以设置 broker 返回数据的上限,也可以设置下限。例如,如果把下限设置为10KB,就好像是在告诉 broker:“等到有 10KB 数据的时候再把它们发送给我。”在主题消息流量不是很大的情况下,这样可以减少 CPU 和网络开销。客户端发送一人请求,brokel等到有足够的数据时才把它们返回给客户端,然后客户端再发出请求,而不是让客户端每隔几毫秒就发送一次请求,每次只能得到很少的数据甚至没有数据。(如图 5-3 所示。)对比这两种情况,它们最终读取的数据总量是一样的,但前者的来回传送次数更少,因此开销也更小。

当然,我们不会让客户端一直等待 broker 累积数据。在等待了一段时间之后,就可以把可用的数据拿回处理,而不是一直等待下去。所以,客户端可以定义一个超时时间,告诉 broker:“如果你无法在X毫秒内累积满足要求的数据量,那么就把当前这些数据返回给我。”

高水位

有意思的是,并不是所有保存在分区首领上的数据都可以被客户端读取。大部分客户端只能读取已经被写入所有同步副本的消息 (跟随者副本也不行,尽管它们也是消费者一一否则复制功能就无法工作)。分区首领知道每人消息会被复制到哪人副本上,在消息还没有被写人所有同步副本之前,是不会发送给消费者的一-尝试获取这些消息的请求会得到空的响应而不是错误。

因为还没有被足够多副本复制的消息被认为是“不安全”的一一如果首领发生崩溃,另一个副本成为新首领,那么这些消息就丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者读取并处理了这样的一个消息,而另一个消费者发现这个消息其实并不存在。所以,我们会等到所有同步副本复制了这些消息,才允许消费者读取它们 (如图 5-4 所示)。这也意味着,如果 broker 间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长 (因为我们会先等待消息复制完毕)。延迟时间可以通过参数 replica.lag.time.max.ms 来配置,它指定了副本在复制消息时可被允许的最大延迟时间。

kafka的可靠性

可靠性保证

了解系统的保证机制对于构建可靠的应用程序来说至关重要,这也是能够在不同条件下解释系统行为的前提。那么 Kafka 可以在哪些方面作出保证呢?

  • Kafka 可以保证分区消息的顺序。如果使用同一个生产者往同一人分区写人消息,而目消息 B 在消息 A之后写入,那么 Kafka 可以保证消息 B 的偏移量比消息 A 的偏移量大而且消费者会先读取消息 A 再读取消息 B。
  • 只有当消息被写入分区的所有同步副本时 (但不一定要写入磁盘),它才被认为是“已提交”的。生产者可以选择接收不同类型的确认,比如在消息被完全提交时的确认,或者在消息被写人首领副本时的确认,或者在消息被发送到网终时的确认。
  • 只要还有一个副本是活跃的,那么已经提交的消息就不会丢失。
  • 消费者只能读取已经提交的消息。

这些基本的保证机制可以用来构建可靠的系统,但仅仅依赖它们是无法保证系统完全可靠的。构建一个可靠的系统需要作出一些权衡,Kafka 管理员和开发者可以在配置参数上作出权衡,从而得到他们想要达到的可靠性。这种权衡一般是指消息存储的可靠性和一致性的重要程度与可用性、高吞吐量、低延迟和硬件成本的重要程度之间的权衡

broker 有3 个配置参数会影响 Kafka 消息存储的可靠性。

与其他配置参数一样,它们可以应用在 broker 级别,用于控制所有主题的行为,也可以应用在主题级别,用于控制个别主题的行为。

  • 复制系数(`replication.factor`):主题级别的配置参数是 replication.factor,而在 broker 级别则可以通过 default.replication.factor 来配置自动创建的主题。
    • 如果复制系数为 N,那么在 N-1 个 broker 失效的情况下,仍然能够从主题读取数据或向主题写人数据。所以,更高的复制系数会带来更高的可用性、可靠性和更少的故障。另一方面,复制系数 N需要至少 N个 broker,而且会有 N个数据副本,也就是说它们会占用N倍的磁盘空间。我们一般会在可用性和存储硬件之间作出权衡。
    • 那么该如何确定一个主题需要几个副本呢? 这要看主题的重要程度,以及你愿意付出多少成本来换取可用性。有时候这与你的偏执程度也有点关系。建议在要求可用性的场景把复制系数设为3。
    • 副本的分布也很重要。默认情况下,Kafka 会确保分区的每个副本被放在不同的 broker 上。不过,有时候这样仍然不够安全。如果这些 broker 处于同一个机架上,一旦机架的交换机发生故障,分区就会不可用,这时候把复制系数设为多少都不管用。为了避免机架级别的故障,我们建议把 broker 分布在多人不同的机架上,并使用 broker.rack 参数来为每人broker 配置所在机架的名字。如果配置了机架名字,Kafka 会保证分区的副本被分布在多人机架上,从而获得更高的可用性。
  • 不完全的首领选举(`unclean.leader.election`):只能在 broker 级别(实际上是在集群范围内)进行配置,它的默认值是 true。
    • 当分区首领不可用时,一个同步副本会被选为新首领。如果在选举过程中没有丢失数据,也就是说提交的数据同时存在于所有的同步副本上,那么这个选举就是“完全”的。
    • 如果我们允许不同步的副本成为首领,那么就要承担丢失数据和出现数据不一致的风险。如果不允许它们成为首领,那么就要接受较低的可用性,因为我们必须等待原先的首领恢复到可用状态。
  • 最少同步副本(`min.insync.replicas`):适用于主题级别和 broker 级别。
    • 尽管为一个主题配置了 3 个副本,还是会出现只有一个同步副本的情况。如果这个同步副本变为不可用,我们必须在可用性和一致性之间作出选择一一这是一个两难的洗择。根据 Kafka 对可靠性保证的定义,消息只有在被写人到所有同步副本之后才被认是已提交的。但如果这里的“所有副本”只包含一个同步副本,那么在这个副本变为不可用时,数据就会丢失。
    • 如果要确保已提交的数据被写人不止一个副本,就需要把最少同步副本数量设置为大一点的值。对于一人包含3 个副本的主题,如果 min.insync.replicas 被设为 2,那么至少要存在两个同步副本才能向分区写入数据。如果3 个副本都是同步的,或者其中一个副本变为不可用,都不会有什么问题。不过,如果有两个副本变为不可用,那么 broker 就会停止接受生产者的请求。尝试发送数据的生产者会收到 NotEnoughReplicasException 异常。消费者仍然可以继续读取已有的数据。

可靠的生产者

即使我们尽可能把 broker 配置得很可靠,但如果没有对生产者进行可靠性方面的配置,整个系统仍然有可能出现突发性的数据丢失。

每个使用 Kafka的开发人员都要注意:

  • 根据可靠性需求配置恰当的 acks 值;
  • 配置生产者的重试参数;
  • 代码里正确处理错误;

生产者发送确认

生产者可以选择以下3 种不同的确认模式

  • acks=0 意味着如果生产者能够通过网络把消息发送出去,那么就认为消息已成功写入Kafka。在这种情况下还是有可能发生错误,比如发送的对象无法被序列化或者网卡发生故障,但如果是分区离线或整个集群长时间不可用,那就不会收到任何错误。即使是在发生完全首领选举的情况下,这种模式仍然会丢失消息,因为在新首领选举过程中它并不知道首领已经不可用了。在 acks=0 模式下的运行速度是非常快的 (这就是为什么很多基准测试都是基于这个模式),你可以得到惊人的吞吐量和带宽利用率,不过如果选择了这种模式,一定会丢失一些消息。
  • acks=1 意味着首领在收到消息并把它写人到分区数据文件 (不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下,如果发生正常的首领选举,生产者会在选举时收到一个 LeaderNotAvailableException 异常,如果生产者能恰当地处理这人错误(参考 6.4.2 节),它会重试发送消息,最终消息会安全到达新的首领那里。不过在这个模式下仍然有可能丢失数据,比如消息已经成功写人首领,但在消息被复制到跟随者副本之前首领发生崩溃。
  • acks=all 意味着首领在返回确认或错误响应之前,会等待所有同步副本都收到消息。如果和 min.insync.replicas 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到消息。这是最保险的做法一一生产者会一直重试直到消息被成功提交。不过这也是最慢的做法,生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。可以通过使用异步模式和更大的批次来加快速度,但这样做通常会降低吞叶量。

不论`acks`的设置如何,Kafka的生产者在消息被成功写入主题的领导者分区后就会返回,而不是等到消息被写入磁盘。这是因为Kafka使用了文件系统缓存(page cache)来加速写入操作,将消息首先写入操作系统的页缓存,然后异步刷写到磁盘。这可以提高写入性能,但也意味着在消息被持久化到磁盘之前,系统可能会丢失一些数据。

要注意的是,即使在`acks=all`的情况下,消息在被确认为成功发送之前可能仍然存储在操作系统的页缓存中。在这种情况下,系统发生故障时可能会导致一些数据的丢失。因此,在对消息的可靠性有更高要求时,可能需要考虑其他配置,例如适当的`min.insync.replicas`设置。

生产者重试参数配置

生产者需要处理的错误包括两部分:一部分是生产者可以自动处理的错误,还有一部分是需要开发者手动处理的错误。

生产者向broker 发送消息时,broker 可以返回一个成功响应码或者一个错误响应码。错误响应码可以分为两种,一种是在重试之后可以解决的,还有一种是无法通过重试解决的。例如,如果broker 返回的是 LEADER NOT AVAILABLE 错误,生产者可以尝试重新发送消息。也许在这个时候一个新的首领被选举出来了,那么这次发送就会成功。也就是说,LEADER_NOT_AVAILABLE是一人可重试错误。另一方面,如果 broker 返回的是 INVALID CONFIG 错误,即使通过重试也无法改变配置选项,所以这样的重试是没有意义的。这种错误是不可重试错误。

一般情况下,如果你的目标是不丢失任何消息,那么最好让生产者在遇到可重试错误时能够保持重试。为什么要这样? 因为像首领选举或网络连接这类问题都可以在几秒钟之内得到解决,如果让生产者保持重试,你就不需要额外去处理这些问题了。

经常会有人问:“为生产者配置多少重试次数比较好?” 这个要看你在生产者放弃重试并抛出异常之后想做些什么。

  • 如果你想抓住异常并再多重试几次,那么就可以把重试次数设置得多一点,让生产者继续重试;
  • 如果你想直接丢弃消息,多次重试造成的延迟已经失去发送消息的意义;
  • 如果你想把消息保存到某个地方然后回过头来再继续处理,那就可以停止重试。

重试风险

重试发送一个已经失败的消息会带来一些风险,如果两个消息都写人成功,会导致消息重复。

例如,生产者因为网络问题没有收到 broker 的确认,但实际上消息已经写入成功,生产者会认为网络出现了临时故障,就重试发送该消息 (因为它不知道消息已经写入成功)。在这种情况下,broker 会收到两个相同的消息。

重试和恰当的错误处理可以保证每个消息“至少被保存一次”,但当前的 Kafka 版本 (0.10.0) 无法保证每个消息“只被保存一次”。现实中的很多应用程序在消息里加人唯一标识符,用于检测重复消息,消费者在读取消息时可以对它们进行清理。还要一些应用程序可以做到消息的“幂等”,也就是说,即使出现了重复消息,也不会对处理结果的正确性造成负面影响。

生产者额外的错误处理

使用生产者内置的重试机制可以在不造成消息丢失的情况下轻松地处理大部分错误,不过对于开发人员来说,仍然需要处理其他类型的错误,包括:

  • 不可重试的 broker 错误,例如消息大小错误、认证错误等;
  • 在消息发送之前发生的错误,例如序列化错误;
  • 在生产者达到重试次数上限时或者在消息占用的内存达到上限时发生的错误;

我们在第 3 章讨论了如何为同步发送消息和异步发送消息编写错误处理器。这些错误处理器的代码逻辑与具体的应用程序及其目标有关。丢弃“不合法的消息”?把错误记录下来? 把这些消息保存在本地磁盘上? 回调另一个应用程序? 具体使用哪一种逻辑要根据具体的架构来决定。只要记住,如果错误处理只是为了重试发送消息,那么最好还是使用生产者内置的重试机制。

可靠的消费者

只有那些被提交到 Kafka 的数据,也就是那些已经被写人所有同步副本的数据,对消费者是可用的,这意味着消费者得到的消息已经具备了一致性,消费者唯一要做的是跟踪哪些消息是已经读取过的,哪些是还没有读取过的。这是在读取消息时不丢失消息的关键。

如果消费者提交了偏移量却未能处理完消息,那么就有可能造成消息丢失,这也是消费者丢失消息的主要原因。在这种情况下,如果 其他消费者接手了工作,那些没有被处理完的消息就会被忽略,永远得不到处理。这就是为什么我们非常重视偏移量提交的时间点和提交的方式。

消费者的可靠性配置

  • group.id。这个参数在第4 章已经详细解释过了,如果两个消费者具有相同的group.id,并且订阅了同一个主题,那么每个消费者会分到主题分区的一个子集,也就是说它们只能读到所有消息的一个子集 (不过群组会读取主题所有的消息)。如果你希望消费者可以看到主题的所有消息,那么需要为它们设置唯一的 group.id。
  • auto.offset.reset。这个参数指定了在没有偏移量可提交时 (比如消费者第 1次启动时) 或者请求的偏移量在 broker 上不存在时 (第4章已经解释过这种场景),消费者会做些什么。这个参数有两种配置。一种是 earliest,如果选择了这种配置,消费者从主题的起始位置开始消费消息,即从最老的消息开始。这意味着消费者会处理该主题中已经存在的所有消息。一种是 Latest,如果选择了这种配置,消费者会从分区的末尾开始读取数据,这样可以减少重复处理消息,但很有可能会错过一些消息。

显示提交偏移量

  • 提交频度是性能和重复消息数量之间的权衡:即使是在最简单的场景里,比如所有的处理都在轮询里完成,并且不需要在轮询之间维护状态,你仍然可以在一个循环里多次提交偏移量 (甚至可以在每处理完一个事件之后),或者多个循环里只提交一次 (与生产者的 acks=all 配置有点类似),这完全取决于你在性能和重复处理消息之间作出的权衡。
  • 确保对提交的偏移量心里有数:在轮询过程中提交偏移量有一个不好的地方,就是提交的偏移量有可能是读取到的最新偏移量,而不是处理过的最新偏移量。这种情况可能导致消息的重复处理或者消息的漏处理。为了解决这个问题,一种常见的做法是在消息处理成功后再提交偏移量,而不是在轮询阶段。这确保了提交的是已经成功处理的消息的偏移量,而不是当前拉取到的最新偏移量。
  • 再均衡:在设计应用程序时要注意处理消费者的再均衡向题。一般要在分区被撤销之前提交偏移量,并在分配到新分区时清理之前的状态。
  • 消费者可能需要重试
    • 在遇到可重试错误时,提交最后一个处理成功的偏移量,然后把还没有处理好的消息保存到缓冲区里 (这样下一个轮询就不会把它们覆盖掉),调用消费者的 pause()方法来确保其他的轮询不会返回数据 (不需要担心在重试时缓冲区溢出),在保持轮询的同时尝试重新处理 (关于为什么不能停止轮询,请参考第 4章)。如果重试成功,或者重试次数达到上限并决定放弃,那么把错误记录下来并丢弃消息,然后调用 resume() 方法让消费者继续从轮询里获取新数据。
    • 在遇到可重试错误时,把错误写人一个独立的主题,然后继续。一个独立的消费者群组负责从该主题上读取错误消息,并进行重试,或者使用其中的一个消费者同时从该主题上读取错误消息并进行重试,不过在重试时需要暂停该主题。这种模式有点像其他消息系统里的 dead-letter-queue。
  • 长时间处理:有时候处理数据需要很长时间:你可能会从发生阻塞的外部系统获取信息,或者把数据写到外部系统,或者进行一个非常复杂的计算。要记住,暂停轮询的时间不能超过几秒钟。即使不想获取更多的数据,也要保持轮询,这样客户端才能往 broker 发送心跳。在这种情况下,一种常见的做法是使用一个线程池来处理数据,因为使用多个线程可以进行并行处理,从而加快处理速度。在把数据移交给线程池去处理之后,你就可以暂停消费者,然后保持轮询,但不获取新数据,直到工作线程处理完成。在工作线程处理完成之后,可以让消费者继续获取新数据。因为消费者一直保持轮询,心跳会正常发送,就不会发生再均衡。
  • 仅一次传递
    • 有些应用程序不仅仅需要“至少一次”(atleast-once) 语义(意味着没有数据丢失),还需要“仅一次”(exactly-once) 语义。尽管 Kafka 现在还不能完全支持仅一次语义,消费者还是有一些办法可以保证 Kafka 里的每人消息只被写到外部系统一次 (但不会处理向Kafka 写入数据时可能出现的重复数据)。
    • 实现仅一次处理最简单日最常用的办法是把结果写到一个支持唯一键的系统里,比如键值存储引擎、关系型数据库、ElasticSearch 或其他数据存储引擎。在这种情况下,要么消息本身包含一个唯一键(通常都是这样),要么使用主题、分区和偏移量的组合来创建唯一键一一它们的组合可以唯一标识一个 Kafka 记录。如果你把消息和一个唯一键写入系统然后碰巧又读到一个相同的消息,只要把原先的键值覆盖掉即可。

断电、崩溃

在 Linux 系统上,消息会被写到文件系统缓存里,并不保证它们何时会被刷新到磁盘上。Kafka 不会一直等待数据被写到磁盘上一一它依赖复制功能来保证消息的持久性。

在Linux系统上,如果Kafka将消息写入文件系统缓存(即操作系统的页缓存),但尚未将数据刷新到磁盘,而此时系统发生断电或崩溃,存在一定的数据丢失的风险。这是因为缓存在内存中的数据尚未被持久化到磁盘,即使Kafka已经成功接收到数据。

Kafka使用了操作系统的页缓存和文件系统缓存,这样可以提高性能,因为直接写入磁盘的操作通常比写入内存快得多。然而,如果消息尚未被刷新到磁盘,就可能会在断电或崩溃时发生数据丢失。

为了尽量减小数据丢失的风险,可以考虑以下措施:

  1. 持久化配置: 确保Kafka的主题(topic)和分区(partition)配置采用适当的持久化配置,以便在写入磁盘时可以提供一定的数据保护。这包括设置适当的`acks`配置以及配置`min.insync.replicas`等。
  2. 同步刷写: 在Kafka的配置中,你可以调整`flush.messages`和`flush.ms`等参数,以控制消息何时被刷新到磁盘。这有助于在更短的时间内将数据写入磁盘,减小数据丢失的风险。
  3. 硬件和电源保护:使用具有良好电源保护和硬件故障容错特性的硬件,可以降低因为硬件问题导致的数据丢失风险。
  4. 备份和复原策略:实施定期备份和复原策略,以便在发生数据丢失时能够迅速恢复数据。
  5. 跨集群数据镜像。

尽管这些措施可以减小数据丢失的风险,但在任何分布式系统中,绝对的零数据丢失是难以实现的。因此,在设计应用程序时,需要权衡性能和可靠性之间的权衡,以满足特定的业务需求。

分区均衡

分区再均衡的触发条件(消费者)

心跳超时: 如果消费者在一定的时间内没有发送心跳,它被认为已经失败。心跳超时时间由 session.timeout.ms 参数控制。

拉取消息的最大时间间隔: 如果消费者长时间没有调用 poll 方法去拉取消息,也会被认为已经失败。这个时间间隔由 max.poll.interval.ms 参数控制。

        如果一个消费者在处理消息的过程中持续发送心跳,或者定期地调用 poll 方法,它就会被认为是存活的,不会触发分区再均衡。在消费者处理消息时,确保及时地与 Kafka 服务器保持心跳和拉取消息的动作是很重要的。

        如果 max.poll.interval.ms 超时,也会触发分区再均衡。虽然 session.timeout.ms 是检测消费者是否存活的超时参数,但 max.poll.interval.ms 也与消费者的健康状态有关。 max.poll.interval.ms 参数定义了消费者在两次调用 poll 方法之间的最大时间间隔。如果消费者在这个时间间隔内没有调用 poll 方法,就会被认为是失败,触发分区再均衡。这个参数的目的是为了检测消费者是 否在正常处理消息,以防止某个消费者由于长时间不响应而被判定为故障。

        具体来说,如果 max.poll.interval.ms 时间超过了设定的值,Kafka 就会认为消费者已经失效,然后启动分区再均衡过程,将该消费者的分区重新分配给其他健康的消费者。

        在配置 Kafka 消费者时,需要确保 max.poll.interval.ms 设置得足够大以容纳消息处理的时间,以避免误判消费者失效而触发不必要 的再均衡。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值