Kafka producer

本文详细介绍了KafkaProducer的工作原理,包括消息发送流程、分区策略、序列化机制、错误处理、配置参数以及如何自定义分区器和序列化器。还探讨了消息的持久性和无消息丢失配置,以及如何利用拦截器增强producer功能。最后,讨论了多线程处理和消息压缩的考量因素。

Kafka producer

producer概览

Kafka producer客户端负责向Kafka写入数据的应用程序。Kafka提供了多个语言版本的producer客户端。Kafka封装了一套二进制通信协议,对于producer而言,用户可以直接使用任意语言按照该协议的格式进行编程,实现消息的发送。

producer之间是相互独立的,producer首要功能就是向某个topic的某个分区发送消息,由分区器确定往topic的哪个分区发送消息,Kafka producer提供了一个默认的分区器,每条待发送消息指定key,默认分区器将根据key的哈希值选择目标分区如果没有指定key,默认分区器将使用轮询方式确认目标分区使消息能均匀分布在所有分区上。在确定目标分区后,需要确定这个分区的leader,即该分区leader副本所在的broker。producer有多种选择来实现消息发送,例如不等待任何副本的响应便返回成功,或只等待leader副本响应写入操作之后再返回成功
在这里插入图片描述
producer使用用户主线程即运行运行producer的线程将待发送消息封装进ProducerRecord类实例,并将其序列化后发送到partitioner,再由其确定目标分区后一同发送到位于producer程序中的一块内存缓冲区中,producer的另一个工作线程(I/O发送线程,即Sender线程)将实时从缓冲区中提取准备就绪的消息封装进一个批次,统一发送给对应的broker

构造producer

producer程序实例

以下代码为发送消息实例

    package com.aim.kafka.client.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;
    public class Sender {

        public void sendMessage(){
            //创建属性对象
            Properties properties = new Properties();
            //设置broker的地址,可以支持多个,这样当某些broker挂掉还可以使用其它broker。必须指定
            properties.put("bootstrap.servers", "172.23.16.83:9092");
            //发送到broker端的任何消息格式都必须是字节数组,因此需要进行序列化,指定key的序列化器,除使用默认提供的
            //序列化器,也可以通过实现Serializer接口,创建自定义序列化器
            properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            //指定value部分的序列化器
            properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            //指定Producer确认请求发送完成之前需要搜集到的反馈信息数量,0将不等待任何服务器的反馈直接认为发送成功
            //将等待leader节点写入本地日志,则认为发送成功,不关心follower节点同步情况,leader节点挂掉,但follwer节点又
            //没来得及同步,将导致消息丢失;all或-1 leader节点将等待所有副本确认之后在确认这条消息发送成功
            properties.put("acks", "-1");
            //设置发送消息重试次数
            properties.put("retries", 3);
            //批次大小
            properties.put("batch.size", 323840);
            //延迟时间
            properties.put("linger.ms", 10);
            //缓冲大小
            properties.put("buffer.memory", 33554432);
            //最大阻塞时间
            properties.put("max.block.ms", 3000);
            //构建生产者
            Producer producer = new KafkaProducer(properties);
            //发送消息
            producer.send(new ProducerRecord("my-topic", Integer.toBinaryString(1), Integer.toString(1)));
            //关闭生产者
            producer.close();
        }
    }

构造KafkaProducer对象:

  • 除直接通过属性对象初始化创建外也可指定keySerializer、valueSerializer创建
    Serializer<String> keySerializer = new StringSerializer();
    Serializer<String> valueSerializer = new StringSerializer();
    //构建生产者
    Producer producer2 = new KafkaProducer(properties, keySerializer, valueSerializer);

构造ProducerRecord对象

  • Java版本Producer使用ProducerRecord来表示每条消息,ProducerRecord提供了多个重载构造函数初始化,可以指定消息topic、分区、时间戳、key、value、headers等内容

发送消息

  • Producer发送消息的主方法是send方法,send方法有两个重载的方法,但Producer底层完全实现了异步化发送,并通过Future同时实现了同步发送和异步+回调两种方式

异步发送

    //构建生产者
    Producer producer2 = new KafkaProducer(properties, keySerializer, valueSerializer);
    producer2.send(new ProducerRecord("my-topic", "2"), (metadata, exception) -> {
        if(exception == null) {
            //消息发送成功
        } else {
            //执行错误处理逻辑
        }
    });

同步发送

    //构建生产者
    Producer producer = new KafkaProducer(properties);
    //发送消息,调用Future.get()将无限等待结果返回,get方法要么返回发送结果要么抛出异常。没有错误get将返回
    //RecordMetadata实例,包括消息发送的topic、分区及消息对应分区的位移信息
    try {
        producer.send(new ProducerRecord("my-topic",
                Integer.toBinaryString(1), Integer.toString(1))).get();
    } catch (Exception e) {
        e.printStackTrace();
    }

Kafka发送消息错误类型有两种

  • 可重试错误
  • 不可重试错误
可重试错误

可重试错误都继承自org.apache.kafka.common.errors.RetriableException,当设置重试次数后,在重试次数内自行恢复并不会抛出异常,但超过重试次数则会抛出异常如下

  • LeaderNotAvailableException:分区leader副本不可用,这种现象通常是leader换届选举期间,因此通常是瞬时异常,重试可自行恢复
  • NotControllerException: controller当前不可用,通常表明controller在经历新一轮的选举,可重试自动恢复
  • NetworkException: 网络瞬时故障导致的异常
不可重试错误

不可重试错误,Kafka无法处理的问题,如下

  • RecordToolLargeException:发送消息尺寸过大,超过规定大小上线
  • SerializationException:序列化失败
  • KafkaException:其他类型异常

关闭producer

  • 完成发送之后需要关闭producer,调用无参数close方法,producer会等待处理完之前的发送请求后再关闭;
    带超时参数close方法,会等待producer timeout超时然后强制退出

producer主要参数

producer参数详情查看

acks:指定producer发送响应前,leader broker必须确保已成功写入该消息的副本数。producer发送消息到kafka集群时,这条消息会被发送到指定topic分区leader所在的broker,producer等待从该leader broker返回消息的写入结果(具有超时时间的等待)以确定消息是否被成功提交,这一切完成后producer就可以继续发送新消息。leader broker何时发送写入结果返还给producer直接影响到消息的持久性及producer端的吞吐量

  • acks = 0:producer将不会等待leader broker返回处理结果,此时producer发送完消息后立即发送下一条消息。由于不接收发送结果,因此producer.send的回调也完全失去了作用,用户无法通过回调机制感知任何发送过程中的失败,所以这种设置并不能保证消息一定被成功发送,但吞吐量最高
  • acks = all 或者 -1:表示发送消息时,leader broker不仅将消息写入本地日志,同时还会等待ISR中其他副本成功写入它们各自的本地日志后,才发送响应结果给producer,这样可以达到最高消息持久性,但producer的吞吐量也是最低
  • acks = 1:producer发送消息后leader broker仅将消息写入本地日志,便发送响应结果给producer,不等待副本写入该消息。这是一种折中方案

buffer.memory:指定producer用于缓存消息的缓冲区大小,默认是33554432字节即32M,由于采用异步消息发送设计架构,producer启动时会首先创建一块内存缓冲区用于保存待发消息,然后由一个专属线程负责从缓冲区读取消息执行发送,这部分空间大小由buffer.memory指定,若producer向缓冲区写消息速度大于专属I/O线程发送消息速度,将造成缓冲区空间不断增大,此时producer会停止手头工作等待I/O线程追上来,若一段时间过后还是无法追上,producer将抛出异常

compression.type:设置producer端是否压缩消息,默认none,即不压缩消息。压缩消息可以降低网络I/O传输开销,提升整体吞吐量,但也会增加producer端机器但CPU开销。如果broker端的压缩参数设置的与producer不同,broker端写入消息时也会额外使用CPU资源进行解压缩-重压缩操作

retries:设置消息发送重试次数,排除瞬时故障导致的消息发送失败。但重试可能导致消息重复发送;可能导致消息乱序,producer会将多个消息发送请求缓存内存中,由于某种原因导致重试,可能造成消息流乱序,可以通过max.in.flight.request.per.connection
参数设置为1,确保producer某一时刻只能发送一个请求。retry.backoff.ms可以指定两次重试消息发送之间的间隔

batch.size:producer会将发往同一分区的多条消息封装进一个batch,当batch满时,producer将发送batch中所有消息,但并不总是等待batch满了才发送。batch.size设置的过小将导致吞吐量过低,设置的过大将给内存带来压力,无论能否填满,producer都会为batch分配固定大小的内存,同时消息延迟也会更大些。默认大小为16384,即16KB,这是比较保守的值可以适当增大该值

linger.ms:延迟发送时间,默认为0,即不关心batch是否填满,立即发送。设置该参数大于0,将等待设置时间,在该段时间发送的消息将放到一个批次发送,当在该段时间内,batch满了将立即发送,当在该时间段batch还没满也将立即发送

max.request.size:控制producer端能发送的最大消息大小,默认的1048576字节太小,通常无法满足企业级消息的大小要求

request.timeout.ms:producer发送请求给broker后,broker需要在规定时间内将处理结果返回给producer,默认30秒,如果在规定时间内仍然没有返回处理结果,producer就认为请求超时,并在回调函数中显示抛出TimeoutException异常,

消息分区机制

producer程序实例

Kafka producer发送消息很重要的一步就是要确定将消息发送到指定的topic的哪个分区中。producer提供了分区策略及对应分区器(partitioner),默认partitioner会尽力确保具有相同key的消息发送到相同分区上。若没有指定key,将以轮询的方式来确保topic的所有分区上消息是均匀分布的

自定义分区机制

对于有key的消息,producer自带的partitioner会根据murmur2算法计算消息key的哈希值,然后对总分区数求模得到消息要发送的目标分区号。用户也可以自定义分区策略

自定义分区策略:

  • 在producer中创建一个实现org.apache.kafka.clients.producer.Partitioner接口的类,主要分区逻辑在Partitioner.partition中实现
  • 用于构造KafkaProducer的Properties对象中设置partitioner.class参数

实现步骤:

1.实现partitioner接口。假设我们的消息中有一些消息使用于审计功能,这类消息的key会被固定地分配一个字符串"audit",我们让这类消息发送到topic的最后一个分区上,便于后续统一处理,对于相同topic下的其他消息则采用随机发送的策略发送到
其他分区上,此自定义分区策略的实现如下

    package com.aim.kafka.client.partitioner;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.kafka.clients.producer.Partitioner;
    import org.apache.kafka.common.Cluster;
    import org.apache.kafka.common.PartitionInfo;

    import java.util.List;
    import java.util.Map;
    import java.util.Random;

    public class AuditPartitioner implements Partitioner {

        private Random random;

        /**
         * 实现必要资源的初始化工作
         *
         * @param configs
         */
        @Override
        public void configure(Map<String, ?> configs) {
            random = new Random();
        }

        /**
         * 实现自定义分区策略
         * @param topic
         * @param keyObj
         * @param keyBytes
         * @param value
         * @param valueBytes
         * @param cluster
         * @return
         */
        @Override
        public int partition(String topic, Object keyObj, byte[] keyBytes, Object value, byte[] valueBytes,
                             Cluster cluster) {
            String key = (String) keyObj;
            //获取topic对应可用分区
            List<PartitionInfo> partitionInfoList = cluster.availablePartitionsForTopic(topic);
            int partitionCount = partitionInfoList.size();
            int auditPartitionCount = partitionCount - 1;
            return StringUtils.isEmpty(key) || StringUtils.contains(key, "audit") ? auditPartitionCount
                    : random.nextInt(partitionCount - 1);
        }

        /**
         * 必要资源的清理工作
         */
        @Override
        public void close() {
        }
    }

2.指定partitioner.class

    properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.aim.kafka.client.partitioner.AuditPartitioner");
    Producer producer3 = new KafkaProducer(properties);
    producer3.send(new ProducerRecord<>("my-topic", "audit", "audit record"), (metadata, exception) -> {
        if (exception == null) {
            //消息发送成功
        } else {
            //执行错误处理逻辑
            if(exception instanceof RetriableException){
                //处理可重试瞬时异常
            } else {
                //处理不可重试异常
            }
        }
    });
    producer3.close();

消息序列化

默认序列化

kafka在网络中以字节的方式发送,支持用户给broker发送各种类型的消息,序列化器负责在producer发送消息前将消息转换成字节数组;解序列化器则将consumer接收到的字节数组转换成相应的对象,默认提供的序列化器如下

  • ByteArraySerializer: 什么都不做
  • ByteBufferSerializer: 序列化ByteBuffer
  • BytesSerializer: 序列化Kafka自定义Bytes类
  • DoubleSerializer: 序列化Double类型
  • IntegerSerializer: 序列化Integer类型
  • LongSerializer: 序列化Long类型
  • StringSerializer: 序列化String类型

producer已经初步建立序列化机制来序列化简单的消息类型,对于复杂的类型则需要自定义serializer。producer的序列化机制使用只需构造producer时指定key.serializer和value.serializer即可

    //发送到broker端的任何消息格式都必须是字节数组,因此需要进行序列化,指定key的序列化器,除使用默认提供的
    //序列化器,也可以通过实现Serializer接口,创建自定义序列化器
    properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    //指定value部分的序列化器
    properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

自定义序列化

Kafka支持用户自定义消息序列化,需要完成3件事情:

  • 定义数据对象格式
  • 创建自定义序列化类,实现org.apache.kafka.common.serialization.Serialize,在serialize方法中实现序列化逻辑
  • 在用于构造KafkaProducer的Properties对象中设置key.serializer或value.serializer

实例:

1.创建Java POJO对象

    package com.aim.kafka.client.serializer;

    public class User {
    private String firstName;

    private String lastName;

    private int age;

    private String address;

    public User(){
    }

    public User(String firstName, String lastName, int age, String address){
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
    }

2.实现serializer接口

    package com.aim.kafka.client.serializer;

    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.apache.kafka.common.serialization.Serializer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;

    import java.util.Map;

    public class UserSerializer implements Serializer {

        private static final Logger logger = LoggerFactory.getLogger(UserSerializer.class);

        @Autowired
        private ObjectMapper objectMapper;

        /**
         * 初始化
         * @param configs
         * @param isKey
         */
        @Override
        public void configure(Map configs, boolean isKey) {
            objectMapper = new ObjectMapper();
        }

        /**
         * 序列化
         * @param topic
         * @param data
         * @return
         */
        @Override
        public byte[] serialize(String topic, Object data) {
            byte[] ret = null;
            try {
                ret = objectMapper.writeValueAsString(data).getBytes("utf-8");
            } catch (Exception e) {
                logger.info("failed to serialize the object:{}", data, e);
            }
            return ret;
        }

        /**
         * 释放资源
         */
        @Override
        public void close() {

        }
    }

3.指定使用序列化器

    properties.put("value.serializer", "com.aim.kafka.client.serializer.UserSerializer");
        Producer producer4 = new KafkaProducer(properties);
        producer4.send(new ProducerRecord<>("my-topic", "audit", new User()), (metadata, exception) -> {
            if (exception == null) {
                //消息发送成功
            } else {
                //执行错误处理逻辑
                if(exception instanceof RetriableException){
                    //处理可重试瞬时异常
                } else {
                    //处理不可重试异常
                }
            }
        });
        producer4.close();

producer拦截器

producer端interceptor拦截器使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,并且支持指定多个interceptor按顺序作用于同一条消息形成拦截链,interceptor的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor,其方法如下:

  • onSend(ProducerRecord): 该方法封装进KafkaProducer.send方法中,运行在用户主线程中,producer确保消息被序列化以计算分区前调用该方法,用户可以在该方法中对消息做任何操作,但最好不要修改消息所属topic和分区,否则会影响目标分区计算
  • onAcknowledgement(RecordMetadata, Exception): 该方法会在消息被应答之前或消息发送失败时调用,并且通常都是在producer回调逻辑触发之前,onAcknowledgement运行在producer的I/O线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率
  • close: 关闭interceptor,主要执行一些资源清理工作

实例

下例是一个简单的双interceptor组成的拦截器,第一个interceptor在消息发送前将时间戳信息加到消息value的最前部;第二个interceptor在消息发送后更新成功发送消息数或失败发送消息数

1.第一个interceptor TimeStampPrependerInterceptor

    package com.aim.kafka.client.interceptor;

    import org.apache.kafka.clients.producer.ProducerInterceptor;
    import org.apache.kafka.clients.producer.ProducerRecord;
    import org.apache.kafka.clients.producer.RecordMetadata;

    import java.util.Map;

    public class TimeStampPrependerInterceptor implements ProducerInterceptor<String, String> {
        @Override
        public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
            return new ProducerRecord<>(record.topic(), record.partition(), record.timestamp(),
                    record.key(), System.currentTimeMillis() + "," + record.value().toString());
        }

        @Override
        public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

        }

        @Override
        public void close() {

        }

        @Override
        public void configure(Map<String, ?> configs) {

        }
    }

2.第二个interceptor CounterInterceptor

    package com.aim.kafka.client.interceptor;

    import org.apache.kafka.clients.producer.ProducerInterceptor;
    import org.apache.kafka.clients.producer.ProducerRecord;
    import org.apache.kafka.clients.producer.RecordMetadata;

    import java.util.Map;
    import java.util.concurrent.atomic.AtomicInteger;

    public class CounterInterceptor implements ProducerInterceptor<String, String> {

        private AtomicInteger errorCounter = new AtomicInteger(0);
        private AtomicInteger successCounter = new AtomicInteger(0);

        @Override
        public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
            return record;
        }

        @Override
        public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
            if(exception == null){
                successCounter.getAndIncrement();
            } else {
                errorCounter.getAndIncrement();
            }
        }

        @Override
        public void close() {
            //保存结果
            System.out.println("Successful sent:" + successCounter);
            System.out.println("Failed sent:" + errorCounter);
        }

        @Override
        public void configure(Map<String, ?> configs) {

        }
    }

3.在producer中指定interceptor::

    List<String> interceptors = new ArrayList<>();
        properties.put("value.serializer", "com.aim.kafka.client.serializer.UserSerializer");
        interceptors.add("com.aim.kafka.client.interceptor.TimeStampPrependerInterceptor");
        interceptors.add("com.aim.kafka.client.interceptor.CounterInterceptor");
        properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
        Producer producer5 = new KafkaProducer(properties);
        producer5.send(new ProducerRecord<>("my-topic", "audit", "bbbb"), (metadata,
                                                                           exception) -> {
            if (exception == null) {
                //消息发送成功
            } else {
                //执行错误处理逻辑
                if (exception instanceof RetriableException) {
                    //处理可重试瞬时异常
                } else {
                    //处理不可重试异常
                }
            }
        });
        producer5.close();

无消息丢失配置

Producer发送消息采用异步方式,仅仅把消息放入缓冲区,由一个专属I/O线程负责从缓冲区提取消息并封装进消息batch中,然后发送出去,如果I/O线程发送之前producer崩溃则缓冲区中数据全部丢失。

producer消息乱序,假设producer端发送两条消息,第一条消息因为网络抖动发送失败进入重试机制,第二条消息发送成功,当第一条消息重试发送成功后,可能第一条消息反而位于第二条消息之后

异步发送无消息丢失配置

    block.on.buffer.full = true
    acks = all or -1
    retries = Integer.MAX_VALUE
    max.in.flight.request.per.connection = 1
    使用带回调机制的send发送消息,KafkaProducer.send(record, callback)
    Callback逻辑中显式立即关闭producer,使用close(0)
    unclean.leader.election.enable = false
    replication.factor = 3
    min.insync.replicas = 2
    replication.factor > min.insync.replicas
    enable.auto.commit = false

producer端配置

block.on.buffer.full = true

  • Kafka 0.9.0.0标记为"deprecated",并使用max.block.ms参数替换,该参数设置当producer内存缓冲区被填满时producer处于阻塞状态并停止接收新消息而不是抛出异常。0.10.0.0之后版本可以直接设置max.block.ms

acks = all:

  • 必须所有follower都响应了发送消息才认为消息发送成功

retries = Integer.MAX_VALUE:

  • producer需要开启无限重试,producer只会重试可恢复异常,对于不可恢复异常并不会重试

max.in.flight.requests.per.connection = 1

  • 该参数设置为1主要防止topic同分区下的消息乱序问题,此消息限制producer在单个broker连接上能够发送的未响应请求的数量,因此设置为1,producer在某个broker发送响应之前无法在往该broker发送PRODUCER请求

使用带回调机制的send

  • 不要使用KafkaProducer单参数的send方法,使用该方法方法,kafka仅仅将消费发送出去,但并不关心消息是否发送成功,肯能导致消息丢失,需要调用带回调的方法发送消息

Callback逻辑中显式立即关闭producer

  • 在Callback失败处理逻辑中显示调用KafkaProducer.close(0),防止消息乱序,不使用close(0)默认情况下producer允许将未完成的消息发送出去,可能造成消息乱序

broker端配置

unclean.leader.election.enable = false:

  • 关闭unclean leader选举,不允许非ISR中的副本被选举成leader,从而避免broker端因日志水位阶段而造成消息丢失

replication.factor >= 3

  • 需要多个副本保存分区消息,设置3主要是参考hadoop及业界通用的三备份原则

min.insync.replicas > 1

  • 控制某条消息至少被写入到ISR多少个副本才算成功,设置大于1是为了让producer端发送语义的持久性。只有当producer端acks被设置成all或-1,该参数才有意义,不要使用默认值

确保replication.factor > min.insync.replicas

  • 两者若相等则只要有一个副本挂掉,分区就无法正常工作,虽然有很高的持久性,但可用性被极大降低, 推荐设置成replication.factor = min.insync.replicas + 1

消息压缩

数据压缩显著降低了磁盘占用、带宽占用,有效提升I/O密集型应用的性能,但压缩会引入CPU资源的占用。Producer端能将一批消息压缩成一条消息发送,broker端将这条压缩消息写入本地日志,当consumer端获取这条压缩消息时,会自定解压。但遇到需要进行消息格式转换等情况时broker端需要对消息进行解压缩然后重新压缩

支持的压缩算法

当前Kafka支持3中压缩算法:GZIP、Snappy、LZ4、zstd,通过compression.type开启消息压缩

    //设置压缩算法,默认不压缩
	properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "zstd");

算法性能比较与调优

KafkaProducer.send方法逻辑主要耗时在消息压缩,因此调优压缩算法至关重要。Kafka中压缩算法的性能,zstd>>LZ4>>Snappy>>GZIP,是否启用压缩的依据是I/O资源消耗与CPU资源消耗的对比。如果生产环境I/O资源非常紧张,producer程序消耗了大量网络带宽或broker端的磁盘占用率非常高,而producer端CPU资源非常富裕,可以考虑为producer端开启消息压缩

多线程处理

实际环境中使用一个用户主线程通常无法满足所需吞吐量目标,因此需要构建多个线程或多个进程来同时给Kafka集群发送消息

  • 多线程单KafkaProducer实例:全局构建一个KafkaProducer实例,多线程共享使用,由于KafkaProducer是线程安全,所以这种使用方式是线程安全,
  • 多线程多KafkaProducer实例:在每个producer主线程中都构建一个KafkaProducer实例,并且保证此实例在该线程中封闭

单KafkaProducer实例,所有线程共享一个KafkaProducer实例,实现简单,性能好,但所有线程共享一个内存缓冲区,可能需要较多内存,一旦producer某个线程崩溃导致KafkaProducer实例被破坏,则所有用户线程都无法工作

多KafkaProducer实例,每个线程维护专属的KafkaProducer实例,每个用户线程专属KafkaProducer实例,缓冲区空间及一组对应的配置参数,可以进行细粒度的调优,单个KafkaProducer崩溃不会影响其他producer线程工作。需要较大的内存分配开销

对于分区不多的Kafka集群而言,推荐第一种方法。对于拥有超多分区的集群而言,采用第二中方法具有较高的可控性

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值