陪你一起学kafka(九)——位移重放

671 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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】,互相学习,一起进步。