目录
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);