Kafka应用——自定义生产者与消费者
实验内容
在某一应用场景中,有两个消费者生产消息和一个消费者消费消息,他们利用Kafka集群进行消息传输。
其中生产者需要记录消息来源(即发送消息的主机名或IP)及消息发送时间(格式为年月日 时分秒),
生产者1同步发送消息、生产者2采用异步发送消息,都需要显示消息发送成功还是失败。
用户通过输入消息内容通过生产者(生产者1或生产者2)将消息发送到Kafka,消费者根据消息内容能区分消息来源。
实现思路
首先我们明确需要两个生产者,两个生产者唯一的区别就是同步发送和异步发送。
实现记录消息来源和发送时间的话,我们就需要用到拦截器,分别是时间拦截器和状态拦截器。
消费者的话只需要接收消息并打印出消息就可以了。
生产者1,同步发送
package hadoop.kafkademo;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
/**
* @author zhz
* @date 2021/4/17 17:13
* 备注:生产者类,同步发送
*/
public class MyProducer
{
public static void main( String[] args ) throws Exception
{
// 使用Properties定义配置属性
Properties props=new Properties();
// 设置生产者Broker服务器链接地址
props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos01:9092,centos02:9092,centos03:9092");
// 设置序列化key程序类
props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 设置序列化value程序类
props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 设置拦截器链
List<String> interceptors=new ArrayList<String>();
// 添加拦截器TimeInterceptor(指定全路径)
interceptors.add("hadoop.kafkademo.TimeInterceptor");
// 添加拦截器CounterInterceptor(指定全路径)
interceptors.add("hadoop.kafkademo.CounterInterceptor");
// 将拦截器添加到配置属性中
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);
// 定义消息生产者对象,依靠对象可以进行消息传输
Producer<String,String> producer=new KafkaProducer<String, String>(props);
Random rd=new Random();
// 循环发送消息
for(int i=0;i<10;i++)
{
/**
* 第一个参数:主题名称
* 第二歌参数:消息的key值
* 第三个参数:消息的value值
* 如果服务器返回错误,get()方法会抛出异常,如果没有错误,可以得到一个RecordMetadata对象,可以利用该对象获得消息的偏移量.
*/
try{
producer.send(new ProducerRecord<String, String>("topictest","hello kafka"+i,"i"));
}catch (Exception e){
e.printStackTrace();
}
Thread.sleep(rd.nextInt(15)*1000);
}
// 关闭生产者,释放资源
producer.close();
}
}
生产者2,异步发送
package hadoop.kafkademo;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
/**
* @author zhz
* @date 2021/4/17 19:02
* 备注:生产者类2,异步发送
*/
public class MyProducer2
{
public static void main( String[] args ) throws Exception
{
// 使用Properties定义配置属性
Properties props=new Properties();
// 设置生产者Broker服务器链接地址
props.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos01:9092,centos02:9092,centos03:9092");
// 设置序列化key程序类
props.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 设置序列化value程序类
props.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 设置拦截器链
List<String> interceptors=new ArrayList<String>();
// 添加拦截器TimeInterceptor(指定全路径)
interceptors.add("hadoop.kafkademo.TimeInterceptor");
// 添加拦截器CounterInterceptor(指定全路径)
interceptors.add("hadoop.kafkademo.CounterInterceptor");
// 将拦截器添加到配置属性中
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);
// 定义消息生产者对象,依靠对象可以进行消息传输
Producer<String,String> producer=new KafkaProducer<String, String>(props);
Random rd=new Random();
// 循环发送消息
for(int i=0;i<10;i++)
{
/**
* 第一个参数:主题名称
* 第二歌参数:消息的key值
* 第三个参数:消息的value值
* 使用send()发送消息,可以指定一个回调方法,服务器返回响应信息时会调用该方法.可以在该方法中对异常消息进行处理
*/
producer.send(new ProducerRecord<String, String>("topictest", "hello kafka" + i, "i"), new Callback() {
public void onCompletion(RecordMetadata recordMetadata, Exception e) {//如果Kafka返回一个错误,则onCompletion()会抛出一个非空异常
if(e!=null){
e.printStackTrace();
}
}
});
Thread.sleep(rd.nextInt(15)*1000);
}
// 关闭生产者,释放资源
producer.close();
}
}
时间拦截器
package hadoop.kafkademo;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.clients.producer.internals.ProducerInterceptors;
/**
* @author zhz
* @date 2021/4/19 16:09
* 备注:时间戳拦截器
* 发送消息之前,在消息内容前面加上时间戳
*/
public class TimeInterceptor implements ProducerInterceptor<String, String>
{
/**
* 该方法在初始化数据的时候被调用,用于获取生产者的配置信息。
* @param configs
*/
public void configure(Map<String, ?> configs)
{
System.out.println(configs.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
}
/**
* 该方法在消息序列化之前带哦用,并传入要发送的消息记录。
* 可以在该方法中对消息记录进行任意修改,包括消息的key和value以及要发送的主题和分区等
* @param record
* @return
*/
public ProducerRecord<String,String> onSend(ProducerRecord<String, String> record)
{
System.out.println("TimerInterceptor--onSend()方法");
ProducerRecord<String,String> proRecord=new ProducerRecord<String,String>(
record.topic(),record.key(),System.currentTimeMillis()+" "+record.value().toString());
return proRecord;
}
/**
* 该方法在发送到服务器的记录已被确认或者记录发送失败时候调用(在生产者回调逻辑触发之前)
* 可以在metadata对象中获取消息的主题、分区和偏移量等消息,在exception对象中获取消息的异常信息
* @param metadata
* @param exception
*/
public void onAcknowledgement(RecordMetadata metadata, Exception exception)
{
System.out.println("TimerInterceptor--onAcknowlegement()方法");
}
/**
* 该方法用于关闭拦截器并释放资源。当生产者关闭时调用
*/
public void close()
{
System.out.println("TimerInterceptor--close()方法");
}
}
状态拦截器
package hadoop.kafkademo;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.clients.producer.internals.ProducerInterceptors;
/**
* @author zhz
* @date 2021/4/19 16:09
* 备注:消息发送状态拦截器
* 统计发送成功和失败的消息树,并在生产者关闭时打印这两个消息数
*/
public class CounterInterceptor implements ProducerInterceptor<String, String>
{
private int sc=0;//successCounter发送成功的消息数量
private int fc=0;//errorCounter发送失败的消息数量
/**
* 获取生产者配置信息
* @param configs
*/
public void configure(Map<String, ?> configs)
{
System.out.println(configs.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
}
/**
* 该方法在消息发送前调用
* 修改发送的消息记录,这里不用做处理,输出提示即可
* @param record
* @return
*/
public ProducerRecord<String,String> onSend(ProducerRecord<String, String> record)
{
System.out.println("CounterInterceptor--onSend()方法");
return record;
}
/**
* 该方法在消息发送完毕后调用
* 当发送到服务器的记录已被确认,或者记录发送失败时调用
* @param metadata
* @param exception
*/
public void onAcknowledgement(RecordMetadata metadata, Exception exception)
{
System.out.println("CounterInterceptor--onAcknowlegement()方法");
//统计成功和失败次数
if(exception==null)
sc++;
else
fc++;
}
/**
* 生产者关闭时调用该方法,可以再次将结果进行持久化保存
*/
public void close()
{
System.out.println("CounterInterceptor--close()方法");
//打印统计结果
System.out.println("成功发送消息数量:"+sc);
System.out.println("发送失败消息数量:"+fc);
}
}
消费者
package hadoop.kafkademo;
import java.util.*;
import java.time.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.*;
/**
* @author zhz
* @date 2021/4/17 19:19
* 备注:消费者类
*/
public class MyConsumer
{
public static void main( String[] args )
{
// 使用Properties定义配置属性
Properties props=new Properties();
// 设置生产者Broker服务器链接地址
props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos01:9092,centos02:9092,centos03:9092");
// 设置反序列化key程序类,与生产者对应
props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 设置反序列化value程序类,与生产者对应
props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 设置消费者ID,即组名称,值可自定义.组名称相同的消费者进程属于同一个消费者类
props.setProperty(ConsumerConfig.GROUP_ID_CONFIG,"group-1");
// 自定义消费者对象
Consumer<String,String> consumer=new KafkaConsumer<String, String>(props);
// 设置消费者读取的主题名称,可以设置多个
consumer.subscribe(Arrays.asList("topictest"));
// 不停的读取消息
while(true)
{
// 拉取消息并设置超时时间为2s
ConsumerRecords<String,String> records=consumer.poll(Duration.ofSeconds(2));
for(ConsumerRecord<String,String> record:records)
{
//打印关键消息
System.out.println(record.key()+","+record.value()+","+record.partition()+","+record.offset());
}
}
}
}
运行生产者
运行消费者