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. 第十三节:telnetlib、redis、threading模块

    telnetlib模块案例: import telnetlib,re class TelnetInfo(): def telnetdo(self, host, port, command): tn = ...

  2. 5分钟python爬虫案例,手把手教爬取国内外最新疫情历史数据

    俗话说的好,“授之以鱼不如授之以渔”,所以小编今天就把爬疫情历史数据的方法分享给你们. 基本思路:分析腾讯新闻“抗肺炎”版块,采用“倒推法”找到疫情数据接口,然后用python模拟请求,进而保存疫情历 ...

  3. spring源码阅读笔记08:bean加载之创建bean

    上文从整体视角分析了bean创建的流程,分析了Spring在bean创建之前所做的一些准备工作,并且简单分析了一下bean创建的过程,接下来就要详细分析bean创建的各个流程了,这是一个比较复杂的过程 ...

  4. stand up meeting 12-8

    根据计划今天项目组成员和travis老师毕然同学进行了最后一次关于design和feature的确认meeting. 项目design和UI的改动较大,feature改动较小,需对UI进行重新整合,对 ...

  5. el-tab-pane label的文字内容怎样设间距

    el-tab-pane label的文字内容怎样设间距 问题描述: 在使用element-ui的el-tab-pane做标签页时,label属性的位置与样式不能通过style样式直接解决 百度后几乎没 ...

  6. BUUOJ [CISCN2019 华北赛区 Day2 Web1]Hack World

    补一下这道题,顺便发篇博客 不知道今年国赛是什么时候,菜鸡还是来刷刷题好了 0X01 考点 SQL注入.盲注.数字型 0X02自己尝试 尝试输入1 赵师傅需要女朋友吗???随便都能有好吧 输入2 ?? ...

  7. python-用户输入和while循环

    函数input() 比较大小要同类型: age=iput() 21 age=int(age) age>=10 true prompt = "If you tell us who you ...

  8. # Unity 游戏框架搭建 2019 (三十四、三十五) 9 ~ 10 示例整理

    第九个示例 目前代码如下: using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace QFramework { p ...

  9. 通用Mapper使用

    通用Mapper介绍 产生背景 使用Mybatis的开发者大多会因为繁多的XML映射配置而头痛不已

  10. Web前端三大主流框架是什么?Web前端前景与就业形势

    近十年以来,IT行业发展火热,衍生了很多新职业,例如UI设计师.开发工程师.软件测试工程师等等,在众多备受瞩目的新生职业中,Web前端工程师是其中的一员.那么Web前端三大主流框架是什么呢? 一.We ...