Consumer之自动提交
在上文中介绍了Producer API的使用,现在我们已经知道如何将消息通过API发送到Kafka中了,那么现在的生产者/消费者模型就还差一位扮演消费者的角色了。因此,本文将介绍Consumer API的使用,使用API从Kafka中消费消息,让应用成为一个消费者角色。
还是老样子,首先我们得创建一个Consumer实例,并指定相关配置项,有了这个实例对象后我们才能进行其他的操作。代码示例:
/**
* 创建Consumer实例
*/
public static Consumer createConsumer() {
Properties props = new Properties();
// 指定Kafka服务的ip地址及端口
props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127。0.0.1:9092");
// 指定group.id,Kafka中的消费者需要在消费者组里
props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 是否开启自动提交
props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 自动提交的间隔,单位毫秒
props.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
// 消息key的序列化器
props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
// 消息value的序列化器
props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
return new KafkaConsumer<>(props);
}
在以上代码中,可以看到设置了group.id这个配置项,这是一个Consumer的必要配置项,因为在Kafka中,Consumer需要位于一个Consumer Group里。具体如下图所示:
在上图中是一个Consumer消费一个Partition,是一对一的关系。但Consumer Group里可以只有一个Consumer,此时该Consumer可以消费多个Partition,是一对多的关系。如下图所示:
一个Consumer可以只消费一个Partition,也可以消费多个Partition,但需要注意的是多个Consumer不能消费同一个Partition:
总结一下Consumer的注意事项:
单个Partition的消息只能由Consumer Group中的某个Consumer来消费
Consumer从Partition中消费消息是顺序的,默认从头开始消费
如果Consumer Group中只有一个Consumer,那么这个Consumer会消费所有Partition中的消息
在Kafka中,当消费者消费数据后,需要提交数据的offset来告知服务端成功消费了哪些数据。然后服务端就会移动数据的offset,下一次消费的时候就是从移动后的offset位置开始消费。
这样可以在一定程度上保证数据是被消费成功的,并且由于数据不会被删除,而只是移动数据的offset,这也保证了数据不易丢失。若消费者处理数据失败时,只要不提交相应的offset,就可以在下一次重新进行消费。
和数据库的事务一样,Kafka消费者提交offset的方式也有两种,分别是自动提交和手动提交。在本例中演示的是自动提交,这也是消费数据最简单的方式。代码示例:
/**
* 演示自动提交offset
*/
public static void autoCommitOffset() {
Consumer consumer = createConsumer();
List topics = List.of("MyTopic");
// 订阅一个或多个Topic
consumer.subscribe(topics);
while (true) {
// 从Topic中拉取数据,每1000毫秒拉取一次
ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
// 每次拉取可能都是一组数据,需要遍历出来
for (ConsumerRecord record : records) {
System.out.printf("partition = %d, offset = %d, key = %s, value = %s%n",
record.partition(), record.offset(), record.key(), record.value());
}
}
}
Consumer之手动提交
自动提交的方式是最简单的,但不建议在实际生产中使用,因为可控性不高。所以更多时候我们使用的是手动提交,但想要使用手动提交,就需要先关闭自动提交,修改配置项如下:
props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
关闭了自动提交后,就得在代码中调用commit相关的方法来提交offset,主要就是两个方法:commitAsync和commitSync,看方法名也知道一个是异步提交一个是同步提交。
这里以commitAsync为例,实现思路主要是在发生异常的时候不要调用commitAsync方法,而在正常执行完毕后才调用commitAsync方法。代码示例:
/**
* 演示手动提交offset
*/
public static void manualCommitOffset() {
Consumer consumer = createConsumer();
List topics = List.of("MyTopic");
// 订阅一个或多个Topic
consumer.subscribe(topics);
while (true) {
// 从Topic中拉取数据,每1000毫秒拉取一次
ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
// 每次拉取可能都是一组数据,需要遍历出来
for (ConsumerRecord record : records) {
try {
// 模拟将数据写入数据库
Thread.sleep(1000);
System.out.println("