文章目录
- 1. producer
- 1.异步不带回调
- 2.异步带回调
- 3.同步不带回调
- 4.同步带回调
- 2.consumer
- 1. 自动提交 offset
- 2. 异步提交offset
- 3.同步提交offset
- 3.自定义功能
- 1.自定义分区规则
- 2.自定义拦截器链
1. producer
流程图
1. 异步:在没有收到ack报文就继续发送下一条信息了
2. 同步:在发送完消息后,线程会进入阻塞状态,直到producer收到ack报文为止
3. 回调函数: 就是在producer收到ack报文后,执行的函数
1.异步不带回调
package review.producer;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
/**
* @ClassName: Async
* @Description: 异步不带回调生产信息
* 要用到的类
* KafkaProducer:需要创建一个生产者对象,用来发送数据
* ProducerConfig:获取所需的一系列配置参数
* ProducerRecord:每条数据都要封装成一个ProducerRecord对象
*
* 1. 配置的key值 可以使用 ProducerConfig 类的常量
* 2. 配置的value值 可以点进ProducerConfig 类 每一个key都会有说明 该填的类型及可选项
* @Author: jjj
* @Date: 2021/12/25 19:00
**/
public class Async {
// 1.生产配置对象
public static void main(String[] args) {
// 1.写配置信息 properties
Properties properties = getProperties();
// 2.创建 kafkaProducer对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送信息 注 : 信息需要用ProduceRecord封装
for (int i = 0; i < 100; i++) {
/**
* 这里有6种重载方法
* 1. topic,value 必填
* 2. partition(分区号)
* 3. key 值
* 4. timestamp 时间戳
* 5. iterable 一个header的迭代对象???不了解
* 这里的泛型时 key,value 的数据类型
*
*/
ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "#");
producer.send(record);
}
// 关闭连接
producer.close();
}
public static Properties getProperties(){
Properties properties = new Properties();
// 1.引导服务
properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092");
// 2.key的序列化类型
// org.apache.kafka.common.serialization.StringSerializer -> 功能是将 string型的值转换为字节数组
// 还有 。。。IntegerSerializer 等一系列序列化类型
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// 2.value的序列化类型
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
/**
* 以上是必填配置信息
* 接下来是可视情况而定
*/
// 3.ack 级别 -> 确保数据的可靠性程度 [0,1,all] 三种选择
properties.put(ProducerConfig.ACKS_CONFIG, "0");
// 4.retries 重复次数
properties.put(ProducerConfig.RETRIES_CONFIG, "1");
// 5.batchSize 批次大小 -> 当达到batchSize时,就会把数据放到recorderAccumulate
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 6.linger.ms 等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
return properties;
}
}
2.异步带回调
package review.producer;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
/**
* @ClassName: AsyncAndCallBack
* @Description: 异步带回调
* @Author: jjj
* @Date: 2021/12/25 19:00
**/
public class AsyncAndCallBack {
public static void main(String[] args) {
// 1.写配置信息
Properties properties = Async.getProperties();
// 2.创建 kafkaProducer对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "%");
producer.send(record, new Callback() {
/**
*
* @param metadata 消息的元数据
* @param exception 异常
* 如果异常为空 说明发送成功
* 否则发送失败
*/
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null)
System.out.println("success" + record.value());
else
exception.printStackTrace();
}
});
}
// 关闭资源
producer.close();
}
}
3.同步不带回调
package review.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @ClassName: Sync
* @Description: 同步发送信息 -> producer在收到ack报文之前会阻塞进程
* 由于send方法返回的是一个Future对象,根据Futrue对象的特点,我们也可以实现同步发送的效果,
* 只需在调用Future对象的get方发即可。
* @Author: jjj
* @Date: 2021/12/25 20:28
**/
public class Sync {
public static void main(String[] args) {
// 1.配置信息
Properties properties = Async.getProperties();
// 2.生产对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送信息
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>("hello", i + "^^");
Future<RecordMetadata> future = producer.send(record);
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 4.关闭资源
producer.close();
}
}
4.同步带回调
package review.producer;
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;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @ClassName: Sync
* @Description: 同步发送信息 -> producer在收到ack报文之前会阻塞进程
* 由于send方法返回的是一个Future对象,根据Futrue对象的特点,我们也可以实现同步发送的效果,
* 只需在调用Future对象的get方发即可。
* @Author: jjj
* @Date: 2021/12/25 20:28
**/
public class SyncAndCallBack {
public static void main(String[] args) {
// 1.配置信息
Properties properties = Async.getProperties();
// 2.生产对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送信息
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "^^");
Future<RecordMetadata> future = producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null){
System.out.println("success"+ record.value());
}else
exception.printStackTrace();
}
});
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 4.关闭资源
producer.close();
}
}
2.consumer
1. 自动提交 offset
1.自动提交 offset: 系统默认 没啥好说
2.手动提交 offset
无论异步同步提交都有可能尝产生数据重新消费或漏消费的情况
1. 异步提交:消费信息 与 提交 offset 这两个动作互不干涉
2. 同步提交:消费一条信息就要把该信息的offset提交上去
同步提交在提交失败的时候,会不断重新尝试提交,知道提交成功,但异步提交自会提交一次
package review.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
/**
* @ClassName: Auto
* @Description: 消费者组 自动提交 offset
* 用到的类:
* KafkaConsumer:需要创建一个消费者对象,用来消费数据
* ConsumerConfig:获取所需的一系列配置参数
* ConsumerRecord:每条数据都要封装成一个ConsumerRecord对象
* @Author: jjj
* @Date: 2021/12/25 20:45
**/
public class AutoSubmit {
public static void main(String[] args) {
// 1.获取配置
Properties properties = getProperties();
// 2.创建消费对象 与建立连接 泛型 key value 的数据类型
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
// 3.订阅主题 -> 可以订阅多个topic
ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("hello");
consumer.subscribe(list);
// 4.消费主题
while (true){
// 要用这个方法 注释说,在没有订阅任何主题或者分区就获取数据是错误的
// ConsumerRecords<String, String> poll = consumer.poll(100);
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(2));
for (ConsumerRecord<String, String> record : records) {
System.out.println("消费到:" +record.topic()
+":"+record.partition()
+":"+record.offset()
+":"+record.key()
+":"+record.value()
);
}
}
// 关闭资源 但前面时死循环所一用不到咯
}
public static Properties getProperties(){
Properties properties = new Properties();
// 1.bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// 2.序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
// 3.消费者所属组 没有会自己创建
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "1002");
// 4.自动提交是否关闭 默认开启 重点
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 5.自动提交间隔 重点
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
// 6.重置offset [earliest,latest,none]
/**
* earliest : 将offset移动到开头
* latest : 将offset 移动到最后
* none : 当 此消费者组找不到offset时抛出异常
*/
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
return properties;
}
}
2. 异步提交offset
package review.consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
/**
* @ClassName: AsyncSubmit
* @Description:
* @Author: jjj
* @Date: 2021/12/25 21:11
**/
public class AsyncSubmit {
public static void main(String[] args) {
// 1.获取配置
Properties properties = SyncSubmit.getProperties();
// 2.生成消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
// 3.订阅主题
ArrayList<String> topics = new ArrayList<>();
topics.add("hello");
topics.add("first");
consumer.subscribe(topics);
// 4.消费
while (true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println("消费到:" +record.topic()
+":"+record.partition()
+":"+record.offset()
+":"+record.key()
+":"+record.value()
);
}
// 异步提交
consumer.commitAsync();
}
}
}
3.同步提交offset
package review.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
/**
* @ClassName: SyncSubmit 相较于异步提交 会可靠一点,但这意味这会阻塞进程对吞吐量造成影响
* 无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。
* 先提交offset后消费,有可能造成数据的漏消费;
* 而先消费后提交offset,有可能会造成数据的重复消费。
*
* 所以一般 选择 异步提交
* @Description:
* @Author: jjj
* @Date: 2021/12/25 21:10
**/
public class SyncSubmit {
public static void main(String[] args) {
// 1.获取配置
Properties properties = getProperties();
// 2.生成消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
// 3.订阅主题
ArrayList<String> topics = new ArrayList<>();
topics.add("hello");
topics.add("first");
consumer.subscribe(topics);
// 4.消费
while (true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println("消费到:" +record.topic()
+":"+record.partition()
+":"+record.offset()
+":"+record.key()
+":"+record.value()
);
}
// 同步提交
consumer.commitSync();
}
}
public static Properties getProperties(){
Properties properties = new Properties();
// 1.bootstrap-server
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
// 2.序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
// 3.消费者所属组 没有会自己创建
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "1002");
// 4.自动提交是否关闭 默认开启 重点
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
// 5.自动提交间隔 重点
// properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
return properties;
}
}
3.自定义功能
1.自定义分区规则
MyPartition
package review.self_defiend;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
* @ClassName: MyPartitioner
* @Description: 自定义分区策略 默认在没有指定key,partition的情况下,默认使用 黏性分区规则
*
* @Author: jjj
* @Date: 2021/12/25 21:43
**/
public class MyPartitioner implements Partitioner {
// 核心业务逻辑
/**
*
* @param topic 消息分区
* @param key 消息 的key
* @param keyBytes key 的字节数组
* @param value 消息的 value
* @param valueBytes value 的字节数组
* @param cluster
* 需求 : 将value以 jsh 开头的给 分区0
* 其他 开头的给 分区1
*
* 注: 一般 都是对value操作的
* @return
*/
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
if (value.toString().startsWith("jsh"))
return 0; // 分区编号
else
return 1;
}
// 结尾
@Override
public void close() {
}
// 配置的
@Override
public void configure(Map<String, ?> configs) {
}
}
MypartitionProducer
package review.self_defiend;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import review.producer.Async;
import java.util.Properties;
/**
* @ClassName: Async
* @Description: 异步不带回调生产信息
* 要用到的类
* KafkaProducer:需要创建一个生产者对象,用来发送数据
* ProducerConfig:获取所需的一系列配置参数
* ProducerRecord:每条数据都要封装成一个ProducerRecord对象
*
* 1. 配置的key值 可以使用 ProducerConfig 类的常量
* 2. 配置的value值 可以点进ProducerConfig 类 每一个key都会有说明 该填的类型及可选项
* @Author: jjj
* @Date: 2021/12/25 19:00
**/
public class MyPartitionProducer {
// 1.生产配置对象
public static void main(String[] args) {
// 1.写配置信息 properties
Properties properties = Async.getProperties();
// 指定分区类
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "review.self_defiend.MyPartitioner");
// 2.创建 kafkaProducer对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 3.发送信息 注 : 信息需要用ProduceRecord封装
for (int i = 0; i < 100; i++) {
String value;
if (i % 2 == 0)
value = "jsh"+i;
else
value = "lw" + i;
ProducerRecord<String, String> record = new ProducerRecord<>("first", value);
producer.send(record);
}
// 关闭连接
producer.close();
}
}
2.自定义拦截器链
one
package review.self_defiend;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Map;
import java.util.Timer;
/**
* @ClassName: MyInterceptorOne
* @Description:
* @Author: jjj
* @Date: 2021/12/25 22:02
**/
public class MyInterceptorOne implements ProducerInterceptor {
/**
* 可以对消息修改
* 这个调用时间 在最开始 比序列化 分区还要早
* @param record 消息对象
* @return
* 需求 : 在消息前面 添加时间戳
*/
@Override
public ProducerRecord onSend(ProducerRecord record) {
// 1. 取出value对器做出修改
String value = record.value().toString();
value += System.currentTimeMillis();
// 2. 因为没有set方法 所以要重新创建一个ProducerRecord对象
return new ProducerRecord(record.topic(), record.partition(), record.timestamp(), record.key(), value);
}
/**
* 看参数就可以知道跟回调函数很像吧
* 该方法会在消息从RecordAccumulator成功发送到Kafka Broker之后,或者在发送过程中失败时调用。
* 并且通常都是在producer回调逻辑触发之前。onAcknowledgement运行在producer的IO线程中,
* 因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。
* @param metadata 消息对象的元数据
* @param exception 异常对象
* 这是事后处理
*/
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
}
@Override
public void close() {
}
// 获取配置信息和初始化数据时调用
@Override
public void configure(Map<String, ?> configs) {
}
}
two
package review.self_defiend;
import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Map;
/**
* @ClassName: MyInterceptorTwo
* @Description:
* @Author: jjj
* @Date: 2021/12/25 22:03
**/
public class MyInterceptorTwo implements ProducerInterceptor {
private int errorCounter = 0;
private int successCounter = 0;
@Override
public ProducerRecord onSend(ProducerRecord record) {
return record;
}
// 对成功数做统计
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
// 统计成功和失败的次数
if (exception == null) {
successCounter++;
} else {
errorCounter++;
}
}
@Override
public void close() {
// 保存结果
System.out.println("Successful sent: " + successCounter);
System.out.println("Failed sent: " + errorCounter);
}
@Override
public void configure(Map<String, ?> configs) {
}
}
MyInterceptorProducer
package review.self_defiend;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import review.producer.Async;
import java.util.ArrayList;
import java.util.Properties;
/**
* @ClassName: MyInterceptorProducer
* @Description:
* @Author: jjj
* @Date: 2021/12/25 22:20
**/
public class MyInterceptorProducer {
public static void main(String[] args) {
Properties properties = Async.getProperties();
// 构建拦截链 每个拦截类的引用
ArrayList<String> interceptors = new ArrayList<>();
interceptors.add("review.self_defiend.MyInterceptorOne");
interceptors.add("review.self_defiend.MyInterceptorTwo");
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>("first", i + "***");
producer.send(record);
}
// 关闭资源
producer.close();
}
}