从包括重试在内的Flux消耗时,顺序调用非阻塞操作

时间:2019-01-10 10:07:36

标签: java apache-kafka reactive-programming spring-webflux project-reactor

因此,我的用例是在使用Project Reactor以响应式编程时,在Spring Webflux应用程序中使用来自Kafka的消息,并对每个消息执行从接收到消息的顺序相同的非阻塞操作卡夫卡该系统还应该能够自行恢复。

以下是设置为从中使用的代码段:

    Flux<ReceiverRecord<Integer, DataDocument>> messages = Flux.defer(() -> {
        KafkaReceiver<Integer, DataDocument> receiver = KafkaReceiver.create(options);
        return receiver.receive();
    });

    messages.map(this::transformToOutputFormat)
            .map(this::performAction)
            .flatMapSequential(receiverRecordMono -> receiverRecordMono)
            .doOnNext(record -> record.receiverOffset().acknowledge())
            .doOnError(error -> logger.error("Error receiving record", error))
            .retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
            .subscribe();

正如您所看到的,我要做的是:从Kafka中获取消息,将其转换为用于新目标的对象,然后将其发送到目标,然后确认偏移量以将消息标记为已使用和已处理。至关重要的是,以与从卡夫卡消费的消息相同的顺序来确认偏移量,以使偏移量不会超出未完全处理的消息(包括向目标发送一些数据)的范围。因此,我正在使用flatMapSequential来确保这一点。

为简单起见,我们假设transformToOutputFormat()方法是身份转换。

public ReceiverRecord<Integer, DataDocument> transformToOutputFormat(ReceiverRecord<Integer, DataDocument> record) {
    return record;
}

performAction()方法需要通过网络执行某些操作,例如调用HTTP REST API。因此,适当的API返回Mono,这意味着需要订阅链。另外,我需要此方法返回ReceiverRecord,以便可以在上面的flatMapSequential()运算符中确认偏移量。因为我需要订阅Mono,所以我在上面使用flatMapSequential。如果没有,我本可以使用map

public Mono<ReceiverRecord<Integer, DataDocument>> performAction(ReceiverRecord<Integer, DataDocument> record) {
    return Mono.just(record)
            .flatMap(receiverRecord ->
                    HttpClient.create()
                            .port(3000)
                            .get()
                            .uri("/makeCall?data=" + receiverRecord.value().getData())
                            .responseContent()
                            .aggregate()
                            .asString()
            )
            .retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
            .then(Mono.just(record));

我在此方法中有两个矛盾的需求: 1.订阅进行HTTP调用的链 2.返回ReceiverRecord

使用flatMap()意味着我的返回类型更改为Mono。在同一位置使用doOnNext()会将ReceiverRecord保留在链中,但不允许HttpClient响应自动订阅。

我无法在.subscribe()之后添加asString(),因为我想等到HTTP响应完全收到后再确认偏移量。

由于.block()在并行线程上运行,因此我也不能使用。

结果,我需要作弊并从方法范围返回record对象。

另一件事是,在performAction内部重试时,它会切换线程。由于flatMapSequential()渴望订阅外部通量中的每个Mono,因此这意味着尽管可以按顺序保证对偏移量的确认,但我们不能保证performAction中的HTTP调用将以相同的顺序执行。

所以我有两个问题。

  1. 是否有可能以自然方式返回record而不是返回方法范围对象?
  2. 是否可以确保HTTP调用和偏移确认均以与发生这些操作的消息相同的顺序执行?

1 个答案:

答案 0 :(得分:1)

这是我想出的解决方案。

Flux<ReceiverRecord<Integer, DataDocument>> messages = Flux.defer(() -> {
    KafkaReceiver<Integer, DataDocument> receiver = KafkaReceiver.create(options);
    return receiver.receive();
});

messages.map(this::transformToOutputFormat)
        .delayUntil(this::performAction)
        .doOnNext(record -> record.receiverOffset().acknowledge())
        .doOnError(error -> logger.error("Error receiving record", error))
        .retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
        .subscribe();

我没有使用flatMapSequential来订阅performAction Mono并保留序列,而是延迟了从Kafka接收器请求更多消息的请求,直到执行该操作为止。这样就可以一次完成我需要的处理。

因此,performAction不需要返回Mono的ReceiverRecord。我还将其简化为以下内容:

public Mono<String> performAction(ReceiverRecord<Integer, DataDocument> record) {
    HttpClient.create()
        .port(3000)
        .get()
        .uri("/makeCall?data=" + receiverRecord.value().getData())
        .responseContent()
        .aggregate()
        .asString()
        .retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5));
}