Kafka

5 篇文章 0 订阅

Kafka 为每个主题维护了分布式的分区(Partition)日志文件,每个 Partition 在 Kafka 存储层面是 Append Log。

任何发布到此 Partition 的消息都会被追加到 Log 文件的尾部,在分区中的每条消息都会按照时间顺序分配到一个单调递增的顺序编号,也就是我们的 offset。offset 是一个 Long 型的数字。

1、Kafka中,生产者如何把信息发送到Partition

如果没有 Key 值则进行轮询发送。

如果有 Key 值,对 Key 值进行 Hash,然后对分区数量取余,保证了同一个 Key 值的会被路由到同一个分区;如果想队列的强顺序一致性,可以让所有的消息都设置为同一个 Key。

2、网络模型

Kafka Client:单线程 Selector。

单线程模式适用于并发链接数小,逻辑简单,数据量小的情况。在 Kafka 中,Consumer 和 Producer 都是使用的上面的单线程模式。

这种模式不适合 Kafka 的服务端,在服务端中请求处理过程比较复杂,会造成线程阻塞,一旦出现后续请求就会无法处理,会造成大量请求超时,引起雪崩。而在服务器中应该充分利用多线程来处理执行逻辑。

Kafka Server:多线程 Selector

在这里插入图片描述
在 Kafka 服务端采用的是多线程的 Selector 模型,Acceptor 运行在一个单独的线程中,对于读取操作的线程池中的线程都会在 Selector 注册 Read 事件,负责服务端读取请求的逻辑。

成功读取后,将请求放入 Message Queue共享队列中。然后在写线程池中,取出这个请求,对其进行逻辑处理。

这样,即使某个请求线程阻塞了,还有后续的线程从消息队列中获取请求并进行处理,在写线程中处理完逻辑处理,由于注册了 OP_WIRTE 事件,所以还需要对其发送响应。

3、副本机制

Kafka 的副本机制是多个服务端节点对其他节点的主题分区的日志进行复制。
当集群中的某个节点出现故障,访问故障节点的请求会被转移到其他正常节点(这一过程通常叫 Reblance)。
Kafka 每个主题的每个分区都有一个主副本以及 0 个或者多个副本,副本保持和主副本的数据同步,当主副本出故障时就会被替代。
在 Kafka 中并不是所有的副本都能被拿来替代主副本,所以在 Kafka 的 Leader 节点中维护着一个 ISR(In Sync Replicas)集合。也叫正在同步中集合,在这个集合中的需要满足两个条件:

  • 节点必须和 ZK 保持连接。
  • 在同步的过程中这个副本不能落后主副本太多。

另外还有个 AR(Assigned Replicas)用来标识副本的全集,OSR 用来表示由于落后被剔除的副本集合。

所以公式如下:ISR = Leader + 没有落后太多的副本;AR = OSR+ ISR。

HW(高水位)是 Consumer 能够看到的此 Partition 的位置,LEO 是每个 Partition 的 Log 一条 Message 的位置。

HW 能保证 Leader 所在的 Broker 失效,该消息仍然可以从新选举的 Leader 中获取,不会造成消息丢失。

当 Producer 向 Leader 发送数据时,可以通过 request.required.acks 参数来设置数据可靠性的级别:

1(默认):这意味着 Producer 在 ISR 中的 Leader 已成功收到的数据并得到确认后发送下一条 Message。如果 Leader 宕机了,则会丢失数据。
0:这意味着 Producer 无需等待来自 Broker 的确认而继续发送下一批消息。这种情况下数据传输效率高,但是数据可靠性却是不稳定的。
-1:Producer 需要等待 ISR 中的所有 Follower 都确认接收到数据后才算一次发送完成,可靠性最高。

但是这样也不能保证数据不丢失,比如当 ISR 中只有 Leader 时(其他节点都和 ZK 断开连接,或者都没追上),这样就变成了 acks = 1 的情况。

4、高可用模型及幂等

在分布式系统中一般有三种处理语义:

at-least-once

至少一次,有可能会有多次。如果 Producer 收到来自 Ack 的确认,则表示该消息已经写入到 Kafka 了,此时刚好是一次,也就是我们后面的 Exactly-once。

但是如果 Producer 超时或收到错误,并且 request.required.acks 配置的不是 -1,则会重试发送消息,客户端会认为该消息未写入 Kafka。

如果 Broker 在发送 Ack 之前失败,但在消息成功写入 Kafka 之后,这一次重试将会导致我们的消息会被写入两次。

所以消息就不止一次地传递给最终 Consumer,如果 Consumer 处理逻辑没有保证幂等的话就会得到不正确的结果。

在这种语义中会出现乱序,也就是当第一次 Ack 失败准备重试的时候,但是第二消息已经发送过去了,这个时候会出现单分区中乱序的现象。

我们需要设置 Prouducer 的参数 max.in.flight.requests.per.connection,flight.requests 是 Producer 端用来保存发送请求且没有响应的队列,保证 Producer端未响应的请求个数为 1。

at-most-once

如果在 Ack 超时或返回错误时 Producer 不重试,也就是我们讲 request.required.acks = -1,则该消息可能最终没有写入 Kafka,所以 Consumer 不会接收消息。

exactly-once

刚好一次,即使 Producer 重试发送消息,消息也会保证最多一次地传递给 Consumer。该语义是最理想的,也是最难实现的。

在 0.10 之前并不能保证 exactly-once,需要使用 Consumer 自带的幂等性保证。0.11.0 使用事务保证了。

如何实现 exactly-once

要实现 exactly-once 在 Kafka 0.11.0 中有两个官方策略:

单 Producer 单 Topic

每个 Producer 在初始化的时候都会被分配一个唯一的 PID,对于每个唯一的 PID,Producer 向指定的 Topic 中某个特定的 Partition 发送的消息都会携带一个从 0 单调递增的 Sequence Number。

在我们的 Broker 端也会维护一个维度为,每次提交一次消息的时候都会对齐进行校验:

  1. 如果消息序号比 Broker 维护的序号大一以上,说明中间有数据尚未写入,也即乱序,此时 Broker 拒绝该消息,Producer 抛出 InvalidSequenceNumber。
  2. 如果消息序号小于等于 Broker 维护的序号,说明该消息已被保存,即为重复消息,Broker 直接丢弃该消息,Producer 抛出 DuplicateSequenceNumber。
  3. 如果消息序号刚好大一,就证明是合法的。

上面所说的解决了两个问题:

  1. 当 Prouducer 发送了一条消息之后失败,Broker 并没有保存,但是第二条消息却发送成功,造成了数据的乱序。
  2. 当 Producer 发送了一条消息之后,Broker 保存成功,Ack 回传失败,Producer 再次投递重复的消息。

上面所说的都是在同一个 PID 下面,意味着必须保证在单个 Producer 中的同一个 Seesion 内,如果 Producer 挂了,被分配了新的 PID,这样就无法保证了,所以 Kafka 中又有事务机制去保证。

5、事务

在 Kafka 中事务的作用是:

  1. 实现 exactly-once 语义。
  2. 保证操作的原子性,要么全部成功,要么全部失败。
  3. 有状态的操作的恢复。

事务可以保证就算跨多个,在本次事务中的对消费队列的操作都当成原子性,要么全部成功,要么全部失败。

并且,有状态的应用也可以保证重启后从断点处继续处理,也即事务恢复。

在 Kafka 的事务中,应用程序必须提供一个唯一的事务 ID,即 Transaction ID,并且宕机重启之后,也不会发生改变。

Transactin ID 与 PID 可能一一对应,区别在于 Transaction ID 由用户提供,而 PID 是内部的实现对用户透明。

为了 Producer 重启之后,旧的 Producer 具有相同的 Transaction ID 失效,每次 Producer 通过 Transaction ID 拿到 PID 的同时,还会获取一个单调递增的 Epoch。

由于旧的 Producer 的 Epoch 比新 Producer 的 Epoch 小,Kafka 可以很容易识别出该 Producer 是老的,Producer 并拒绝其请求。

为了实现这一点,Kafka 0.11.0.0 引入了一个服务器端的模块,名为 Transaction Coordinator,用于管理 Producer 发送的消息的事务性。

该 Transaction Coordinator 维护 Transaction Log,该 Log 存于一个内部的 Topic 内。

由于 Topic 数据具有持久性,因此事务的状态也具有持久性。Producer 并不直接读写 Transaction Log,它与 Transaction Coordinator 通信,然后由 Transaction Coordinator 将该事务的状态插入相应的 Transaction Log。

Transaction Log 的设计与 Offset Log 用于保存 Consumer 的 Offset 类似。

6、Consumer Group (CG)

消息系统有两类,一是广播,二是订阅发布。广播是把消息发送给所有的消费者;发布订阅是把消息只发送给订阅者。Kafka通过Consumer Group组合实现了这两种机制: 实现一个topic消息广播(发给所有的consumer)和单播(发给任意一个consumer)。一个topic可以有多个CG。
topic的消息会复制(不是真的复制,是概念上的)到所有的CG,但每个CG只会把消息发给该CG中的一个 consumer(这是实现一个Topic多Consumer的关键点:为一个Topic定义一个CG,CG下定义多个Consumer)。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
典型的应用场景是,多个Consumer来读取一个Topic(理想情况下是一个Consumer读取Topic的一个Partition),那么可以让这些Consumer属于同一个Consumer Group即可实现消息的多Consumer并行处理,原理是Kafka将一个消息发布出去后,ConsumerGroup中的Consumers可以通过Round Robin的方式进行消费(Consumers之间的负载均衡使用Zookeeper来实现)。

7、Zookeeper在Kakfa中扮演的角色

Kafka将元数据信息保存在Zookeeper中,但是发送给Topic本身的数据是不会发到Zk上的,否则Zk就疯了。

  1. kafka使用zookeeper来实现动态的集群扩展,不需要更改客户端(producer和consumer)的配置。broker会在zookeeper注册并保持相关的元数据(topic,partition信息等)更新。
  2. 而客户端会在zookeeper上注册相关的watcher。一旦zookeeper发生变化,客户端能及时感知并作出相应调整。这样就保证了添加或去除broker时,各broker间仍能自动实现负载均衡。这里的客户端指的是Kafka的消息生产端(Producer)和消息消费端(Consumer)。
  3. Broker端使用zookeeper来注册broker信息,以及监测partition leader存活性。
  4. Consumer端使用zookeeper用来注册consumer信息,其中包括consumer消费的partition列表等,同时也用来发现broker列表,并和partition leader建立socket连接,并获取消息。
  5. Zookeeper和Producer没有建立关系,只和Brokers、Consumers建立关系以实现负载均衡,即同一个Consumer Group中的Consumers可以实现负载均衡。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值