目录

1 发送消息流程

2 发送消息API

2.1 设置properties

2.2 生产者发送消息

2.3 自定义分区策略

2.4 自定义拦截器

2.4.1 核心方法介绍

2.4.2 代码示例


1 发送消息流程

kafka Procedure发送消息采用的是异步发送方式,消息发送过程中涉及到两个线程,Main线程和Sender线程,以及一个共享变量RecordAccumulator。main线程把消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到broker
相关参数
batch.size:发送消息时候的数据累积量
linger.ms:如果数据迟迟未达 batch.size,则sender等待linger.ms后发送

2 发送消息API

2.1 设置properties

public static Map<String, Object> getProcedureProperties() {
    Map<String, Object> properties = new HashMap<>();
    properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.249.201:9092");
    // 生产者ACK级别
    properties.put(ProducerConfig.ACKS_CONFIG, "all");
    // 重试次数
    properties.put(ProducerConfig.RETRIES_CONFIG, 3);
    // 批次发送数据大小
    properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
    // 等待时间,1ms,数据达到16k或等待1ms会发送数据
    properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
    // RecordAccumulator缓冲区大小
    properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
    // 配置k-v序列化
    properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    // 自定义分区策略
    //properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.jms.kafka.demo.ValueHashPartitioner");
    return properties;
}

2.2 生产者发送消息

private KafkaProducer<String, String> producer = KafkaUtil.getProcedure();

public void sendMsg(String topic, String key, String value) {
    ProducerRecord<String, String> record = new ProducerRecord<>(topic, key, value);
    producer.send(record, (recordMetadata, e) -> {
        if (Objects.isNull(e)) {
            log.info("sendMsg msg success, topic:{}, key:{}, value:{}, partition:{}, offset:{}",
                    topic, key, value, recordMetadata.partition(), recordMetadata.offset());
        }else {
            log.error("sendMsg msg error, topic:{}, key:{}, value:{}, e:", topic, key, value, e);
        }
    });
}

2.3 自定义分区策略

/**
 * @author huwenlong
 * @date 2020/8/23 11:57
 * 自定义分区策略,按照value哈希值进行分区
 */
public class ValueHashPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int partitionCount = partitions.size();
        return value.hashCode() % partitionCount;
    }

    @Override
    public void close() {

    }

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

    }
}

如果需要,可以通过如下方式设置分区

properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.jms.kafka.demo.ValueHashPartitioner");

2.4 自定义拦截器

2.4.1 核心方法介绍

对于producer而言,interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链。interceptor的实现接口是ProducerInterceptor,其定义的方法主要包括

  • 1)void configure(Map<String, ?> configs);

获取配置信息和初始化时调用

  • 2)ProducerRecord<K, V> onSend(ProducerRecord<K, V> record)

该方法封装进KafkaProcedure.send方法中,运行在用户主线程。Procedure确保消息被序列化以及计算分区前调用该方法。我们可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响目标分区的计算

  • 3)onAcknowledgement(RecordMetadata metadata, Exception exception)

该方法会从RecordAccumulator成功发送到kafka broker之后,或者在发送过程中失败时调用。并且通常是在producer回调逻辑触发之前。该方法运行在producer的IO线程中,因此不要在该方法中放入重要的逻辑,否则会拖慢producer的消息发送效率。

  • 4)void close()

关闭interceptor,清理一些资源。

2.4.2 代码示例

这里定义两个拦截器,一个拦截器用于给每个消息加时间戳,一个拦截器用于统计发送成功、失败消息个数
1)时间戳拦截器

public class TimeInterceptor implements ProducerInterceptor{

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

    }
    /**
      * 消息发送到kafka之前
      * @param record
      * @return
     */
    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        // 创建一个新的ProcedureRecord对象并返回
        ProducerRecord  producerRecord = new ProducerRecord(record.topic(),record.partition(),record.key(),
                new Date().getTime()+","+record.value());
        return producerRecord;
    }

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

    }

    @Override
    public void close() {

    }
}

2)发送统计拦截器

public class CounterIntercePtor implements ProducerInterceptor {
    private static AtomicInteger SUCCESS = new AtomicInteger(0);

    private static AtomicInteger ERROR = new AtomicInteger(0);
    @Override
    public void configure(Map<String, ?> configs) {

    }

    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        return record;
    }

    /**
     * 统计成功和失败的条数
     * @param metadata
     * @param exception
     */
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        if (null == metadata){
            ERROR.incrementAndGet();
        }else {
            SUCCESS.incrementAndGet();
        }
    }

    @Override
    public void close() {
        System.out.println("成功:"+SUCCESS);
        System.out.println("失败:"+ERROR);
    }
}

3)配置拦截器

//添加拦截器
List<String> interceptors = new ArrayList<>();
interceptors.add("com.jms.kafka.demo.TimeInterceptor");
interceptors.add("com.jms.kafka.demo.CounterIntercePtor");
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);