阅读 452

Kafka Producer

Kafka ProducerKafka 客户端中的一种,用于投递消息到 kafka Topic

消息投递时的负载均衡

我们知道,一个 TopicKafka 拆分成多个 Partition 存储在不同的 broker ,为了更好的利用 Partition 特性来提高 Kafka 性能,很容易想到,消息投递时,最好把消息均匀的投递到各个 Partition 上。

Producer 消息投递策略

Producer 会根据消息投递是的情况,选择不同的投递策略

  • 消息指定了 Partition :这种情况下,直接将消息投递到指定的 Partition

  • 消息没有指定 Partition 但设置了 key :这种情况下,将 keyhash 值与 topicpartition 数量进行取余,得到 partition 的值

  • 消息既没有指定 partition 又没有设置 key 时 :第一次调用时随机生成一个整数,后面每次调用在这个整数上自增,将这个值与 topicpartition 数取余得到 partition 值,也就是常说的 round-robin (轮询) 算法

可以看出,在不手动指定 Partition 的情况下,Producer 的消息投递都是负载均衡的

异步(批量)发送消息

Producer 不仅可以做到消息投递时的负载均衡,还通过批量发送实现更高的吞吐量

kafka 0.11.x 之前 Producer 都是同步发送消息的,即生产了一条,投递一条。这种方式虽然及时性最高,但也存在一个问题,一条消息就发送一次,网络开销太大了。

kafka 0.11.x 开始 Producer 提供了异步发送消息的功能,即,将生产的消息先缓存起来,等满足一定的条件后,再批量发送

批格式的消息

Producer 会将这一批数据合并成一条"大消息",发送出去,这条"大消息"其实就是 kafka 0.11.x 新增加的批格式的消息

image.png

异步(批量)发送消息相关的配置

上面说到异步(批量)发送消息需要满足一定的条件,具体有哪些条件呢? Producer 提供了一些配置可供选择

批大小

一批消息的大小,单位字节,默认值16384,也就是 16K

batch.size=16384
复制代码

Producer 会为每一个 Partition 在内存中一个指定批大小的缓冲区,只有发往某一 Partition 的消息累计到了指定大小,才会开始投递。

该值不宜过大或过小。过小会降低吞吐量,为零将完全禁用批处理,过大可能会浪费内存

批暂存时长

设置批大小后,如果某一 Partition 缓冲区中的消息迟迟没有达到指定大小,消息会留在 Producer 端,造成消息延迟过大,为了解决这一问题,需要为设置缓冲区设置一个暂存时间,单位毫秒,默认值为0

linger.ms=0
复制代码

当到达这个时间后,不管消息大小有没有达到指定大小,都发送,防止延迟过大。

acks

为了确保消息到达了服务端, Producer 发送消息后,需要等待 broker 的 ack 响应, Producer 为消息提供了一个 acks 的属性来控制这一行为,它有三个值

  • acks=0 : 默认发送成功,Producer 不会等待服务器的任何确认,每个记录返回的偏移量将始终设置为-1。该配置相当于最多一次语义

  • acks=1 : 默认值, Leader replication 将记录写入其本地日志后响应 ack,无需等待 ISR 中的其它副本确认。如果此时 Leader replication 宕机了,那么消息还是会丢失

acks=-1 OR acks=all : Leader replication 等待 ISR 中的所有副本回复确认信息后响应 ack。这种情况下,即使 Leader replication 宕机消息也不会丢失,但代价就是延迟最高,效率最低

ack 超时与消息重投

不管是将 acks 设置成 1 还是 -1 都面临着响应超时的问题,当消息发送之后,久久没有收到 ack 回复,就需要考虑消息可能丢失了,需要重新投递

Producer 通过 request.timeout.ms 来控制请求超时时间,单位为毫秒,默认值为 30000 也就是 30 秒

request.timeout.ms=30000
复制代码

超过该时间还没有收到响应,则会重新发送

Producer 幂等性

消息重投之后必然会带来消息重复的问题,好在从 Kafka 0.11.0 开始,提供了幂等处理来进行消息去重。 要启用幂等性,需要将 Producerenable.idompotence 参数设置为 true

开启幂等性的 Producer 在初始化的时候会被分配一个 PID,对于每个 PID,消息发送到的每一个分区都有对应的序列号,这些序列号从 0 开始单调递增。生产者每发送一条消息就会将 <PID, 分区> 对应的序列号值加 1。

Broker 端在内存中为每一对 <PID, 分区> 维护一个序列号 SN_old,针对生产者发送来的每一条消息,对其序列号 SN_new 进行判断,并作相应处理。

  • 当 SN_new < SN_old + 1时,说明此消息已被写入,broker 直接丢弃该条消息
  • 当 SN_new = SN_old + 1 时, broker 才会接受这条消息
  • 当 SN_new > SN_old + 1时,说明中间有数据尚未写入,出现了消息乱序,可能存在消息丢失的现象,对应的 Producer 会抛出 OutOfOrderSequenceException 异常

Broker 通过消息的序列号和 <PID, 分区> ,保证了消息的顺序写入和去重

注意:序列号只针对 <PID, 分区> ,这意味着幂等性只能保证单个主题的单一分区内消息不重复。Producer 重启 PID 就会变化,如果将一次 Producer 的生命周期看作一次会话,那么它只能实现单会话上的幂等性。

Producer 事务

为了实现跨分区跨会话的幂等以及防止 Producer 重启 造成的 PID 变化,需要为 Topic 引入一个全局唯一的 Transaction ID,并将 Producer 获得的 PIDTransaction ID 绑定。这样当 Producer 重启后就可以通过正在进行的 TransactionID 获得原来的 PID

为了管理 TransactionKafka 引入了一个新的组件 Transaction CoordinatorProducer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的 PID 的 。TransactionCoordinator 还负责将所有事务写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

Producer Exactly-Once 语义

Producer 如要实现不重不漏,精确一次的 Exactly-Once 语义,需要启用幂等 enable.idompotence=true(默认为false )。如果禁用,Producer 将不会在请求中设置 PID 字段。

当启用幂等性时,Producer 强制 acks=allretries > 1max.inflight.requests.per.connection=1,如果没有配置就不能保证幂等性。如果应用程序未明确覆盖这些设置,则当启用幂等性时,Producer 将自动设置 。

但要实现跨分区跨会话的幂等,还需要开启 Producer 事务

注意,必须启用幂等性才能使用事务。

参考

Exactly Once Delivery and Transactional Messaging

文章分类
后端
文章标签