kafka——producer

一、工作流程

Java版本的工作流程,ProducerRecord是消息对象。

 简单描述,

1、主线程建立ProducerRecord对象,包含topic、partition、key、value、timestamp等信息

2、将ProducerRecord中的消息体(key-value),序列化后结合kafkaProducer缓存的元数据(topic分区信息数等),共同提交给partitioner

3、partitione计算出目标分区并放入缓存中对应目标分区的batch(批量思想,一批一批的发送,而不是一条一条的发送)中,这里主线程任务已经完成

4、sender线程异步发送batch信息,并接受响应,然后回调给主线程。

二、序列化

kafka默认提供多种序列化器。

ByteArraySerializer:本质什么也没做,已经是字节数组了。

ByteBufferSerializer:序列化ByteBuffer

BytesSerializer:序列化kafka自定义的Bytes类

DoubleSerializer:序列化Double类型

IntegerSerializer:序列化Integer类型

LongSerializer:序列化Long类型

StringSerializer:序列化String类型

三、分区策略

partitioner职责是确认到底要向topic的那个分区发送消息。

kafak提供默认的分区器partitioner,分区策略:

① 根据消息key的hash值确定目标分区。大多数情况下消息是没有key的,我们可以控制多条消息相同key,达到这些消息被分到同一个分区,完成一些业务需求。

② 若无key,则采用轮询的方式确定目标分区

③ 用户指定,producer的API提供了用户自定指定目标分区的功能

四、Java代码实现与参数说明

Java代码实现:配置参数key在ProducerConfig.Java中可以找到。

 1 public class ProducerTest {
 2 
 3     private static final String HOST = "192.168.1.4";
 4 
 5     public static void main(String[] args) {
 6         Properties props = new Properties();
 7         props.put("bootstrap.servers",HOST + ":9091"+","+HOST + ":9092"+","+HOST + ":9093");//指定broker,配置部分就可以了,producer会替换成实际brokers列表
 8         props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");//key序列化类,无默认值,必须配置
 9         props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");//value序列化类,无默认值,必须配置,可与key不同
10         props.put("acks","-1");//控制参数的持久性策略,须传入字符串参数
11         props.put("retries","3");//重试次数,默认为0
12         props.put("batch.size","16384");//一个批次大小,默认16KB,producer调优重要参数
13         props.put("linger.ms","10");//发送消息延时,默认为0,消息立即发送,不需要batch满,producer调优重要参数
14         props.put("buffer.memory","33554432");//消息缓冲区大小默认,32MB,producer调优重要参数
15         props.put("max.block.ms","3000");//等待元数据超时时间,(例如发送topic,客户端未缓存)
16         props.put("max.request.size","10485760");//请求消息的最大值,默认10MB,由于消息头开销需要比实际最大值大
17         props.put("request.timeout.ms","3000");//请求超时时间
18         try(Producer producer = new KafkaProducer(props);) {
19             producer.send(new ProducerRecord("topic-test","hello world"));
20         }
21     }
22 }

除了三个必须的参数bootstrap.servers、key.serializer、value.serializer外另外必须清楚acks配置的作用

acks:参数用于控制producer生产消息的持久化级别。

acks = -1或all :消息发送时,producer需要等待leader broker的响应,而leader broker需要将消息写入本地日志,同时还会等ISR中副本都成功写入日志后,才会响应,吞吐量最差,不能容忍消息丢失

acks = 0 :发送消息时,producer不需要等待leader broker的响应。吞吐量最高,但完全不担心消息是否发送成功,允许消息丢失(例如服务器日志应用)

acks = 1 :发送消息时,producer需要等待leader broker的响应,而leader broker将消息写入本地日志,就会响应,无需等待ISR中副本都成功写入日志。吞吐量适中

其中acks = 0 , acks = 1可能引起消息丢失。

五、producer拦截器

拦截器interceptor是的用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,例如修改消息,添加通用字段

接口是

//org.apache.kafka.clients.producer.ProducerInterceptor
public interface ProducerInterceptor<K, V> extends Configurable {
    //封装仅send方法中,运行在用户主线程,确保消息在序列化前调用此方法
    ProducerRecord<K, V> onSend(ProducerRecord<K, V> var1);
    //回调逻辑触发前,运行在producer I/O线程(sender线程)中,不能放入过重的逻辑,会拖累消息发送效率
    void onAcknowledgement(RecordMetadata var1, Exception var2);
    //关闭拦截器,主要用于资源清理
    void close();
}

具体使用时将自定义的拦截器放入配置props的interceptor.classes.config属性中。

六、同步异步发送

新版producer的写入操作默认都是异步的。异步发送可能导致消息乱序,利用send方法返回Future对象可转化为同步发送。

           //异步发送+回调
            producer.send(new ProducerRecord("topic-test", "test1"), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e == null){
                        System.out.println("message send success");
                    }else {
                        System.out.println("message send fail");
                    }
                }
            });
            producer.send(new ProducerRecord("topic-test", "test2"), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e == null){
                        System.out.println("message send success");
                    }else {
                        System.out.println("message send fail");
                    }
                }
            });
            //利用future的get()方法实现同步发送
            producer.send(new ProducerRecord("topic-test", "test1")).get();
            producer.send(new ProducerRecord("topic-test", "test2")).get();

七、多线程处理

producer多线程中两种使用方法,社区推荐多线程单KafkaProducer实例,效率高

多线程单KafkaProducer实例:所有线程共享一个KafkaProducer实例,实现简单性能好,但①共享一个内存缓冲区,需要配置更大的缓存空间;② 一旦producer实例破坏,所有用户线程无法工作

多线程多KafkaProducer实例:每个线程维护自己的专属kafkaProducer实例,配置粒度小,单个producer奔溃不会影响其他线程;但①占用更多的内存

八、无消息丢失配置

① 新版producer默认异步发送可能导致消息乱序,可采用上面同步方式发送消息,但同步方式性能很差,实际场景不推荐使用

②producer端“无消息丢失配置”

/************  product config  *****************/
block.on.buffer.full=true //缓冲区满,停止接受新消息
acks = -1 //ISR日志全部持久化
retries = Integer.MAX_VALUE //无限重试
max.in.flight.requests.per.connection=1 //防止同partition下消息乱序。单个broker仅响应一个请求
//使用带回调机制的send方法.producer.send(record,callback)
//callback逻辑中显示地立即关闭producer,使用close()
/************  broker config  *****************/
unclean.leader.election.enable=false//不允许ISR外副本竞选leader
replication.factor=3//业界通用的三备份原则
min.insync.replicas=2//写入ISR多少个副本才算成功,acks=-1才有效,实际使用,不要使用默认值
replication.factor>min.insync.replicas//两者相等会导致,一个副本挂掉,分区将无法提供服务
enable.auto.commit=false

九、旧版本producer

新旧producer主要区别:

①旧版:kafka.producer.Producer;新版:org.apache.kafka.clients.producer.KafkaProducer

②旧版默认同步发送;新版默认异步发送

③旧版:kafka-core.jar;新版;kafka-clients.jar

④新旧参数列表几乎完全不同

⑤旧版本直接与ZooKeeper通信来发送数据,新版本彻底摆脱ZooKeeper的依赖

十、spring中的producer

public class SpringProducer {

    private static KafkaTemplate<String,String> kafkaTemplate = null;
    private static final String HOST = "mcip";

    static{
        Properties pro = new Properties();
        pro.put("bootstrap.servers",HOST + ":9091"+","+HOST + ":9092"+","+HOST + ":9093");
        pro.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
        pro.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
        pro.put("acks","-1");
        pro.put("retries","3");
        pro.put("batch.size","323840");
        pro.put("linger.ms","10");
        pro.put("buffer.memory","33554432");
        pro.put("max.block.ms","3000");
        ProducerFactory producerFactory = new DefaultKafkaProducerFactory(pro);
        kafkaTemplate = new KafkaTemplate<String,String>(producerFactory,true);
    }

    public static ListenableFuture<SendResult<String, String>> send(String topic, String message){
        return kafkaTemplate.send(topic,message);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ListenableFuture<SendResult<String, String>> future = send("tttttopic","replica test4");
        System.out.println(future.get().getProducerRecord().value());
    }

}

 

参考《kafka实战》

posted on 2020-02-20 05:36  FFStayF  阅读(299)  评论(0编辑  收藏  举报