Kafka 的 Producer发送消息采用的是异步发送的方式。
在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程,以及一个共享变量——RecordAccumulator。
main 线程将消息发送给 RecordAccumulator, Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka broker。
/**
* 注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。
*/
public static void send() throws ExecutionException, InterruptedException {
Properties props = new Properties();
//kafka 集群,broker-list
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
props.put(ProducerConfig.ACKS_CONFIG, "all");
//重试次数;消息发送失败会自动重试,不需要我们在回调函数中手动重试
props.put(ProducerConfig.RETRIES_CONFIG, 1);
//批次大小.只有数据积累到 batch.size 之后,sender 才会发送数据。
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
//等待时间.如果数据迟迟未达到 batch.size,sender 等待 linger.time 之后就会发送数据。
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
//RecordAccumulator 缓冲区大小:32M
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
//构建拦截链
List<String> interceptors = new ArrayList<String>();
interceptors.add(TimeInterceptor.class.getName());
//添加拦截器
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
//key、value的fan序列化
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
//自定义分区器
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,
MyPartition.class.getName());
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);
for (int i = 0; i < 100; i++) {
//发送到的主题
String topic = "firsTopic";
//key可做分区使用
String key = Integer.toString(i);
//value为消息数据
String value = Integer.toString(i);
// int partition=1;
// ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic, partition,key, value);
ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic, key, value);
//(1)只管发,没有回调函数
//Future<RecordMetadata> future = producer.send(record);
//(2)Producer 收到 ack 时,异步回调Callback函数
//如果 Exception 为 null,说明消息发送成功,反之为失败
Future<RecordMetadata> future = producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
System.err.println("Producer收到消息发送成功的ack:" + metadata.offset());
} else {
//消息发送失败会自动重试,不需要我们在回调函数中手动重试
System.err.println("Producer收到消息发送失败的ack");
exception.printStackTrace();
}
}
});
//返回future,可以根据需要调用get阻塞线程
//future.get();
}
producer.close();
}
/**
* 自定义分区器
*/
public class MyPartition implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
//根据不同业务,可以根据key、value等 返回分区id
return key.toString().hashCode() % 3;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
实现一个简单的双 interceptor 组成的拦截链。第一个 interceptor 会在消息发送前将时间 戳信息加到消息 value 的最前部;第二个 interceptor 会在消息发送后更新成功发送消息数或 失败发送消息数。
public class TimeInterceptor implements ProducerInterceptor<String, String> {
//获取配置信息和初始化数据时调用。
@Override
public void configure(Map<String, ?> configs) {
}
//该方法封装进 KafkaProducer.send 方法中,即它运行在用户主线程中。Producer 确保在
//消息被序列化以及计算分区前调用该方法。
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
// 创建一个新的 record,把时间戳写入消息体的最前部
String topic = record.topic();
int partition = record.partition();
String key = record.key();
String value = record.value();
String newValue=System.currentTimeMillis() + "," + value;
Long timestamp = record.timestamp();
ProducerRecord recordResult= new ProducerRecord(topic, partition, timestamp, key, newValue);
return recordResult;
}
//该方法会在消息从 RecordAccumulator 成功发送到 Kafka Broker 之后,或者在发送过程
//中失败时调用。
@Override
public void onAcknowledgement(RecordMetadata metadata,
Exception exception) {
}
//关闭 interceptor,主要用于执行一些资源清理工作
@Override
public void close() {
}
}