第二章 kafka的producer开发

一 producer概念

producer设计上比consumer简单,不涉及复杂组管理操作,即每个producer都是独立进行工作的,与其他producer实例之间没有关联。

producer的功能就是向topic发送消息,在发送之前需要确认消息要发送的分区。

确认分区:

producer提供了默认的分区策略和分区器。若待发送消息指定了key,那partition会根据key哈希值选目标分区,若未指定key,partitioner会使用轮讯的方式确认目标分区,这样最大限度地确保消息在所有分区上的均匀性。

producer也赋予了用户指定分区的权力。另外,partitioner会认为具有相同key的所有消息都会被路由到相同分区中。

确认分区后:

producer要寻找分区对应leader,就是该分区leader副本所在kafka broker上(topic有多个副本,但是只有一个为leader的副本会响应client请求)。

二 构造Producer步骤

  • 创建Properties对象;
  • 创建KafkaProducer对象;
  • 构造发送的消息对象ProducerRecord;
  • KafkaProducer.send()发送消息;
  • 关闭kafkaProducer。

Producer实例:

首先先引入kafka的maven依赖:

 <dependency>
     <groupId>org.apache.kafka</groupId>
     <artifactId>kafka_2.11</artifactId>
     <version>2.3.0</version>
 </dependency>

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

import java.util.Properties;
import java.util.concurrent.Future;

public class ProducerTest {
    public static void main(String[] args){
        Properties props = new Properties();
        props.put("bootstrap.servers","192.168.25.70:9092,192.168.25.70:9093,192.168.25.70:9094");//必须指定
        props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");//必须指定
        props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");//必须指定
        props.put("acks","-1");
        props.put("retries",3);
        props.put("batch.ms",323840);
        props.put("linger.ms",10);
        props.put("buffer.memory",33554432);
        props.put("max.block.ms",3000);
        Producer<String,String> producer = new KafkaProducer<String, String>(props);
        for (int i = 0; i < 100; i++){
            producer.send(new ProducerRecord<>("topic12", Integer.toString(i), Integer.toString(i)));
        }
        producer.close();


    }
}
bootstrap.servers、key.serializer、value.serializer这三个参数是必须指定的。

其次在Producer发送消息分为同步发送和异步发送。KafkaProducer.send()方法是通过Future实现了同步发送和异步发送。

发送方式方法名返回值
异步发送send(ProducerRecord record, Callback callback)Future<ProducerRecord>
同步发送send(ProducerRecord record)Future<ProducerRecord>

 

Producer发送消息失败时,会返回异常错误。

返回的异常分为可重试异常和不可重试异常。

可重试异常(RetriableException):

  • LeaderNotAvailableException:分区leader不可用,一般出现的leader选举期间;
  • NotControllerException:controller不可用;
  • NetWorkException:网络不可用或网络故障。

不可重试异常:这种异常非常严重,kafka无法自行处理。

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

 

开发Producer时可以配置的参数:

acks:控制producer生产消息的持久性;

  • acks=0:producer不管leader broker处理结果,直接返回响应结果;
  • acks=all或-1:表示当发送消息时,leader broker不仅会将消息写本地日志,同时会等ISR其他副本写本地日志成功,才返回响应结果;
  • acks=1:写入leader broker成功后即返回响应结果。

buffer.memory:指定缓存消息的缓冲区大小,单位:字节。

compression.type:是否压缩消息,默认none,压缩方式有GZIP、Snappy、Lz4等,压缩消息会降低网络I/O开销,提升整体吞吐量,但会增加producer端的cpu开销,若broker端压缩参数与producer不同,也会增加broker端cpu开销。

retries:重试次数,默认0。重试会造成消息重复发送,还可能造成消息乱序,所以需要consumer进行去重。处理乱序,producer提供了max.in.flight.requests.per.connection参数,该参数设为1后,producer确保某一时刻只能发送一个请求。

producer两次重试之间会停顿一段时间,防止频繁重试对系统带来冲击,可配置两次重试的时间,参数为retry.backoff.ms,默认100ms。

batch.size:它对于调优producer吞吐量和延时有重要作用。producer会将同一分区的多条消息封装进一个batch中,当batch满后,producer会发送batch中的所有消息。producer可以不用等batch满才发消息。

linger.ms:控制消息发送时延行为,默认为0,表示消息需要被立即发送。无需关心batch是否被填满。

max.request.size:用于控制producer发送请求的大小,实际是能够发送的最大消息的大小。

request.timeout.ms:请求超时时间,默认30秒。

 

三 消息分区机制

1 分区策略

producer提供了分区策略和分区器,默认partitioner会尽力将相同key的消息发送相同分区,没有指定key,partitioner采用轮训方式确保消息在topic的所有分区上均匀分布。

2 自定义分区器

自定义分区器需要实现org.apache.kafka.clients.producer.Partitioner接口。

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 MyPartitioner implements Partitioner {

    private Random random = new Random();

    @Override
    public int partition(String topic, Object keyObj, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        String key = (String) keyObj;

        List<PartitionInfo> partitionInfosList = cluster.availablePartitionsForTopic(topic);
        int partitionCount = partitionInfosList.size();
        int auditPartition = partitionCount - 1;
        return key == null || key.isEmpty() || !key.contains("audit")?random.nextInt(partitionCount-1):auditPartition;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {
        random = new Random();
    }
}

3 消息序列化

默认的序列化方式有:

  • ByteArraySerializer:本质上什么也不做,因为已经是字节数组了;
  • ByteBufferSerializer:序列化ByteBuffer;
  • ByteSerializer:序列化kafka的自定义的Byte类;
  • DoubleSerializer:序列化double类型;
  • IntegerSerializer:序列化Integer类型;
  • LongSerializer:序列化Long类型;
  • StringSerializer:序列化String类型。

自定义序列化器:

需要实现org.apache.kafka.common.serialization.Serializer。


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Serializer;

import java.util.Map;

public class MySerializer implements Serializer {

    private ObjectMapper objectMapper;

    @Override
    public void configure(Map configs, boolean isKey) {
        objectMapper = new ObjectMapper();
    }

    @Override
    public byte[] serialize(String s, Object o) {
        byte[] ret = null;
        try {
            ret = objectMapper.writeValueAsString(o).getBytes();
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return ret;
    }

    @Override
    public byte[] serialize(String topic, Headers headers, Object data) {
        byte[] ret = null;
        try {
            ret = objectMapper.writeValueAsString(data).getBytes();
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return ret;
    }

    @Override
    public void close() {

    }
}

四 Producer拦截器(0.10.0.0版本引入)

producer拦截器实现了client端定制化控制逻辑。

interceptor使用在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求。producer允许用户指定多个interceptor作用于同一条消息形成链。

实现接口:ProducerInterceptor。

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> producerRecord) {
        return new ProducerRecord<String,String>(producerRecord.topic(),producerRecord.partition(),producerRecord.timestamp(),
                producerRecord.key(),System.currentTimeMillis()+"," +producerRecord.value().toString());
    }

    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {

    }

    @Override
    public void close() {

    }

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

    }
}

五 无消息丢失设置

如何规避网络消息丢失以及消息乱序?

在producer端可以通过以下参数配置实现:

(1)block.on.buffer.full=true

0.9.0.0版本中标记为deprecate,用max.block.ms替代。作用是当内存缓冲区填满时,producer进入阻塞状态,停止接收新消息,而不是抛出异常。

(2)acks=all

等所有follower都响应了才能认为提交成功。

(3)retries=Integer.MAX_VALUE

重试次数

(4)max.in.flight.requests.per.connection=1

等1时为防止topic下同分区下消息乱序。

然而实际效果限制了producer在单个broker连接上能够发送的来响应请求的数量,设为1时,producer在broker响应前无法再给broker发新请求。

(5)使用带回调机制的send方法,这样能收到消息发送出去的返回结果

(6)callback逻辑中显示立即关闭producer,能够解决乱序,若不关闭,producer会被允许将来完成的消息发送出去。

broker端配置:

(1) unclean.leader.election.enable=false

关闭unclean leader选举。即不允许非ISR中副本被选为leader,从而避免broker端因日志水位截断而造成消息丢失

(2)replication.factor>=3

多副本保存。

(3)min.insync.replicas>1

控制某条消息至少被写入到ISR中多个副本才算成功(前提是producer端acks=-1或all)。

(4)确保replication.factor>min.insync.replicas

相等时,只要有一副本挂掉,分区就无法工作,可用性降低。

六 消息压缩(0。7版本开始支持)

降低磁盘占用或带宽占用,提升I/O密集型应用的性能。

Kafka目前支持Gzip、Snappy、Lz4算法。目前压缩算法性能Lz4>>Snappy>Gzip。

七 多线程处理

实际环境中需要构造多个线程或多个进程来同时给kafka集群发消息。

目前有两种方法:

  • 多线程但KafkaProducer实例;
  • 多线程多KafkaProducer实例。

KafkaProducer是线程安全的。

 说明优势劣势
单KafkaProducer实例所有线程共享一个KafkaProducer实例实现简单,性能好所有线程共享一个内存缓冲区,可能需要较多内存;一旦producer某个线程崩溃导致KafkaProducer实例被破坏,则所有用户线程都无法工作
多KafkaProducer实例每个线程维护自己专属的KafkaProducer实例每个用户线程拥有专属的KafkaProducer实例、缓冲区空间及一组对应的配置参数,可以进行细粒度的调优;单个KafkaProducer崩溃不会影响其他Producer线程工作需要较大的内存分配开销

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值