前言
建议对kafka还不了解的小伙伴先看此文章:Kafka入门笔记(一) --kafka概述+kafka集群搭建 与 Kafka入门笔记(二) --kafka常用命令
一、Producer(生产者)
1、消息发送流程
- Kafka 的 Producter 发送消息采用的是异步发送的方式。涉及两个线程:main线程和Sender线程,以及一个线程共享变量:RecordAccumulator。
- 执行过程:main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消费发送到Kafka broker。
- 发送消息流程:
相关参数:
1) batch.size:只有数据累积到batch.size之后,sender才会发送数据;
2)linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。
2、异步发送API
- 导入kafka依赖:
<!--kafka依赖-->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.0</version>
</dependency>
<!--Json转换相关依赖-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib-ext-spring</artifactId>
<version>1.0.2</version>
</dependency>
- 涉及类
- ProducerConfig:获取所需的一系列配置参数:
// 生产者相关配置
Properties properties = new Properties();
//设置kafka集群
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094");
//ack模式
properties.put(ProducerConfig.ACKS_CONFIG,"all");
//发送失败重试次数
properties.put(ProducerConfig.RETRIES_CONFIG,"3");
//批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,"16384");
//等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
//缓存区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,3355432);
//key序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
//value序列化
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
ack应答机制:kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行配置。
acks参数配置:
① 0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没写入磁盘就以及返回,当broker故障时有可能丢失数据;
② 1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据;
③ -1(all):producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack,但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复。
- KafkaProducer:需要创建一个生产者对象,用来发送数据
//创建生产者对象
Producer producer = new KafkaProducer<>(properties);
- ProducerRecord:每条数据都要封装成一个ProducerRecord对象
//生产者要发送的消息
map = new HashMap<>();
map.put("globalTaskId","T2024011600007");
JSONObject json = JSONObject.fromObject(map);
// ProducerRecord(topic,key,val)
producer.send(new ProducerRecord("modelTopic","risk", json.toString()));
//关闭资源
producer.close();
- API带回调函数的生产者
ProducerConfig配置同上就不复述了。
方法一:发送数据,并创建回调函数
//创建生产者对象
Producer producer = new KafkaProducer<>(properties);
map = new HashMap<>(4);
map.put("globalTaskId","T2024011600007");
JSONObject json = JSONObject.fromObject(map);
producer.send(new ProducerRecord("modelTopic", "risk", json.toString()), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("finalI:" + finalI +";topic:" + recordMetadata.topic() + ";offset:" + recordMetadata.offset()
+ ";partition:" + recordMetadata.partition() + ";serializedKeySize:" + recordMetadata.serializedKeySize()
+ ";serializedValueSize:" + recordMetadata.serializedValueSize() + ";timestamp:" + recordMetadata.timestamp());
}else{
e.printStackTrace();
}
}
});
//关闭资源
producer.close();
方法二:发送数据,使用lambda表达式创建回调函数
//创建生产者对象
Producer producer = new KafkaProducer<>(properties);
map = new HashMap<>(4);
map.put("globalTaskId","T2024011600007");
JSONObject json = JSONObject.fromObject(map);
producer.send(new ProducerRecord("modelTopic","risk", json.toString()), (recordMetadata, e) -> {
if (e == null) {
System.out.println("finalI:" + finalI +";topic:" + recordMetadata.topic() + ";offset:" + recordMetadata.offset()
+ ";partition:" + recordMetadata.partition() + ";serializedKeySize:" + recordMetadata.serializedKeySize()
+ ";serializedValueSize:" + recordMetadata.serializedValueSize() + ";timestamp:" + recordMetadata.timestamp());
}else{
e.printStackTrace();
}
});
//关闭资源
producer.close();
- API自定义分区的生产者
①自定义分区类
package com.ts.kafka.producer;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
* @author
*/
public class MyPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
// 自定义分区方法
//获取该topic分区数
Integer integer = cluster.partitionCountForTopic(topic);
//分区逻辑
return key.toString().hashCode() % integer;
}
@Override
public void close() {}
@Override
public void configure(Map<String, ?> map) {}
}
②分区器的使用,在配置中添加分区器
//在生产者相关配置中添加分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.ts.kafka.producer.MyPartitioner");
3、同步发送API
- 同步发送:一条消息发送之后,会阻塞当前线程,直至返回ack;
- 由于send方法返回的是一个Future对象,根据Futrue对象的特点,我们也可以实现同步发送的效果,只需在调用Future对象的get方法即可;
- 具体实现如下:
//发送消息
// 返回 Future
Future<RecordMetadata> send = producer.send(new ProducerRecord("topic", "key", "val"));
try {
// 阻塞
RecordMetadata metadata = send.get();
System.out.println("partition:"+metadata.partition());
} catch (ExecutionException e) {
e.printStackTrace();
}
- 在同一分区,又同一个同步发送,保证发送数据与接收到顺序一直
4、事务模式
- 事务模式要求数据发送必须包含在事务中,在事务中可以向多个topic发送数据,消费者端最好也使用事务模式读,保证一次能将整个事务的数据全部读取过来。当然消费者也可以不设置为事务读的模式。
- 事务包括:
- 初始化事务:producer.initTransactions();
- 开始事务:producer.beginTransaction();
- 提交事务:producer.commitTransaction();
- 终止事务:producer.abortTransaction();
- 实现如下:
public void ProducerData(){
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094");
//ack模式
properties.put(ProducerConfig.ACKS_CONFIG,"all");
//发送失败重试次数
properties.put(ProducerConfig.RETRIES_CONFIG,"3");
//批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,"16384");
//等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
//缓存区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,3355432);
//key序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
//value序列化
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
Producer producer = new KafkaProducer<>(properties);
//初始化事务
producer.initTransactions();
try {
//数据发送必须在beginTransaction()和commitTransaction()中间,否则会报状态不对的异常
//开始事务
producer.beginTransaction();
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<>("mytopic1", Integer.toString(i), Integer.toString(i)));
}
//提交事务
producer.commitTransaction();
}catch (ProducerFencedException | Out0fOrderSeguenceException |AuthorizationException e){
//这些异常不能被恢复,因此必须要关闭并退出
producer.close();
} catch (KafkaException e){
//出现其它异常,终止事务
producer.abortTransaction();
}
producer.close();
}
最近比较忙,kafka消费者相关的API使用在下一节更新。