Kafka生产者理解

目录

 

生产逻辑

参数说明

发送消息模式

代码实现

序列化器

分区器

生产者客户端的内部原理

参考资料


生产逻辑

1)配置生产者客户端参数及创建相应的生产者实例;

2)构建待发送的消息;

3)发送消息;

4)关闭生产者实例。

参数说明

1)bootstrap.servers:指定连接Kafka 集群所需的broker 地址清单,具体的内容格式为hostl:portl,host2:port2 ,可以设置一个或多个地址,中间以逗号隔开,此参数的默认值为“” 。注意这里并非需要所有的broker 地址,因为生产者会从给定的broker 里查找到其他broker 的信息。不过建议至少要设置两个以上的broker 地址信息,当其中任意一个岩机时,生产者仍然可以连接到Kafka集群上。

2)key.serializer:消息中key 对应的序列化类,需要实现org.apache.kafka.common.serialization.Serializer接口,此参数的默认值为“”。

3)value.serializer:消息中value 对应的序列化类,需要实现org.apache.kafka.common.serialization.Serializer接口,此参数的默认值为“”。

4)client.id:这个参数用来设定KafkaProducer 对应的客户端id ,默认值为“” 。如果客户端不设置, 则KafkaProducer 会自动生成一个非空字符串,内容形式如“ producer-1”“producer-2”。

5)buffer.memory:生产者客户端中用于缓存消息的缓冲区大小,默认值为33554432(32MB)。

6)batch.size:用于指定ProducerBatch 可以复用内存区域的大小,默认值为16384(16KB)。

7)max.in.flight.requests.per.connection:限制每个连接(也就是客户端与Node之间的连接)最多缓存的请求数,默认值为5。

8)acks:指定分区中必须要有多少个副本收到这条消息,之后生产者才会认为这条消息是成功写入的。涉及消息的可靠性和吞吐量之间的权衡。默认值为"1",注意此值类型为string。                                                                                                                  acks=1。生产者发送消息之后,只要分区的leader 副本成功写入消息,那么它就会收到来自服务端的成功响应。如果消息无法写入leader 副本,比如在leader副本崩溃、重新选举新的leader 副本的过程中,那么生产者就会收到一个错误的响应,为了避免消息丢失,生产者可以选择重发消息。如果消息写入leader 副本并返回成功响应给生产者,且在被其他follower 副本拉取之前leader 副本崩溃,那么此时消息还是会丢失,因为新选举的leader副本中并没有这条对应的消息。acks设置为1,是消息可靠性和吞吐量之间的折中方案;
acks=0。生产者发送消息之后不需要等待任何服务端的响应。如果在消息从发送到写入Kafka 的过程中出现某些异常,导致Kafka 并没有收到这条消息,那么生产者也无从得知,消息也就丢失了。在其他配置环境相同的情况下,acks设置为0可以达到最大的吞吐量;
acks=-1或acks=all。生产者在消息发送之后,需要等待ISR中的所有副本都成功写入消息之后才能够收到来自服务端的成功响应。在其他配置环境相同的情况下,acks设置为-1 (all)可以达到最强的可靠性。但这并不意味着消息就一定可靠,因
为ISR中可能只有leader副本,这样就退化成了acks=1 的情况。

9)max.request.size:这个参数用来限制生产者客户端能发送的消息的最大值,默认值为1048576B ,即1MB 。一般情况下,这个默认值就可以满足大多数的应用场景了。不建议盲目地增大这个参数的配置值,尤其是在对Kafka 整体脉络没有足够把控的时候。因为这个参数还涉及一些其他参数的联动。

10)retries:retries参数用来配置生产者重试的次数,默认值为0,即在发生异常的时候不进行任何重试动作。消息在从生产者发出到成功写入服务器之前可能发生一些临时性的异常, 比如网络抖动、leader副本的选举等,这种异常往往是可以自行恢复的,生产者可以通过配置retries大于0 的值,以此通过内部重试来恢复而不是一昧地将异常抛给生产者的应用程序。如果重试
达到设定的次数,那么生产者就会放弃重试并返回异常。

11)retry.backoff.ms:默认值为100 , 它用来设定两次重试之间的时间间隔,避免无效的频繁重试。

注意:Kafka 可以保证同一个分区中的消息是有序的。如果生产者按照一定的顺序发送消息,那么这些消息也会顺序地写入分区,进而消费者也可以按照同样的顺序消费它们。对于某些应用来说,顺序性非常重要,如果出现错误就会造成非常严重的后果。如果将acks参数配置为非零值,并且max.in.flight.requests.per.connection参数配置为大于1的值,那么就会出现错序的现象: 如果第一批次消息写入失败, 而第二批次消息写入成功,那么生产者会重试发送第一批次的消息, 此时如果第一批次的消息写入成功,那么这两个批次的消息就出现了错序。一般而言,在需要保证消息顺序的场合建议把参数
max.in.flight.requests.per.connection 配置为1 ,而不是把acks 配置为0 ,不过这样也会影响整体的吞吐。

发送消息模式

KafkaProducer是线程安全的,可以在多个线程中共享单个KafkaProducer 实例,也可以将KafkaProducer实例进行池化来供其他线程调用。
构建消息,即创建ProducerRecord对象。在实际的应用中,还会用到其他构造方法,比如要指定key ,或者添加headers等。有可能会遇到这些构造方法都不满足需求的情况,需要自行添加更多的构造方法。注意,针对不同的消息,需要构建不同的ProducerRecord 对象,在实际应用中创建ProducerRecord 对象是一个非常频繁的动作。
发送消息主要有三种模式:发后即忘(fire-and-forget)、同步(sync)及异步(async)。

发后即忘,它只管往Kafka 中发送消息而并不关心消息是否正确到达。在大多数情况下,这种发送方式没有什么问题, 不过在某些时候( 比如发生不可重试异常时〉会造成消息的丢失。这种发送方式的性能最高,可靠性也最差。

同步发送的方式可靠性高,要么消息被发送成功,要么发生异常。如果发生异常,则可以捕获并进行相应的处理,而不会像“发后即忘”的方式直接造成消息的丢失。不过同步发送的方式的性能会差很多,需要阻塞等待一条消息发送完之后才能发送下一条。

异步发送,使用Callback 的方式非常简洁明了,Kafka有响应时就会回调,要么发送成功,要么抛出异常。onCompletion()方法的两个参数是互斥的,消息发送成功时, metadata 不为null 而exception 为null:消息发送异常时,metadata 为null 而exception 不为null 。

close()方法会阻塞等待之前所有的发送请求完成后再关闭KafkaProducer。如果调用了带超时时间timeout的close()方法,那么只会在等待timeout时间内来完成所有尚未完成的请求处理, 然后强行退出。在实际应用中,一般使用的都是无参的close()方法。

代码实现

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class KafkaProducerTest {
    public static final String brokerList = "host1:9092,host2:9092";
    public static final String topic = "test";

    public static Properties initConfig(){
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,brokerList);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        props.put(ProducerConfig.CLIENT_ID_CONFIG,"clientDemo");
        props.put(ProducerConfig.RETRIES_CONFIG,10);
        return props;
    }

    public static void main(String[] args){
        Properties props= initConfig();
        KafkaProducer<String,String> producer = new KafkaProducer<>(props);
        ProducerRecord<String,String> record = new ProducerRecord<>(topic,"hello,test");//此处可以有多种实现,比如指定headers,partition,key等
        //发后即忘:
        producer.send(record);
//        //同步:
//        try{
//            Future<RecordMetadata> future =  producer.send(record);
//            RecordMetadata metadata = future.get();
//            System.out.println(metadata.topic() +"----" +
//            metadata.partition() + ":"+ metadata.offset());
//        }catch(InterruptedException|ExecutionException e){
//            e.printStackTrace();
//        }
//        //异步:
//        producer.send(record, new Callback() {
//            public void onCompletion(RecordMetadata metadata, Exception exception) {
//                if (exception != null){
//                    exception.printStackTrace();
//                }else{
//                    System.out.println(metadata.topic() +"----" +
//                            metadata.partition() + ":"+ metadata.offset());
//                }
//            }
//        });

        producer.close();
    }

}

序列化器

生产者需要用序列化器(Serializer)把对象转换成字节数组才能通过网络发送给Kafka 。而在对侧,消费者需要用反序列化器(Deserializer)把从Kafka 中收到的字节数组转换成相应的对象。
生产者使用的序列化器和消费者使用的反序列化器是需要一一对应的,如果生产者使用了某种序列化器,比如StringSerializer,而消费者使用了另一种序列化器,比如IntegerSerializer ,那么是无法解析出想要的数据的。

分区器

消息在通过send()方法发往broker的过程中,有可能需要经过拦截器( Interceptor )、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能被真正地发往broker 。拦截器一般不是必需的,而序列化器是必需的。消息经过序列化之后就需要确定它发往的分区,如果消息ProducerRecord中指定了partition字段,那么就不需要分区器的作用,因为partition 代表的就是所要发往的分区号;如果消息ProducerRecord 中没有指定partition 字段,那么就需要依赖分区器, 根据key这个字段来计算partition 的值。分区器的作用就是为消息分配分区。Kafka中提供的默认分区器是org.apache.kafka.clients.producer.intemals.DefaultPartitioner。

在默认分区器DefaultPartitioner的实现中,close()是空方法,而在partition()方法中定义了主要的分区分配逻辑。如果key不为null,那么默认的分区器会对key进行哈希(采用MurmurHash2算法,具备高运算性能及低碰撞率),最终根据得到的哈希值来计算分区号,拥有相同key的消息会被写入同一个分区。如果key为null ,那么消息将会以轮询的方式发往主题内的各个可用分区。

生产者客户端的内部原理

整个生产者客户端由两个线程协调运行,这两个线程分别为主线程和Sender线程(发送线程)。在主线程中由KafkaProducer创建消息,然后通过可能的拦截器、序列化器和分区器的作用之后缓存到消息累加器(RecordAccumulator,也称为消息收集器〉中。Sender线程负责从RecordAccumulator中获取消息并将其发送到Kafka 中。

参考资料

《深入理解Kafka:核心设计与实践原理》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值