Kafka 生产者(Producer)详解

                             Producer : 消息生产者,就是向Kafka broker 发消息的客户端。

      下面将从producer的分区策略,数据可靠性保证、Exactly Once、Producer API 来进行介绍。

1、分区策略

1)分区原因

  1.    方便在集群中扩展(相当于负载) :每个Partition 可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
  2. 可以提高并发:因为可以以Partition为单位读写了;

2)分区原则

         我们需要将 producer 发送的数据封装成一个 ProducerRecord 对象。

  1. 指明 partition 的情况下,直接将指明的值作为partition的值
  2. 没有指明partition但有key的情况下,将key的hash值与topic的partition数据进行取余得到partition值
  3. 既没有partition值又没有key值的情况下,第一次调用是随机生成一个整数(后面调用在这个整数上自增),将这个值与topic可用的parition总数取余得到partition值,也就是常说的round-robin算法。

2、数据可靠性保证

         为保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后都需要向producer发送ack(acknowledgement 确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。

1)副本数据同步策略

方案 优点 缺点
半数以上完成同步,就发送ack 延迟低 选举新的leader时,容忍n台节点故障,需要2n+1个副本
全部完成同步,才发送ack 选举新的leader时,容忍n台节点故障,需要n+1 个副本 延迟高

 

 

 

 

 

Kafka选择了第二种方案,原因如下:

  1. 同样为了容忍 n 台节点的故障,第一种方案需要 2n+1 个副本,而第二种方案只需要 n+1 个副本,而 Kafka 的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
  2. 虽然第二种方案的网络延迟会比较高,但网络延迟对 Kafka 的影响较小。

2)ISR

        采用第二种方案之后,设想以下情景:leader 收到数据,所有 follower 都开始同步数据, 但有一个 follower,因为某种故障,迟迟不能与 leader 进行同步,那 leader 就要一直等下去, 直到它完成同步,才能发送 ack。这个问题怎么解决呢?

       Leader 维护了一个动态的 in-sync replica set (ISR),意为和 leader 保持同步的 follower 集 合。当 ISR 中的 follower 完成数据的同步之后,leader 就会给 follower 发送 ack。如果 follower 长时间 未 向 leader 同 步 数 据 , 则 该 follower 将 被 踢 出 ISR , 该 时 间 阈 值 由replica.lag.time.max.ms 参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader。

3)ack 应答机制

      对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失, 所以没必要等 ISR 中的 follower 全部接收成功。

    所以 Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡, 选择以下的配置。

acks 参数配置:

   acks:

       0:producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还 没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据

       1:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower 同步成功之前 leader 故障,那么将会丢失数据;

        -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盘成功后才 返回 ack。但是如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,那么会 造成数据重复。

4)故障处理细节

   Log文件中的HW和LEO

LEO:指的是每个副本最大的 offset;

HW:指的是消费者能见到的最大的 offset,ISR 队列中最小的 LEO。

(1)follower 故障

        follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘 记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。 等该 follower 的 LEO 大于等于该 Partition 的 HW,即 follower 追上 leader 之后,就可以重 新加入 ISR 了

(2)leader 故障

        leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader 同步数据。

        注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。

3、Exactly Once 语义

      将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,即 At Least Once 语义。相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被 发送一次,即 At Most Once 语义。

      At Least Once 可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once 可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说 交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。在 0.11 版 本以前的 Kafka,对此是无能为力的,只能保证数据不丢失,再在下游消费者对数据做全局 去重。对于多个下游应用的情况,每个都需要单独做全局去重,这就对性能造成了很大影响。

      0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论 向 Server 发送多少次重复数据,Server 端都只会持久化一条。幂等性结合 At Least Once 语 义,就构成了 Kafka 的 Exactly Once 语义。即:

      At Least Once + 幂等性 = Exactly Once

       要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。Kafka 的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在 初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而 Broker 端会对做缓存,当具有相同主键的消息提交时,Broker 只 会持久化一条。 但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨 分区跨会话的 Exactly Once。

 

4、Producer API 

1、消息发送流程

       Kafka 的 Producer 发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了 两个线程——main 线程和 Sender 线程,以及一个线程共享变量——RecordAccumulator。 main 线程将消息发送给 RecordAccumulator,Sender 线程不断从 RecordAccumulator 中拉取 消息发送到 Kafka broker。

相关参数:

 batch.size:只有数据积累到 batch.size 之后,sender 才会发送数据。

 linger.ms:如果数据迟迟未达到 batch.size,sender 等待 linger.time 之后就会发送数据。

2、异步发送API

1)不带回调函数的API

package com.bonc.qyl.producer;
/*
*   不带回调函数的kafka API
* */

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class ProducerTest {
    public static void main(String[] args) {
        Properties props = new Properties();
        //kafka集群,broker-list
        props.put("bootstrap.servers","qyl01:9092");
        props.put("acks","all");
        props.put("retries",1);  //重试次数
        props.put("batch.size",16384); //批次大小
        props.put("linger.mx",1);  //等待时间
        props.put("buffer.memory", 33554432); ///RecordAccumulator 缓冲区大小

        props.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String, String> producer = new KafkaProducer<String,String>(props);

        for(int i = 0 ;i < 100 ; i++){
            producer.send(new ProducerRecord<String, String>("first",
                    Integer.toString(i),Integer.toString(i)));
        }
        producer.close();
    }
}

2) 带回调函数的 API

     回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是 RecordMetadata 和 Exception,如果 Exception 为 null,说明消息发送成功,如果 Exception 不为 null,说明消息发送失败。

          注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。

package com.bonc.qyl.producer;

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.concurrent.ExecutionException;
import java.util.Properties;


public class ProducerComeAPI {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "qyl01:9092");//kafka 集群,broker-list
        props.put("acks", "all");
        props.put("retries", 1);//重试次数
        props.put("batch.size", 16384);//批次大小
        props.put("linger.ms", 1);//等待时间
        props.put("buffer.memory", 33554432);//RecordAccumulator 缓冲区大小
        props.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);
        for(int i = 0; i < 100; i++){
            producer.send(new ProducerRecord<String, String>(
                    "first", Integer.toString(i), Integer.toString(i)
            ), new Callback() {

                //回调函数,该方法会在producer收到ack时调用,为异步调用
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                  if(e == null){
                      System.out.println("successs ->" + recordMetadata.offset());
                  }else{
                      e.printStackTrace();
                  }
                }
            });
        }
        producer.close();
    }
}

  3、 同步发送 API

            同步发送的意思就是,一条消息发送之后,会阻塞当前线程,直至返回 ack。

            由于 send 方法返回的是一个 Future 对象,根据 Futrue 对象的特点,我们也可以实现同 步发送的效果,只需在调用 Future 对象的 get 方发即可。

package com.bonc.qyl.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class CustomProducer {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
 Properties props = new Properties();
 props.put("bootstrap.servers", "hadoop102:9092");//kafka 集
群,broker-list
 props.put("acks", "all");
 props.put("retries", 1);//重试次数
 props.put("batch.size", 16384);//批次大小
 props.put("linger.ms", 1);//等待时间
 props.put("buffer.memory", 33554432);//RecordAccumulator 缓
冲区大小
 props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 Producer<String, String> producer = new
KafkaProducer<>(props);
 for (int i = 0; i < 100; i++) {
 producer.send(new ProducerRecord<String, String>("first",
Integer.toString(i), Integer.toString(i))).get();
 }
 producer.close();
 }
}

4、依赖

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>0.11.0.0</version>
        </dependency>

 

发布了93 篇原创文章 · 获赞 107 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章