-
消费位移确认
Kafka消费者消费位移确认有自动提交与手动提交两种策略。
自动提交的参数设置:
enable.auto.commit 设置的参数为true # true表示自动提交(默认)
auto.commit.interval.ms 时间的间隔 自动需要自己控制
自动提交策略由消费者协调器(ConsumerCoordinator)每隔${auto.commit.interval.ms}毫秒执行一次偏移量的提交。手动提交需要由客户端自己控制偏移量的提交。
(1)自动提交。在创建一个消费者时,默认是自动提交偏移量。例如,我们创建一个消费者,该消费者自动提交偏移量
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("client.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("test"));// 订阅主题
#位移的获取和数据key 和valuve 的值的获取
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
logger.info("offset = " + record.offset() + ", key = " + record.key() + ", value = " + record.value());
}
Properties的实例props中存放的key意义:
bootstrap.servers表示要连接的Kafka集群中的节点,其中9092表示端口号;
1)group.id表示Kafka消费者组的唯一标识;
2)enable.auto.commit为true,表示在auto.commit.interval.ms时间后会自动提交topic的offset,其中auto.commit.interval.ms默认值为5000ms;
3)其中name1和name2为要消费的topic名称,由group.id为binghe作为consumer group统一进行管理;
4)key.deserializer和value.deserializer表示指定将字节序列化为对象。
(2)手动提交。
对消费偏移量有更精确的管理,以保证消息不被重复消费以及消息不被丢失。也就是,需要在数据消费完全后再提交offset,也就是说在数据从kafka的topic取出来后并被逻辑处理后,才算是数据被消费掉,此时需要手动去提交topic的offset。
手动提交代码的示例:
public void manualOffsetCommit() {
Properties props = new Properties();
props.put("bootstrap.servers", "binghe100:9092");
props.put("group.id", "binghe");
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.Stirng.Deserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList("name1", "name2"));
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) {
// operation to handle data
consumer.commitSync();
buffer.clear();
}
注意:本方案的缺点是必须保证所有数据被处理后,才提交topic的offset。为避免数据的重复消费,可以用第三种方案,根据每个partition的数据消费情况进行提交
Kafka 提供了异步提交(commitAsync)及同步提交(commitSync)两种手动提交的方式:
两者的主要区别在于同步模式下提交失败时一直尝试提交,直到遇到无法重试的情况下才会结束,同时,同步方式下消费者线程在拉取消息时会被阻塞,直到偏移量提交操作成功或者在提交过程中发生错误。而异步方式下消费者线程不会被阻塞,可能在提交偏移量操作的结果还未返回。
手动提交的步骤:
一:创建消费者时关闭自动提交,即设置enable.auto.commit=false
二:业务处理成功后调用commitAsync()或commitSync()方法手动提交偏移量。
注意:由于同步提交会阻塞线程直到提交消费偏移量执行结果返回,
而异步提交并不会等消费偏移量提交成功后再继续下一次拉取消息的操作,
因此异步提交还提供了一个偏移量提交回调的方法commitAsync(OffsetCommitCallback callback)。
当提交偏移量完成后会回调OffsetCommitCallback 接口的onComplete()方法,这样客户端根据回调结果执行不同的逻辑处理。
手动提交partition的offset
public void manualOffsetCommitOfPartition() {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost":9092");
props.put("group.id", "binghe");
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.Stirng.Deserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList("name1", "name2"));
boolean running = true;
try {
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
for (TopicPartition partition : records.partitions()) {
List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
for (ConsumerRecord<String, String> record : partitionRecords) {
logger.info(record.offset() + " : " + record.value());
}
long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));
}
}
} finally {
consumer.close();
}
}
异步提交的是示例代码:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("client.id", "test");
props.put("fetch.max.bytes", 1024);// 为了便于测试,这里设置一次fetch 请求取得的数据最大值为1KB,默认是5MB
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"));
try {
int minCommitSize = 10;// 最少处理10 条消息后才进行提交
int icount = 0 ;// 消息计算器
while (true) {
// 等待拉取消息
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records) {
// 简单打印出消息内容,模拟业务处理
System.out.printf("partition = %d, offset = %d,key= %s value = %s%n", record. partition(), record.offset(), record.key(),record.value());
icount++;
}
// 在业务逻辑处理成功后提交偏移量
if (icount >= minCommitSize){
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
if (null == exception) {
// TODO 表示偏移量成功提交
System.out.println("提交成功");
} else {
// TODO 表示提交偏移量发生了异常,根据业务进行相关处理
System.out.println("发生了异常");
}
}
});
icount=0; // 重置计数器
}
}
} catch(Exception e){
// TODO 异常处理
e.printStackTrace();
} finally {
consumer.close();
}
以时间戳时间查询消息(偏移量和时间)
版本更新: Kafka 在0.10.1.1 版本增加了时间戳索引文件,因此我们除了直接根据偏移量索引文件查询消息之外,还可以根据时间戳来访问消息
consumer-API 提供了一个offsetsForTimes(Map<TopicPartition, Long> timestampsToSearch)方法,该方法入参为一个Map 对象,Key 为待查询的分区,Value 为待查询的时间戳,该方法会返回时间戳大于等于待查询时间的第一条消息对应的偏移量和时间戳。需要注意的是,若待查询的分区不存在,则该方法会被一直阻塞。
场景:希望从某个时间段开始消费,那们就可以用offsetsForTimes()方法定位到离这个时间最近的第一条消息的偏移量,在查到偏移量之后调用seek(TopicPartition partition, long offset)方法将消费偏移量重置到所查询的偏移量位置,然后调用poll()方法长轮询拉取消息。例如,我们希望从主题“stock-quotation”第0 分区距离当前时间相差12 小时之前的位置开始拉取消息
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("client.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.assign(Arrays.asList(new TopicPartition("test", 0)));
try {
Map<TopicPartition, Long> timestampsToSearch = new HashMap<TopicPartition,Long>();
// 构造待查询的分区
TopicPartition partition = new TopicPartition("stock-quotation", 0);
// 设置查询12 小时之前消息的偏移量
timestampsToSearch.put(partition, (System.currentTimeMillis() - 12 * 3600 * 1000));
// 会返回时间大于等于查找时间的第一个偏移量
Map<TopicPartition, OffsetAndTimestamp> offsetMap = consumer.offsetsForTimes (timestampsToSearch);
OffsetAndTimestamp offsetTimestamp = null;
// 这里依然用for 轮询,当然由于本例是查询的一个分区,因此也可以用if 处理
for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : offsetMap.entrySet()) {
// 若查询时间大于时间戳索引文件中最大记录索引时间,
// 此时value 为空,即待查询时间点之后没有新消息生成
offsetTimestamp = entry.getValue();
if (null != offsetTimestamp) {
// 重置消费起始偏移量
consumer.seek(partition, entry.getValue().offset());
}
}
while (true) {
// 等待拉取消息
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String, String> record : records){
// 简单打印出消息内容
System.out.printf("partition = %d, offset = %d,key= %s value = %s%n", record.partition(), record.offset(), record.key(),record.value());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
consumer.close();
}
以kafka消费速度进行控制
提供 pause(Collection<TopicPartition> partitions)和resume(Collection<TopicPartition>
partitions)方法,分别用来暂停某些分区在拉取操作时返回数据给客户端和恢复某些分区向客户端返回数据操作。通过这两个方法可以对消费速度加以控制,结合业务使用。