消息由生产者发布到Kafka集群后,会被消费者消费。消息的消费模型有两种:推送模型(push)和拉取模型(pull)。
基于推送模型(push)的消息系统,由消息代理记录消费者的消费状态。消息代理在将消息推送到消费者后,标记这条消息为
已消费,但这种方式无法很好地保证消息被处理。比如,消息代理把消息发送出去后,当消费进程挂掉或者由于网络原因没有收
到这条消息时,就有可能造成消息丢失(因为消息代理已经把这条消息标记为已消费了,但实际上这条消息并没有被实际处理)。
如果要保证消息被处理,消息代理发送完消息后,要设置状态为“已发送”,只有收到消费者的确认请求后才更新为“已消费”,这就
需要消息代理中记录所有的消费状态,这种做法显然是不可取的。
Kafka采用拉取模型,由消费者自己记录消费状态,每个消费者互相独立地顺序读取每个分区的消息,如下图所示,有两个消费者(不同消费者组)拉取同一个主题的消息,消费者A的消费进度是3,消费者B的消费进度是6。消费者拉取的最大上限通过最高水位(watermark)控制,生产者最新写入的消息如果还没有达到备份数量,对消费者是不可见的。这种由消费者控制偏移量的优点是:消费者可以按照任意的顺序消费消息。比如,消费者可以重置到旧的偏移量,重新处理之前已经消费过的消息;或者直接跳到最近的位置,从当前的时刻开始消费。
package com.kafka.producer;
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
public class CustomerProducer {
public static void main(String[] args) {
//1:配置生产者属性
Properties props = new Properties();
//配置kafka集群节点信息
props.put("bootstrap.servers","hadoop102:9092");
//配置发送的消息是否等待应答
props.put("acks","all");
//配置消息发送失败重试次数
props.put("retries","0");
//批量处理数据的大小
props.put("batch.size","16384");
//设置批处理数据的延迟,单位:ms
props.put("linger.ms","5");
//设置内存缓冲区大小,32MB
props.put("buffer.memory","33554432");
//对要发送的数据进行序列化
props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
//2:实例化kafkaProducer
KafkaProducer producer = new KafkaProducer(props);
//3:调用kafka发送消息
for (int i = 0; i < 50; i++) {
producer.send(new ProducerRecord("test1","hello"+i));
}
//关闭资源
producer.close();
}
}
package com.kafka.producer;
import java.util.Properties;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
public class CustomerProducer2 {
public static void main(String[] args) {
//1:配置生产者属性
Properties props = new Properties();
//配置kafka集群节点信息
props.put("bootstrap.servers","hadoop102:9092");
//配置发送的消息是否等待应答
props.put("acks","all");
//配置消息发送失败重试次数
props.put("retries","0");
//批量处理数据的大小
props.put("batch.size","16384");
//设置批处理数据的延迟,单位:ms
props.put("linger.ms","5");
//设置内存缓冲区大小,32MB
props.put("buffer.memory","33554432");
//对要发送的数据进行序列化
props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
//2:实例化kafkaProducer
KafkaProducer producer = new KafkaProducer(props);
//3:调用kafka发送消息
for (int i = 0; i < 50; i++) {
//发送消息加上回调函数
producer.send(new ProducerRecord("test1","hello"+i),new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if(metadata!=null) {
System.out.println(metadata.offset()+"-----"+metadata.partition());
}
}
});
}
//关闭资源
producer.close();
}
}
package com.kafka;
import java.util.Arrays;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
public class CustomerConsumer {
public static void main(String[] args) {
Properties props = new Properties();
// 定义kakfa 服务的地址,不需要将所有broker指定上
props.put("bootstrap.servers", "hadoop102:9092");
// 制定consumer group
props.put("group.id", "test");
// 是否自动确认offset
props.put("enable.auto.commit", "true");
// 自动确认offset的时间间隔
props.put("auto.commit.interval.ms", "1000");
// key的序列化类
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// value的序列化类
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 定义consumer
KafkaConsumer consumer = new KafkaConsumer<>(props);
// 消费者订阅的topic, 可同时订阅多个
consumer.subscribe(Arrays.asList("test1"));
while (true) {
// 读取数据,读取超时时间为100ms
ConsumerRecords records = consumer.poll(100);
for (ConsumerRecord record : records)
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
}
package com.kafka.intercepter;
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
/***
* 在消息发送前将时间戳信息加到消息value的最前部
* @author KGF
*
*/
public class TimeIntecpter implements ProducerInterceptor{
@Override
public void configure(Map configs) {
}
/**
* 该方法就是Producer确保在消息被序列化以及计算分区前调用该方法。
* 用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响目标分区的计算。
* 消息传递到这里的时候还没有到达kafka集群
*/
@Override
public ProducerRecord onSend(ProducerRecord record) {
return new ProducerRecord(record.topic(),
record.partition(),
record.key(),
System.currentTimeMillis()+","+record.value());
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
}
@Override
public void close() {
}
}
package com.kafka.intercepter;
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
/***
* 在消息发送后更新成功发送消息数或失败发送消息数。
* @author KGF
*
*/
public class CounterIntecepter implements ProducerInterceptor {
private int errorCounter = 0;
private int successCounter = 0;
@Override
public void configure(Map configs) {
}
/**
* 进入kafka集群前不对消息进行任何处理
*/
@Override
public ProducerRecord onSend(ProducerRecord record) {
return record;
}
/**
* 该方法会在消息被应答或消息发送失败时调用,并且通常都是在producer回调逻辑触发之前。
* onAcknowledgement运行在producer的IO线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率
*/
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
// 统计成功和失败的次数
if (exception == null) {
successCounter++;
} else {
errorCounter++;
}
}
/***
* 关闭interceptor,主要用于执行一些资源清理工作
*/
@Override
public void close() {
// 保存结果
System.out.println("Successful sent: " + successCounter);
System.out.println("Failed sent: " + errorCounter);
}
}
⑶创建生成者,在生产者中添加拦截器链package com.kafka.producer;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
public class CustomerProducer {
public static void main(String[] args) {
//1:配置生产者属性
Properties props = new Properties();
//配置kafka集群节点信息
props.put("bootstrap.servers","hadoop102:9092");
//配置发送的消息是否等待应答
props.put("acks","all");
//配置消息发送失败重试次数
props.put("retries","0");
//批量处理数据的大小
props.put("batch.size","16384");
//设置批处理数据的延迟,单位:ms
props.put("linger.ms","5");
//设置内存缓冲区大小,32MB
props.put("buffer.memory","33554432");
//对要发送的数据进行序列化
props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
// 2 构建拦截链
List interceptors = new ArrayList<>();
interceptors.add("com.kafka.intercepter.TimeIntecpter");
interceptors.add("com.kafka.intercepter.CounterIntecepter");
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
//2:实例化kafkaProducer
KafkaProducer producer = new KafkaProducer(props);
//3:调用kafka发送消息
for (int i = 0; i < 50; i++) {
producer.send(new ProducerRecord("test1","hello"+i));
}
//关闭资源
producer.close();
}
}