Kafka的设计-生产消费与日志处理

129 阅读12分钟

本文介绍了Kafka中消费与生产的客户端在整个流处理过程中的对效率的贡献以及具体的操作,还介绍了kafka的消息传递语义

生产者

负载均衡

Kafka的生产者客户端可以直接将数据发送到其消息对应topic的leader上

  • kafka会获取并且记录下对应topic的分区以及leader信息,发送消息时找对对应topic的leader节点发送
  • 生产者可以向任何一个 Kafka 节点发送元数据请求,以获取关于哪些服务器处于活动状态、特定主题分区的领导者所在位置等信息
  • 当分区记录的leader过时/宕机,生产者会在发送信息时尝试与记录的leader节点建立连接,如果失败了就重新获取新的节点元数据信息。

异步发送

异步发送相比于同步发送,是能够支持批处理的一个不错的选择,异步发送能够将要即时发送的数据缓存积累在buffer中,合并成一个较大的请求发送。

消费者

消费者的工作方式:向想要使用的分区代理发送获取请求,并且指定其在对应topic或者分区中的偏移量,然后获取消息。 消费者对偏移量有明显的控制权,可以在需要时将其倒回重新使用数据

推与拉

消费到底是broker推送到下游,还是下游主动拉取,各有利弊,在推送系统中,当消费者的消费率低于生产率时,消费者往往会不知所措(大量数据推送过来,会崩溃,本质上是拒绝服务攻击)

Kafka拉取的优势

  • 消费者可以简单地落在后面并在可以的时候赶上。
    • 可以通过某种退避协议来缓解,消费者可以通过退避协议来表明它已不堪重负,但要充分利用(但绝不过度利用)消费者的传输率比看起来更棘手。
  • 有助于将数据批量发送给消费者,相比于推送流派拉取可以自己决定一时间处理多少数据
    • 推送系统必须选择立即发送请求或积累更多数据然后稍后发送,而不知道下游消费者是否能够立即处理它。
  • 如果没数据可拉,那么可能导致轮询空转,但是kafka通过设置参数允许长沦陷的阻塞等待。

消费者定位

要跟踪已消费的内容是消息系统的关键性能点,很多消息传递系统都会保存交互的元数据,要么立即记录,要么等待确认然后删除消息,可以避免消息丢失和数据大小较小

kafka的消费者定位

  • 将发送的消息标记为发送而不是确认,等确认消费后才更新消息的状态
    • 这样不仅可以不用立即删除消息,有利于批处理,还能避免消息的丢失(毕竟不删除消息)
  • 通过将主题分组为有序的分区,每个分区在任何时间都由一个消费组中的消费者消费
    • 每个分区中会记录消费者的位置(offset下一个消息的偏移量)
    • 每个分区只有一个数字,可以定期检查状态,成本非常低

离线消息加载

kafka的消息存储能存储大量消息且不怕消息过多而崩溃,所以能够支持定期消费,比如定期加载数据到离线系统(Hadoop或关系数据仓库).

消息传递语义

kafka在生产者和消费者之间提供的语义保证如下: 1.最多一次——消息可能会丢失,但永远不会重新传送。 2.至少一次——消息永远不会丢失,但可以重新传送。 3.恰好一次——这就是人们真正想要的,每条消息都传递一次且仅一次。

kafka提供的语义保证

  • 生产者发布消息时,消息被提交到日志,只要消息被复制到至少一个代理,就不会丢失,也就是我们说的ack为all/-1
  • 每个消息分配ID保证不会重复消费
    • 通过给每个消息分配ID,broker可以通过id排除重复的内容,确保消息不会被重复发送并保证消息在多个主题分区之间一致。
  • 通过事务性功能,保证消费者出错后将offset恢复,没出错就没事,并且还有读已提交和读未提交两种隔离级别

讲解了Kafka的副本复制以及节点容灾的各种机制,同时介绍了Kafka的日志压缩技术和配额功能

复制

Kafka通过在多台服务器上复制每个主题分区的日志来实现故障容错。这些副本可以自动接管故障服务器的角色,确保消息在出现故障时仍然可用。

复制

  • 每个主题分区在非故障情况下都有一个领导者和零个或多个追随者,构成复制因子。
    • 所有写入操作都发送到领导者,而读取可以发送到领导者或追随者。
    • 追随者从领导者那里获取消息并应用到自己的日志中。
  • 使用特殊节点“控制器”来管理集群中的代理注册,确保活跃性条件得到满足。
    • 只要至少有一个同步副本处于活动状态,已提交的消息就不会丢失。
    • 负责管理broker的注册,当探测到代理发生故障时负责选举ISR的剩余成员之一作为新的领导者。这可以批量处理大量分区的领导者变更通知,从而提高效率和速度。
  • 提供了故障转移和高可用性功能,但在网络分区情况下可能无法保持可用。 ISR
  • 各个节点满足与控制器保持活动会话,如果是flower复制leader的消息不能落后太多,这样的节点称为“同步”,以避免“活动”或“失败”的模糊性
  • leader维持一组“同步”副本成为ISR,如果一旦有一个节点不满足上述的其中一点,则会被踢出

复制日志:仲裁、ISR 和状态机

  • 复制日志的核心原理:复制日志是分布式系统中的基本原语,通过模拟达成共识的过程来确保数据一致性。
  • 领导者和追随者:领导者选择值的顺序,追随者复制领导者的值。当领导者失败时需要选举新的领导者,确保数据的完整性和一致性。
  • 多数投票:常用的选举领导者和提交决策的方法,确保系统的高可用性和容错性。
  • Kafka 的 ISR 模型:使用动态维护的同步副本集选择leader,确保面对故障时能够容忍和高可用。
  • 设计差异:不要求节点恢复时数据完好无损,采用 ISR 模型和较低的复制因子来提高性能和减少磁盘空间需求。

如果所有的ISR节点都G了怎么办?

一般来说只有两种选择,保证可用或者保证一致

  • 等待 ISR 中的副本恢复并选择该副本作为领导者(希望它仍然拥有所有数据)。
    • 这种就是保持一致,但是只要这些副本关闭,我们就将保持不可用状态。
  • 选择第一个作为领导者复活的副本(不一定在 ISR 中)。
    • 不在ISR中选择,而是直接选择一个没有死亡的节点,但是由于其已经被踢出ISR说明数据与leader不一致,并且其成为新的leader就会把数据同步到其他节点,那么就会造成数据永久丢失,但是至少保证了可以使用

可用和耐用的保证

kafka通过ack=all来保证所有的ISR副本节点都将新的消息写入

  • 禁用不干净的领导者选举 - 如果所有副本都不可用,则分区将保持不可用,直到最近的领导者再次可用。
    • 这实际上更倾向于不可用而不是消息丢失的风险。
  • 指定最小 ISR 大小 - 仅当 ISR 大小高于某个最小值时,分区才会接受写入
    • 以防止仅写入单个副本的消息丢失,该副本随后变得不可用。

副本管理

  • 多个分区的管理:Kafka集群管理数百/数千个分区,通过循环方式平衡分区分布,避免节点承载过多分区。
  • 领导力的平衡:努力确保每个节点都能领导其分区的一定比例,以实现领导力的均衡分配。
  • 优化领导选举流程:领导者选举是关键窗口,需要进行优化以提高效率。

日志压缩

日志压缩-实际上指的是一种数据保留机制,而不是传统意义上的压缩算法或技术

  • 是一种更细粒度的保留机制,会保留每个主键对应的最后一次更新(保留关键信息),类似于Redis将AOF进行优化压缩的机制,把不需要保留的日志删除(节约资源),可以提高重启后快速重建。
  • 应用场景:
    • 数据库变更订阅:通过保留关键信息、实时同步数据以及节约资源等方式,有效支持数据库变更订阅功能,订阅者能够及时获取最新的数据变更情况
    • 事件溯源:将更改日志作为应用程序的主要存储,有利于数据的快速构建,并且可以追溯到每个主键的最终状态。
    • 高可用性的日志记录:通过记录本地状态的更改来实现容错,以便在失败时能够快速重新加载并继续执行。

日志压缩提供的保证

  1. 参数控制消息写入和压缩时间限制:
    1. 主题中的min.compaction.lag.ms参数规定了消息写入后必须经过的最短时间长度才能被压缩
    2. 而max.compaction.lag.ms参数则规定了消息写入和消息适合压缩的时间之间的最大延迟。
    3. 这样可以确保消息在一定时间内被正确地压缩和删除。
  2. 消息顺序和偏移量保持不变: 日志压缩不会重新排序消息,只是删除一些消息,因此消息的顺序永远保持不变。
    1. 此外,消息的偏移量也永远不会改变,它是日志中某个位置的永久标识符。
  3. 消费者获取消息保证: 任何从日志开始进行的消费者都将按照写入顺序看到所有记录的最终状态。
    1. 如果消费者在到达日志头部的时间小于delete.retention.ms参数所设置的时间段内,将看到已删除记录的所有删除标记。
    2. 消费者需要注意,在delete.retention.ms时间段后会错过已删除记录的删除标记。

日志压缩详细信息

通过一个后台线程池构建的日志清理器,重新复制日志段文件,删除其键出现在日志头部的记录 工作原理

  1. 它选择具有最高日志头与日志尾比的日志
  2. 它为日志头部的每个键创建最后一个偏移量的简洁摘要
  3. 它从头到尾重新复制日志,删除日志中稍后出现的键。新的、干净的段会立即交换到日志中,因此所需的额外磁盘空间只是一个额外的日志段(而不是日志的完整副本)。
  4. 日志头的摘要本质上只是一个空间紧凑的哈希表。每个条目正好使用 24 个字节。因此,使用 8GB 的​​清理缓冲区,一次清理迭代可以清理大约 366GB 的日志头(假设有 1k 条消息)。

配额

Kafka 集群能够对请求强制实施配额,以控制客户端使用的代理资源。Kafka 代理可以为共享配额的每组客户端强制执行两种类型的客户端配额:

  1. 网络带宽配额定义字节率阈值(自 0.9 起)
  2. 请求率配额将 CPU 利用率阈值定义为网络和 I/O 线程的百分比(自 0.11 起)

为什么需要配额

  • 生产者和消费者有可能产生/消耗非常大量的数据或以非常高的速率生成请求,从而垄断代理资源,导致网络饱和,并且通常会 DOS 其他客户端和代理本身。
  • 拥有配额可以防止这些问题,并且在大型多租户集群中尤为重要,其中一小部分行为不良的客户端可能会降低行为良好客户端的用户体验。
  • 事实上,当将 Kafka 作为服务运行时,这甚至可以根据商定的合同强制实施 API 限制。

客户群体

Kafka 客户端的身份是用户主体,它代表安全集群中经过身份验证的用户。在支持未经身份验证的客户端的集群中,用户主体是代理使用可配置的PrincipalBuilder. Client-id 是客户端的逻辑分组,具有由客户端应用程序选择的有意义的名称。元组(用户、客户端 ID)定义共享用户主体和客户端 ID 的安全逻辑客户端组。

配额可以应用于(用户、客户端 ID)、用户或客户端 ID 组。对于给定连接,将应用与该连接匹配的最具体的配额。配额组的所有连接共享为该组配置的配额。例如,如果 (user="test-user", client-id="test-client") 的生产配额为 10MB/秒,则该配额将在用户“test-user”的所有生产者实例与客户端之间共享id“测试客户端”。

具体配置

4.9