一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情
定义
kafka位移重放即重新设置消费者的偏移量
场景
- 历史消息需要重新消费
- kafka数据迁移
策略
位移维度
Earliest
把位移调整到当前最早位移处
Earliest 策略表示将位移调整到主题当前最早位移处。这个最早位移不⼀定就是 0,因为在⽣ 产环境中,很久远的消息会被 Kafka ⾃动删除,所以当前最早位移很可能是⼀个⼤于 0 的值。如 果你想要重新消费主题的所有消息,那么可以使⽤ Earliest 策略。
Latest
把位移调整到当前最新位移处
Latest 策略表示把位移重设成最新末端位移。如果你总共向某个主题发送了 15 条消息,那么 最新末端位移就是 15。如果你想跳过所有历史消息,打算从最新的消息处开始消费的话,可以使 ⽤ Latest 策略
Current
把位移调整到当前最新提交位移处
Current 策略表示将位移调整成消费者当前提交的最新位移。有时候你可能会碰到这样的场 景:你修改了消费者程序代码,并重启了消费者,结果发现代码有问题,你需要回滚之前的代码 变更,同时也要把位移重设到消费者重启时的位置,那么,Current 策略就可以帮你实现这个功 能。
Specified-Offset
把位移调整到指定位移处
Specified-Offset 策略则是⽐较通⽤的策略,表示消费者把位移值调整到你指定的位移处。这 个策略的典型使⽤场景是,消费者程序在处理某条错误消息时,你可以⼿动地“跳过”此消息的处 理。在实际使⽤过程中,可能会出现 corrupted 消息⽆法被消费的情形,此时消费者程序会抛出 异常,⽆法继续⼯作。⼀旦碰到这个问题,你就可以尝试使⽤ Specified-Offset 策略来规避。
Shift-By-N
把位移调整到当前位移+N处(N可以是负值)
如果说 Specified-Offset 策略要求你指定位移的绝对数值的话,那么 Shift-By-N 策略指定的就 是位移的相对数值,即你给出要跳过的⼀段消息的距离即可。这⾥的“跳”是双向的,你既可以向 前“跳”,也可以向后“跳”。⽐如,你想把位移重设成当前位移的前 100 条位移处,此时你需要指定 N 为 -100
时间维度
DateTime
把位移调整到大于给定时间的最小位移处
DateTime 允许你指定⼀个时间,然后将位移重置到该时间之后的最早位移处。常⻅的使⽤场 景是,你想重新消费昨天的数据,那么你可以使⽤该策略重设位移到昨天 0 点。
Duration
把位移调整到距离当前时间指定间隔的位移处
Duration 策略则是指给定相对的时间间隔,然后将位移调整到距离当前给定时间间隔的位移 处,具体格式是 PnDTnHnMnS。如果你熟悉 Java 8 引⼊的 Duration 类的话,你应该不会对这个 格式感到陌⽣。它就是⼀个符合 ISO-8601 规范的 Duration 格式,以字⺟ P 开头,后⾯由 4 部分 组成,即 D、H、M 和 S,分别表示天、⼩时、分钟和秒。举个例⼦,如果你想将位移调回到 15 分钟前,那么你就可以指定 PT0H15M0S。
操作
Api
KafkaConsumer 的 seek ⽅法,或者是它的变种⽅法 seekToBeginning 和 seekToEnd。
package org.apache.kafka.clients.consumer;
.....
public class KafkaConsumer implements Consumer {
.....
@Override public void seek(TopicPartition partition, long offset) {
....
}
public void seekToBeginning(Collection partitions) {
....
}
public void seekToEnd(Collection partitions) {
....
}
....
}
实现实例
Earliest实现
Properties consumerProperties = new Properties();
......
String topic = "test"; // 要重设位移的 Kafka 主题
try (final KafkaConsumer consumer =
new KafkaConsumer<>(consumerProperties)) {
consumer.subscribe(Collections.singleton(topic));
consumer.poll(0);
consumer.seekToBeginning( consumer.partitionsFor(topic).stream().map(partitionInfo ->
new TopicPartition(topic, partitionInfo.partition()))
.collect(Collectors.toList()));
}
Latest实现
consumer.seekToEnd(
consumer.partitionsFor(topic).stream().map(partitionInfo ->
new TopicPartition(topic, partitionInfo.partition()))
.collect(Collectors.toList()));
Current实现
consumer.partitionsFor(topic).stream().map(info ->
new TopicPartition(topic, info.partition())) .forEach(tp -> {
long committedOffset = consumer.committed(tp).offset();
consumer.seek(tp, committedOffset); });
Specified-Offset实现
long targetOffset = 1234L;
for (PartitionInfo info : consumer.partitionsFor(topic)) {
TopicPartition tp = new TopicPartition(topic, info.partition());
consumer.seek(tp, targetOffset);
}
Shift-By-N 实现
for (PartitionInfo info : consumer.partitionsFor(topic)) {
// 假设向前跳 123 条消息
TopicPartition tp = new TopicPartition(topic, info.partition());
long targetOffset = consumer.committed(tp).offset() + 123L;
consumer.seek(tp, targetOffset);
}
datatime 实现
long ts = LocalDateTime.of(
2020, 7, 20, 20, 0).toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
Map timeToSearch = consumer.partitionsFor(topic).stream().map(info ->
new TopicPartition(topic, info.partition()))
.collect(Collectors.toMap(Function.identity(), tp -> ts));
for (Map.Entry entry : consumer.offsetsForTimes(timeToSearch).entrySet()) {
consumer.seek(entry.getKey(), entry.getValue().offset());
}
Duration 实现
Map timeToSearch =
consumer.partitionsFor(topic).stream()
.map(info -> new TopicPartition(topic, info.partition())) .collect(Collectors.toMap(Function.identity(), tp ->
System.currentTimeMillis() - 30 * 1000 * 60));
for (Map.Entry entry :
consumer.offsetsForTimes(timeToSearch).entrySet()) {
consumer.seek(entry.getKey(), entry.getValue().offset());
}
结束
需要交流学习可以关注公众号【温故知新之java】,互相学习,一起进步。