本篇主要介绍如何进行producer的开发,为了进行相关测试,建议先按照本系列前两篇文章安装单机kafka或者kafka集群。
一、producer工作流程
producer使用用户启动producer的线程,将待发送的消息封装到一个ProducerRecord类实例,然后将其序列化之后发送给partitioner,再由后者确定目标分区后一同发送到位于producer程序中的一块内存缓冲区中。而producer的另外一个线程(Sender线程)则负责实时从该缓冲区中提取出准备就绪的消息封装进一个批次(batch),统一发送给对应的broker,具体流程如下图:
二、producer示例程序开发
首先引入kafka相关依赖,在pom.xml文件中加入如下依赖:
<!--kafka-->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.12</artifactId>
<version>2.2.0</version>
</dependency>
kafka-producer.properties
bootstrap.servers=192.168.184.128:9092,192.168.184.128:9093,192.168.184.128:9094
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=org.apache.kafka.common.serialization.StringSerializer
acks=-1
retries=3
batch.size=323840
linger.ms=10
buffer.memory=33554432
max.block.ms=3000
其中,前三个参数必须明确指定,因为这三个参数没有默认值(注:kafka的producer参数配置可以参考http://kafka.apache.org/documentation/#producerconfigs),然后编写producer发送消息的代码:
/**
* Kafka发送消息测试
* @throws IOException
*/
public void sendMsg() throws IOException {
//1.构造properties对象
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream("F:\\javaCode\\jvmdemo\\src\\main\\resources\\kafka-producer.properties");
properties.load(fileInputStream);
fileInputStream.close();
//2.构造kafkaProducer对象
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0; i < 100; i++) {
//3.构造待发送消息的producerRecord对象,并指定消息要发送到哪个topic,消息的key和value
ProducerRecord testTopic = new ProducerRecord("testTopic", Integer.toString(i), Integer.toString(i));
//4.调用kafkaProducer对象的send方法发送消息
producer.send(testTopic);
}
//5.关闭kafkaProducer
producer.close();
}
然后登陆kafka所在服务器,执行以下命令监听消息:
cd /usr/local/kafka/bin
./kafka-console-consumer.sh --bootstrap-server 192.168.184.128:9092,192.168.184.128:9093,192.168.184.128:9094 --topic testTopic --from-beginning
运行sendMsg方法,注意观察消费端,
可以看到有0-99之间的数字依次被消费到,说明消息发送成功。
三、异步和同步发送消息
上面发送消息的示例程序中,没有对发送结果进行处理,如果消息发送失败我们也是无法得知的,这种方法在实际应用中是不推荐的。在实际使用场景中,一般使用异步和同步两种常见发送方式。Java版本producer的send方法会返回一个Future对象,如果调用Future.get()方法就会无限等待返回结果,实现同步发送的效果,否则就是异步发送。
1.异步发送消息
Java版本producer的send()方法提供了回调类参数来实现异步发送以及对发送结果进行的响应,具体代码如下:
/**
* 异步发送消息
*
* @throws IOException
*/
public void sendMsg() throws IOException {
//1.构造properties对象
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream("F:\\javaCode\\jvmdemo\\src\\main\\resources\\kafka-producer.properties");
properties.load(fileInputStream);
fileInputStream.close();
//2.构造kafkaProducer对象
KafkaProducer producer = new KafkaProducer(properties);
for (int i = 0; i < 100; i++) {
//3.构造待发送消息的producerRecord对象,并指定消息要发送到哪个topic,消息的key和value
ProducerRecord testTopic = new ProducerRecord("testTopic", Integer.toString(i), Integer.toString(i));
//4.调用kafkaProducer对象的send方法发送消息,传入Callback回调参数
producer.send(testTopic, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception exception) {
if (null == exception) {
//消息发送成功后的处理
System.out.println("消息发送成功");
} else {
//消息发送失败后的处理
System.out.println("消息发送失败");
}
}
});
}
//5.关闭kafkaProducer
producer.close();
}
org.apache.kafka.clients.producer.Callback 接口的类对象。同时 onCompletion 方法的两个入参 recordMetadata 和 exception 不会同时为空,当消息发送成功后, exception 为null,消息发送失败后 recordMetadata
org.apache.kafka.common.errors.RetriableException 抽象类,理论上所有没有继承 RetriableException
//4.调用kafkaProducer对象的send方法发送消息
producer.send(testTopic, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception exception) {
if (null == exception) {
//消息发送成功后的处理
System.out.println("消息发送成功");
} else {
if(exception instanceof RetriableException){
// 可重试异常
System.out.println("可重试异常");
}else{
// 不可重试异常
System.out.println("不可重试异常");
}
}
}
});
2.同步发送消息
同步发送和异步发送是通过Java的Futrue来区分的,调用Future.get()无限等待结果返回,即实现了同步发送的结果,具体代码如下:
// 发送消息
Future future = producer.send(testTopic);
try {
// 调用get方法等待结果返回,发送失败则会抛出异常
future.get();
} catch (Exception e) {
System.out.println("消息发送失败");
}
四、其他高级特性
1.消息分区机制
kafka producer提供了分区策略以及分区器(partitioner)用于确定将消息发送到指定topic的哪个分区中。默认分区器根据murmur2算法计算消息key的哈希值,然后对总分区数求模确认消息要被发送的目标分区号(这点让我想起了redis集群中key值的分配方法),这样就确保了相同key的消息被发送到相同分区。若消息没有key值,将采用轮询的方式确保消息在topic的所有分区上均匀分配。
org.apache.kafka.clients.producer.Partitioner 接口来自定义分区器,此时需要在构造 KafkaProducer 的 properties 中增加 partitioner.class 来指明分区器实现类,如: partitioner.class=com.demo.service.CustomerPartitioner
2.消息序列化
在本篇开始的producer示例程序中,在构造 KafkaProducer 对象的时候,有两个配置项 key.serializer=org.apache.kafka.common.serialization.StringSerializer 和 value.serializer=org.apache.kafka.common.serialization.StringSerializer
ByteArraySerializer
ByteBufferSerializer
BytesSerializer
IntegerSerializer
DoubleSerializer
LongSerializer
org.apache.kafka.common.serialization.Serializer 接口,并且将 key.serializer 和 value.serializer
3.消息压缩
消息压缩可以显著降低磁盘占用以及带宽占用,从而有效提升I/O密集型应用性能,但是引入压缩同时会消耗额外的CPU,因此压缩是I/O性能和CPU资源的平衡。kafka目前支持3种压缩算法:CZIP,Snappy和LZ4,性能测试结果显示三种压缩算法的性能如下:LZ4>>Snappy>GZIP,目前启用LZ4进行消息压缩的producer的吞吐量是最高的。
KafkaProducer 对象的时候设置producer端参数 compression.type 来开启消息压缩,如配置 compression.type=LZ4
4.无消息丢失配置
KafkaProducer.send()
4.1 producer端配置
max.block.ms=3000
acks=all
retries=Integer.MAX_VALUE
max.in.flight.requests.per.connection=1
KafkaProducer.close(0)
4.2 broker端配置
unclean.leader.election.enable=false
replication.factor>=3
min.issync.replicas>1
replication.factor>min.issync.replicas ,如果两者相等,那么只要有一个副本挂掉,分区就无法工作,推荐配置 replication.factor=min.issync.replicas+1
关于producer端的开发就介绍到这儿,下一篇将介绍consumer端的开发。