La invocación de operaciones no bloqueantes secuencialmente mientras que el consumo de un flujo incluyendo reintentos

Rajesh J Advani:

Así que mi caso de uso es consumir mensajes de Kafka en una aplicación de primavera Webflux al programar en el estilo reactiva utilizando Project reactor, y para realizar una operación no bloqueante para cada mensaje en el mismo orden en que se recibieron los mensajes de Kafka. El sistema también debe ser capaz de recuperarse por sí sola.

Aquí está el fragmento de código que está configurado para consumir a partir de:

    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();

Como se puede ver, lo que hago es: llevar el mensaje de Kafka, transformarlo en un objeto destinado a un nuevo destino, a continuación, enviar a su destino, y luego reconocer el desplazamiento a marcar el mensaje como consumidos y procesados. Es fundamental reconocer el desplazamiento en el mismo orden que los mensajes que están siendo consumidos de Kafka para que no nos movemos el desplazamiento más allá de los mensajes que no se han procesado completamente (incluyendo el envío de algunos datos al destino). Por lo tanto estoy usando un flatMapSequentialpara asegurar esto.

Por simplicidad asumamos que el transformToOutputFormat()método es transformar una identidad.

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

El performAction()método tiene que hacer algo por la red, por ejemplo llamar a una API REST HTTP. Así las API apropiadas vuelven a Mono, lo que significa que las necesidades de la cadena para ser suscrito. Además, necesito el ReceiverRecordser devuelto por este método de manera que el desplazamiento puede ser reconocido en el operador flatMapSequential () anterior. Porque necesito el Mono suscrito, estoy usando flatMapSequentialanteriormente. Si no es así, podría haber usado una mapvez.

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));

Tengo dos necesidades conflictivas en este método: 1. Suscribirse a la cadena que hace la llamada HTTP 2. Retorno del ReceiverRecord

El uso de un flatMap () significa mi tipo de retorno cambia a un mono. Usando doOnNext () en el mismo lugar retendría la ReceiverRecord en la cadena, pero no permitiría la respuesta HttpClient a ser suscritas a automáticamente.

No puedo añadir .subscribe()después asString(), porque quiero esperar hasta que la respuesta HTTP se recibe por completo antes de que el desplazamiento se reconoció.

No puedo usar .block()cualquiera ya que se ejecuta en un hilo paralelo.

Como resultado, tengo que engañar y devolver el recordobjeto del ámbito de aplicación del método.

La otra cosa es que en un interior de reintento performActionse cambia hilos. Desde flatMapSequential () suscribe con entusiasmo a cada mono en el flujo externo, esto significa que, si bien el reconocimiento de las compensaciones se puede garantizar con el fin, no podemos garantizar que la llamada HTTP en performActionse llevará a cabo en el mismo orden.

Así que tengo dos preguntas.

  1. ¿Es posible devolver recordde forma natural en lugar de devolver el objeto ámbito de método?
  2. ¿Es posible para asegurar que tanto la llamada HTTP, así como el reconocimiento de desplazamiento se realizan en el mismo orden que los mensajes para los que se están produciendo estas operaciones?
Rajesh J Advani:

Aquí está la solución que he llegado con.

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();

En lugar de utilizar flatMapSequential para suscribirse a la PerformAction Mono y preservar la secuencia, lo que he hecho en su lugar se retrasa la solicitud de más mensajes de Kafka el receptor hasta que se realiza la acción. Esto permite que el procesamiento de una en una sola vez que necesito.

Como resultado, PerformAction no necesita volver a Mono de ReceiverRecord. También he simplificado a la siguiente:

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));
}

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=138191&siteId=1
Recomendado
Clasificación