Consumer API


Kafka官网文档给了基本格式

http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html

JavaAPI 模板

自动提交offset

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records)
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}

手动提交offset

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "false");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
final int minBatchSize = 200;
List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
buffer.add(record);
}
if (buffer.size() >= minBatchSize) {
insertIntoDb(buffer);
consumer.commitSync();
buffer.clear();
}
}

自定义 自动提交offset

在这之前需要明白一点,自动提交是有可能造成重复消费的

比如我们设置的props.put("auto.commit.interval.ms", "1000");——提交offset值的时间间隔为1s

现在有这么几条数据等待消费


157 hello offset



287 hello world

295 abc test 900ms

351 hello abc 1000ms


157 hello offset 为这一次提交offset值的起点,351 hello abc 为提交offset值的重点

295 abc test 是到900ms的时候提交的offset,如果在此时发生了宕机,重新开始就会从157 hello offset再次进行消费,就会造成重复消费的情况

package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays;
import java.util.Properties; public class MyConsumer {
public static void main(String[] args) {
Properties props = new Properties();
//指定Kafka服务器地址
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup");
//允许程序自动提交offset,保存到kafka当中的一个topic中去
props.put("enable.auto.commit", "true");
//每隔多长时间提交一次offset的值
props.put("auto.commit.interval.ms", "1000");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义KafkaConsumer
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//订阅topic:test,并消费其中的数据
consumer.subscribe(Arrays.asList("test"));
//死循环拉取数据
while (true) {
//所有拉取到的数据都封装在了ConsumerRecords
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
long offset = record.offset();
String key = record.key();
System.out.printf("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
} }
}
}

自定义 手动提交offset
package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties; public class ManualOffsetCommit {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
props.put("group.id", "testGroup2");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("test"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
long offset = record.offset();
String key = record.key();
System.out.printf("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
// ConsumerRecords 里面的数据消费完后,需要提交offset值
// 使用异步提交的方法,不会阻塞程序的消费
// consumer.commitSync();
// 同步提交
consumer.commitSync();
}
}
}

消费完每个分区后手动提交offset
package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; import java.util.*; public class CommitPartition {
public static void main(String[] args) {
Properties props = new Properties();
//指定kafka服务器地址
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup4");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义kafkaConsumer
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props);
//订阅topic:test 并消费其中的数据
kafkaConsumer.subscribe(Arrays.asList("test"));
//调用poll方法,获取所有的数据,包含各个分区的数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(3000);
//获取topic中所有分区
Set<TopicPartition> partitions = consumerRecords.partitions();
//循环消费数据
for (TopicPartition topicPartition : partitions) {
//获取一个分区立面的所有数据
List<ConsumerRecord<String, String>> records = consumerRecords.records(topicPartition);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
String key = record.key();
long offset = record.offset();
System.out.println("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
//提交partition的offset值
//Map<TopicPartition, OffsetAndMetadata> offsets //获取分区里面最后一条数据的offset值
long offset = records.get(records.size() - 1).offset();
Map<TopicPartition, OffsetAndMetadata> topicPartitionOffsetAndMetadataMap = Collections.singletonMap(topicPartition, new OffsetAndMetadata(offset)); //处理完成一个分区里面的数据后提交offset
kafkaConsumer.commitSync(topicPartitionOffsetAndMetadataMap);
} }
}

消费指定分区数据
package cn.itcast.kafka.demo2;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; import java.util.*; /**
* 消费指定分区
*/
public class ConsumerMyPartition {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
//指定消费者组的名字
props.put("group.id", "testGroup4");
//关闭自动提交offset值,改为手动提交
props.put("enable.auto.commit", "false");
//数据key和value的序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//定义kafkaComsumer
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props); //Collection<TopicPartition> partitions
//创建一个集合 泛型为TopicPartition
TopicPartition topicPartition = new TopicPartition("test", 0);
TopicPartition topicPartition1 = new TopicPartition("test", 1); List<TopicPartition> topicPartitions = Arrays.asList(topicPartition, topicPartition1); //通过assign方法注册消费topic:test中的某些分区
kafkaConsumer.assign(topicPartitions); while (true) {
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(3000);
//获取所有分区
Set<TopicPartition> partitions = consumerRecords.partitions();
for (TopicPartition topicPartition2 : partitions) {
//获取一个分区中的所有数据
List<ConsumerRecord<String, String>> records = consumerRecords.records(topicPartition2);
for (ConsumerRecord<String, String> record : records) {
int partition = record.partition();
String value = record.value();
String key = record.key();
long offset = record.offset();
System.out.println("数据的key为" + key + ",数据的value为" + value + ",数据的offset为" + offset + ",数据的分区为" + partition);
}
long offset = records.get(records.size() - 1).offset();
kafkaConsumer.commitSync(Collections.singletonMap(topicPartition2, new OffsetAndMetadata(offset))); }
}
}
}

重复消费和数据丢失



以上图为例,Consumer需要将数据写入到Hbase后,再提交offset值。那么就可以有四种上传情况的发生:

一、写入Hbase成功,提交offset成功 —— 这就是正常的消费情况

二、写入Hbase失败,提交offset失败 —— 不会有什么影响,继续进行消费即可

三、写入Hbase成功,但是offset提交失败 —— 这就会造成重复消费

四、写入Hbase失败,但是offset提交成功 —— 这样就会造成数据丢失



Kafka一共有三种消费模型:

exactly once —— 没有出错

at least once —— 重复消费

at most once —— 数据丢失

出现后两种模型的原因一般是offset没有管理好

实际工作中大多数公司的解决办法是将offset的值保存到redis或者hbase当中



数据消费存在高阶API (High Level API)低阶API (High Level API)

高阶API是将offset值默认保存在zk中,早期的Kafka一般默认使用高阶API。

低阶API就是将offset值保存在kafka自带的一个topic种

【Kafka】Consumer API的更多相关文章

  1. 【Kafka】Producer API

    Producer API Kafka官网文档给了基本格式 地址:http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/cli ...

  2. 【Kafka】Consumer配置

    从0.9.0.0开始,下面是消费者的配置. 名称 描述 类型 默认值 bootstrap.servers 消费者初始连接kafka集群时的地址列表.不管这边配置的什么地址,消费者会使用所有的kafka ...

  3. 【Kafka】Stream API

    Stream API Kafka官方文档给了基本格式 http://kafka.apachecn.org/10/javadoc/index.html?org/apache/kafka/streams/ ...

  4. 【Kafka】JavaAPI操作

    目录 先创建Maven工程导入jar包 Producer API Consumer API Stream API 先创建Maven工程导入jar包 帮助文档地址:http://kafka.apache ...

  5. 【译】Android API 规范

    [译]Android API 规范 译者按: 修改R代码遇到Lint tool的报错,搜到了这篇文档,aosp仓库地址:Android API Guidelines. 58e9b5f Project ...

  6. 【kafka】Java连接出现Connection refused: no further information的解决方法

    在Linux机器(ip:10.102.16.203)安装完kafka(参考:kafka的安装及使用),在windows上使用Java接口访问服务时(参考:Java实现Kafka的生产者.消费者),报异 ...

  7. 【Kafka】《Kafka权威指南》——分区partition

    在上篇的例子里([Kafka]<Kafka权威指南>--写数据), ProducerRecord 对象包含了目标主题.键和值. Kafka 的消息是 一个个 键值对, ProducerRe ...

  8. 【Kafka】数据分区策略

    数据分区策略 四种策略 一.指定分区号,数据会直接发送到所指定的分区 二.没有指定分区号,指定了数据的key,可以通过key获取hashCode决定数据发送到哪个分区 三.都没有指定的话,会采取rou ...

  9. 【Kafka】Kafka-分区数-备份数-如何设置-怎么确定-怎么修改

    Kafka-分区数-备份数-如何设置-怎么确定-怎么修改 kafka partition 数量 更新_百度搜索 kafka重新分配partition - - CSDN博客 如何为Kafka集群选择合适 ...

随机推荐

  1. 使用pandas读取csv文件和写入文件

    这是我的CSV文件 读取其中得tempo这一列 import pandas as pd #导入pandas包 data = pd.read_csv("E:\\毕设\\情感识别\\Music- ...

  2. ASE project demo:pdf

    欢迎使用 pdf ~ 主页面如下,整个app风格一致,保持简约舒适的视觉体验~ 侧边栏打开,可选择打开新的pdf文件,返回主页面,打开本地生词本,登录等操作~ 可以点击侧边栏OpenFile打开新的p ...

  3. 实验一 熟悉IDLE和在线编程平台

    实验目的 1.掌握python IDLE集成开发环境的安装与使用 2.熟悉在线编程平台 3.掌握基本的python程序编写.编译与运行程序的方法 实验内容 1.按照实验指导安装IDLE,尝试交互式运行 ...

  4. get 获取方式练习题及dom基础

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. 图解AVL树

    1:AVL树简介 二叉搜索树在一般情况下其搜索的时间复杂度为O(logn),但某些特殊情况下会退化为链表,导致树的高度变大且搜索的时间复杂度变为O(n),发挥不出树这种数据结构的优势,因此平衡二叉树便 ...

  6. 一年时间,Pipenv就成为Python官方推荐的顶级工具?

    Pipenv是Kenneth Reitz在一年多前创建的“面向程序员的Python开发工作流程”,现在已成为管理软件包依赖关系的Python官方推荐资源. Python软件包安装管理的简要历史 为了正 ...

  7. 最通俗易懂的Redis发布订阅及代码实战

    发布订阅简介 除了使用List实现简单的消息队列功能以外,Redis还提供了发布订阅的消息机制.在这种机制下,消息发布者向指定频道(channel)发布消息,消息订阅者可以收到指定频道的消息,同一个频 ...

  8. 二、Go语言开发环境安装与编写第一个Hello World

    本系列文章均为学习过程中记录的笔记,欢迎和我一起来学习Go语言. 全文使用环境如下: Go语言版本:1.13 操作系统:deepin 使用工具:Goland开发工具 Go语言追溯历史 Go语言2009 ...

  9. Java ASM学习(2)

    1.编译后的方法区,其中存储的代码都是一些字节码指令 2.Java虚拟机执行模型: java代码是在一个线程内部执行,每个线程都有自己的执行栈,栈由帧组成,每个帧表示一个方法的调用,每调用一个方法,都 ...

  10. 文件包含漏洞(pikachu)

    文件包含漏洞 在web后台开发中,程序员往往为了提高效率以及让代码看起来更加简洁,会使用'包含'函数功能,比如把一系列功能函数都写进function.php中,之后当某个文件需要调用的时候,就直接在文 ...