【问题标题】:How to manually commit offsets on Kafka source module on getting acknowledgment from Kafka sink module in Spring XD?如何在 Spring XD 中从 Kafka 接收器模块获取确认时手动提交 Kafka 源模块中的偏移量?
【发布时间】:2016-12-26 20:11:36
【问题描述】:

在 XD 流中,消息通过源模块从 Kafka 主题消费,然后发送到接收器 Kafka 模块。开发自定义源和接收器 Kafka 模块背后的原因是,我只想在成功发送消息时从下游接收器模块得到确认时更新源模块的偏移量。

我正在使用 Spring Integration Kafka 2.0.1.RELEASE 和 Spring Kafka 1.0.3.RELEASE 以及 Kafka 0.10.0.0 环境中的主题。我尝试了以下方法:

源模块配置:

@Configuration
public class ModuleConfiguration {

    @Value("${topic}")
    private String topic;

    @Value("${brokerList}")
    private String brokerAddress;

    @Bean
    public SubscribableChannel output() {
        DirectChannel output = new DirectChannel();
        return output;
    }

    @Autowired
    TopicPartitionInitialOffset topicPartition;

    @Bean
    public TopicPartitionInitialOffset topicPartition(){
        return new TopicPartitionInitialOffset(this.topic, 0, (long) 0);    
    }

    @Bean
    public KafkaMessageListenerContainer<String, String> container() throws Exception {
        ContainerProperties containerProps = new ContainerProperties(topicPartition);
        containerProps.setAckMode(AckMode.MANUAL);
        KafkaMessageListenerContainer<String, String> kafkaMessageListenerContainer = new KafkaMessageListenerContainer<>(consumerFactory(),containerProps);
        return kafkaMessageListenerContainer;
    }
    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.brokerAddress);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-consumer-group");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 15000);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        DefaultKafkaConsumerFactory<String,String> consumerFactory =  new DefaultKafkaConsumerFactory<>(props);
        return consumerFactory;
    }
}

源模块:InboundKafkaMessageDrivenAdapter

@MessageEndpoint
@Import(ModuleConfiguration.class)
public class InboundKafkaMessageDrivenAdapter {

    @Autowired
    KafkaMessageListenerContainer<String, String> container;

    @Autowired
    SubscribableChannel output;

    @Bean
    public KafkaMessageDrivenChannelAdapter<String, String> adapter(KafkaMessageListenerContainer<String, String> container) {
        KafkaMessageDrivenChannelAdapter<String, String> kafkaMessageDrivenChannelAdapter = new KafkaMessageDrivenChannelAdapter<>(container);
        kafkaMessageDrivenChannelAdapter.setOutputChannel(output);
        return kafkaMessageDrivenChannelAdapter;
    }
}

接收器模块:配置

@Configuration
@EnableIntegration
public class ModuleConfiguration {

    @Value("${topic}")
    private String topic;

    @Value("${brokerList}")
    private String brokerAddress;

    @Bean
    public KafkaProducerMessageHandler<String,String> handler() throws Exception {
        KafkaProducerMessageHandler<String, String> handler = new KafkaProducerMessageHandler<>(kafkaTemplate());
        handler.setTopicExpression(new LiteralExpression(this.topic));
        return handler;
    }

    @Bean
    public SubscribableChannel input() {
        return new DirectChannel();
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.brokerAddress);
        props.put(ProducerConfig.RETRIES_CONFIG, 0);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        return new DefaultKafkaProducerFactory<>(props);
    }
}

Sink 模块:SinkActivator

@Import(ModuleConfiguration.class)
@MessageEndpoint
public class SinkActivator {

    @Autowired
    KafkaProducerMessageHandler<String,String> handler;

    @Autowired
    SubscribableChannel input;

    @ServiceActivator(inputChannel = "input")
    public void sendMessage(Message<?> msg) throws Exception{
            Acknowledgment acknowledgment = msg.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class);
            handler.handleMessage(msg);
            acknowledgment.acknowledge();
            }
}

源成功接收消息并将它们发送到接收器,但是当我尝试在接收器中获取确认时:

确认确认 = msg.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class);

抛出以下异常:

原因:java.lang.IllegalArgumentException:为标头“kafka_acknowledgment”指定的类型不正确。预期 [interface org.springframework.kafka.support.Acknowledgment] 但实际类型是 [class org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer$ConsumerAcknowledgment]

在 spring-integration-kafka-2.0.1.RELEASE 的源代码中,当 AckMode=MANUAL 时,将 kafka_acknowledgment 标头添加到消息中,但类型是 ConsumerAcknowldgment 的内部静态类。

那么我如何从接收器模块获得对源发送消息的确认?

【问题讨论】:

    标签: spring-integration spring-xd spring-kafka


    【解决方案1】:

    除非您使用本地传输,否则您不能这样做,Acknowledgment 是一个“活动”对象,不能通过线路发送到另一个模块。

    如果您使用本地传输,它会工作,但您会遇到类加载器问题,因为每个模块都在其自己的类加载器中运行,并且Acknowledgment 接口是类的不同实例。

    您必须将 spring-integration-kafka 和 spring-kafka 移动到 xd/lib 文件夹,以便从通用类加载器加载类。

    【讨论】:

    • 快速问题:我们使用 xd 和 redis 传输 {default},所以我们部署在 xd 上的模块通过 redis 队列进行通信。这会被视为本地交通吗?我相信是这样,但只是想和你确认一下。
    • 否;本地意味着接收器与源在同一线程上运行 - 您不能通过 Redis 传输“实时”Acknowledgment 对象。确认具有与源消费者相关的状态,因此您不能像那样“远程”确认。并且本地仅适用于单个节点。
    • 还有一个后续问题:那么在我们的流中,您对架构模式有什么建议吗?在 XD 中,Kafka 源模块的流仅在消息从接收器模块成功发送时才会确认。这是为我们 XD 平台上的所有流提供给我们的不可协商的解决方案。我们非常感谢您提出解决方案的建议或解决方法。再次感谢。
    • 确实没有开箱即用的解决方案;您必须向源提供某种后门确认(可能通过另一个 redis 列表)。源必须保留对 ack 对象的引用,并在确认到达时确认它。欢迎使用微服务 :) 您还必须小心多线程 - 您可能会在较早的消息失败时确认稍后的消息,而稍后的偏移量将有效地确认失败的消息。
    • 感谢您的回复!根据您转向微服务的建议,您能否暗示我们将如何利用 Spring Cloud 数据流、Cloud Foundry 或其他 Spring Cloud 功能来实现其源仅在成功接收消息处理时确认的流?我们已经完成了一些基础知识,但我们没有实施经验,因此我们很难将解决方案可视化,因此非常感谢任何架构设计/指导。我相信这个案例对很多企业来说都是重要的案例。感谢您的帮助。
    猜你喜欢
    • 2018-05-05
    • 2020-02-18
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    • 1970-01-01
    相关资源
    最近更新 更多