繁体   English   中英

使用 Spring Cloud Stream 寻找 Kafka 偏移量

[英]Seeking to a Kafka Offset with Spring Cloud Stream

我有一个事件源服务,它侦听 Kafka 主题并将状态保存在关系数据库中。

考虑到适合该服务的恢复策略(即如何在灾难恢复场景中恢复数据库),一种选择是将当前偏移量保存在数据库中,拍摄快照并从快照中恢复。 在这种情况下,服务在以“恢复模式”启动时需要寻找偏移量。

我正在使用 Spring Cloud Stream,并且想知道该框架是否提供了任何寻求偏移的机制?

我意识到恢复的另一种选择是简单地从头开始播放所有事件,但这对于我的某些微服务来说并不是一个理想的选择。

如果您说的是灾难,是什么让您认为您可以向 DB 写入任何内容? 换句话说,您可能最终会在至少一个事件上处理重复数据删除(至少您必须考虑到这一点),如果是这样,那么重复数据删除仍然是您必须处理的事情。

我理解您对重播的担忧(您只是不想从一开始就回复,但您可以存储定期快照,这将确保您拥有相对固定数量的可能需要重新处理/删除重复的事件。

也就是说,Kafka 会维护当前的偏移量,因此您可以依靠 Kafka 的自然事务特性来确保下次启动微服务时,它将从最后一个未处理(成功)的偏移量开始。

您可以使用 KafkaBindingRebalanceListener 接口

@Slf4j
@Component
public class KafkaRebalanceListener implements KafkaBindingRebalanceListener {

    @Value("${config.kafka.topics.offsets:null}")
    private String topicOffsets;

    @Override
    public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer, Collection<TopicPartition> partitions, boolean initial) {
        if (topicOffsets != null && initial) {
            final Optional<Map<TopicPartition, Long>> offsetsOptional = parseOffset(topicOffsets);
            if (offsetsOptional.isPresent()) {
                final Map<TopicPartition, Long> offsetsMap = offsetsOptional.get();
                partitions.forEach(tp -> {
                    if (offsetsMap.containsKey(tp)) {
                        final Long offset = offsetsMap.get(tp);
                        try {
                            log.info("Seek topic {} partition {} to offset {}", tp.topic(), tp.partition(), offset);
                            consumer.seek(tp, offset);
                        } catch (Exception e) {
                            log.error("Unable to set offset {} for topic {} and partition {}", offset, tp.topic(), tp.partition());
                        }
                    }
                });
            }
        }
    }

    private Optional<Map<TopicPartition, Long>> parseOffset(String offsetParam) {
        if (offsetParam == null || offsetParam.isEmpty()) {
            return Optional.empty();
        }

        return Optional.of(Arrays.stream(offsetParam.split(","))
                .flatMap(slice -> {
                    String[] items = slice.split("\\|");
                    String topic = items[0];
                    return Arrays.stream(Arrays.copyOfRange(items, 1, items.length))
                            .map(r -> {
                                String[] record = r.split(":");
                                int partition = Integer.parseInt(record[0]);
                                long offset = Long.parseLong(record[1]);
                                return new AbstractMap.SimpleEntry<>(new TopicPartition(topic, partition), offset);
                            });
                }).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)));
    }
}

config.kafka.topics.offsets字段看起来像这样,但您可以使用任何格式

String topicOffsets = "topic2|1:100|2:120|3:140,topic3|1:1000|2:1200|3:1400";

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM