Kafka应用——自定义生产者与消费者

实验内容

在某一应用场景中,有两个消费者生产消息和一个消费者消费消息,他们利用Kafka集群进行消息传输。

其中生产者需要记录消息来源(即发送消息的主机名或IP)及消息发送时间(格式为年月日 时分秒),

生产者1同步发送消息、生产者2采用异步发送消息,都需要显示消息发送成功还是失败。

用户通过输入消息内容通过生产者(生产者1或生产者2)将消息发送到Kafka,消费者根据消息内容能区分消息来源。

kafka 生产者事务 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());
        	}
        }
    }
}

运行生产者

kafka 生产者事务 kafka生产者发送消息_kafka_02

kafka 生产者事务 kafka生产者发送消息_kafka 生产者事务_03

运行消费者

kafka 生产者事务 kafka生产者发送消息_kafka_04