Monday, May 1, 2023
HomeProgrammingIntroduction to Reactive Kafka

Introduction to Reactive Kafka


Within the earlier chapter, we took a glance into the Spring Cloud Stream binders for Kafka. On this chapter, we’ll introduce ourselves with “reactive” structure sample and mess around with Reactive Kafka.

Often conventional functions usually take care of blocking calls, or let’s say synchronous calls which implies if we wish to entry a selected entity or data from a system the place a lot of the threads being busy, then the applying would block the brand new one or wait till the earlier threads full processing its requests. Now whereas dealing with massive stream of knowledge, this needs to be processed with large pace and agility. That’s when software program builders realized that they would want some sort of multi-threaded surroundings which handles asynchronous and non-blocking calls to make the most effective use of processing information streams.

Overview of Reactive Programming Paradigm

Principally, a Stream of knowledge is a sequential data or file that’s transferred from one system to different. They’re normally processed as FIFO(First-In-First-Out) sample. Now the identical blocking methodology of knowledge streaming usually prohibits a system to course of real-time information whereas we stream our information. Thus, a bunch of outstanding builders steadily realized that they would want an method to construct a “reactive” techniques structure that will ease the processing of knowledge whereas streaming. Therefore, they signed a manifesto, popularly often known as the Reactive Manifesto.

The authors of the manifesto said {that a} reactive system have to be an asynchronous software program that offers with producers who’ve a single duty to ship messages to shoppers. They described the next options to bear in mind:

  • Responsive: Reactive techniques have to be quick and responsive in order that they will present constant prime quality of service.
  • Resilient: Reactive techniques must be designed to anticipate system failures. Thus, they need to be responsive by replication and isolation.
  • Elastic: Reactive techniques have to be adaptive to shard or replicate elements primarily based upon their requirement. They need to use predictive scaling to anticipate sudden ups and downs of their infrastructure.
  • Message-driven: Since all of the elements in a reactive system are imagined to be loosely coupled, they need to talk throughout their boundaries by asynchronously exchanging messages.

In a traditional MVC utility, each time a request reaches the server, a servlet thread is being created and delegated to employee threads to carry out numerous operations like I/O, database transactions, and many others. Whereas the employee threads are busy finishing their processes, the servlet threads enters right into a ready state as a consequence of which the calls stay blocked. That is termed as blocking or synchronous course of.

In case of a non-blocking system, all of the incoming requests are accompanied by an occasion handler and a callback. The request thread delegates the incoming request to a thread pool that manages a reasonably small variety of threads. After that the thread pool delegates the request to its handler perform and will get out there to course of the subsequent incoming requests from the request thread.

When the handler perform completes its course of, one of many threads from the pool fetches the response and passes it to the callback perform. Thus the threads in a non-blocking system by no means go into the ready state. This in flip will increase the productiveness and the efficiency of the identical utility.

Non-Blocking Request

We frequently come throughout the time period “backpressure” whereas working with reactive code. It’s an analogy derived from fluid dynamics which accurately means the resistance or power that opposes the specified circulate of knowledge. In Reactive Streams, backpressure defines the mechanism to control the info transmission throughout streams.

Idea of Mono/Flux

Spring launched a Multi-Occasion Loop mannequin to allow a reactive stack often known as WebFlux. It’s a absolutely non-blocking and annotation-based net framework constructed on Challenge Reactor which permits constructing reactive net functions on the HTTP layer. It supplies help for common inbuilt severs like Netty, Undertow, and Servlet 3.1 containers.

Earlier than we get began with Spring Webflux, we should accustom ourselves to 2 of the publishers that are getting used closely within the context of Webflux:

  • Mono: A Writer that emits 0 or 1 aspect.

    Mono<String> mono = Mono.simply("David");
    Mono<Object> monoEmpty = Mono.empty();
    Mono<Object> monoError = Mono.error(new Exception());
    
  • Flux: A Writer that emits 0 to N components which might preserve emitting components endlessly. It returns a sequence of components and sends a notification when it has accomplished returning all its components.

    Flux<Integer> flux = Flux.simply(1, 2, 3, 4);
    Flux<String> fluxString = Flux.fromArray(new String[]{"A", "B", "C"});
    Flux<String> fluxIterable = Flux.fromIterable(Arrays.asList("A", "B", "C"));
    Flux<Integer> fluxRange = Flux.vary(2, 5);
    Flux<Lengthy> fluxLong = Flux.interval(Length.ofSeconds(10));
    
    // To Stream information and name subscribe methodology
    Checklist<String> dataStream = new ArrayList<>();
    Flux.simply("X", "Y", "Z")
        .log()
        .subscribe(dataStream::add);
    

    As soon as the stream of knowledge is created, it must be subscribed to so it begins emitting components. The information gained’t circulate or be processed till the subscribe() methodology is known as. Additionally by utilizing the .log() methodology above, we will hint and observe all of the stream indicators. The occasions are logged into the console.

    So we now have spoken quite a bit about Reactive Streams and its programming paradigm. Now let’s begin with the implementation. So to start with, let’s initialize the Spring Boot utility utilizing Spring Initializr:

    Spring Initializr Reactive Kafka

We’ve got added the Spring Reactive Net dependency, Spring for Apache Kafka so as to add libraries or modules for Kafka and Lombok. Moreover, we have to add maven dependency for Challenge Reactor for Kafka.

<dependency>
    <groupId>io.projectreactor.kafka</groupId>
	<artifactId>reactor-kafka</artifactId>
	<model>1.3.11</model>
</dependency>

Lastly, we have to decide the final pending service from Steve and Jane’s e-commerce app, i.e., Courier Service.

Microservice E-commerce Event-Driven app

Reactive Pipeline with Kafka Supply

Messages saved in Kafka matter might be consumed utilizing the reactive receiver org.springframework.kafka.core.reactive.ReactiveKafkaConsumerTemplate. The ReactiveKafkaConsumerTemplate is created with an occasion of receiver configuration choices reactor.kafka.receiver.ReceiverOptions. Therefore, we’ll first create a ReactiveKafkaConsumerTemplate bean as a shopper configuration.

@Configuration
public class ReactiveKafkaConsumerConfig {
    @Bean
    public ReceiverOptions<String, Order> kafkaReceiverOptions(
            @Worth(worth = "${spring.kafka.shopper.matter}") String matter,
            KafkaProperties kafkaProperties) {
        ReceiverOptions<String, Order> basicReceiverOptions = ReceiverOptions.create(
                kafkaProperties.buildConsumerProperties());
        return basicReceiverOptions.subscription(Collections.singletonList(matter));
    }

    @Bean
    public ReactiveKafkaConsumerTemplate<String, Order> reactiveKafkaConsumerTemplate(
            ReceiverOptions<String, Order> kafkaReceiverOptions) {
        return new ReactiveKafkaConsumerTemplate<>(kafkaReceiverOptions);
    }
}

We will then outline the Kafka configuration as a part of utility.yml:

spring:
  kafka:
    bootstrap-servers: localhost:9092
    shopper:
      matter: order-warehouse
      group-id: reactive-kafka
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.widespread.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.help.serializer.JsonDeserializer
      properties:
        spring:
          json:
            use:
              kind:
                headers: false
            worth:
              default:
                kind: com.stackabuse.kafkaspringbootreactive.mannequin.Order
    properties:
      spring:
        json:
          trusted:
            packages: com.stackabuse.kafkaspringbootreactive.mannequin

Lastly, we’re going to outline a service layer to devour the info utilizing ReactiveKafkaConsumerTemplate:

@Slf4j
@Service
public class ReactiveConsumerService {

    @Autowired
    non-public ReactiveKafkaConsumerTemplate<String, Order> reactiveKafkaConsumerTemplate;

    non-public Flux<Order> consumeAnyOrders() {

        return reactiveKafkaConsumerTemplate
                .receiveAutoAck()
                .delayElements(Length.ofSeconds(5)) //BACKPRESSURE
                .doOnNext(consumerRecord ->
                        log.data("Acquired an Order with key={}, worth={} from matter={} at offset={}",
                        consumerRecord.key(),
                        consumerRecord.worth(),
                        consumerRecord.matter(),
                        consumerRecord.offset()))
                .map(ConsumerRecord::worth)
                .doOnNext(order -> log.data("Processing Order with particulars {}={} to e-book a courier",
                        Order.class.getSimpleName(), order))
                .doOnError(throwable -> log.error("Some error occurred whereas consuming an order as a consequence of: {}",
                        throwable.getMessage()));
    }

    @PostConstruct
    public void startConsuming() {
        // We have to set off the consumption course of. This will both be a PostConstruct,
        // CommandLineRunner, ApplicationContext or ApplicationReadyEvent
        consumeAnyOrders().subscribe();
    }
}

Reactive Pipeline with Kafka Sink

Outbound messages are despatched to Kafka utilizing org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate. That is created with an occasion of sender configuration choices reactor.kafka.sender.SenderOptions. Therefore, we’ll create a ReactiveKafkaProducerTemplate bean as a producer config:

@Configuration
public class ReactiveKafkaProducerConfig {

    @Bean
    public ReactiveKafkaProducerTemplate<String, Order> reactiveKafkaProducerTemplate(
            KafkaProperties properties) {
        Map<String, Object> props = properties.buildProducerProperties();
        return new ReactiveKafkaProducerTemplate<>(SenderOptions.create(props));
    }
}

Then we have to outline Producer configuration as a part of utility.yml:

spring:
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      matter: order-warehouse
      key-serializer: org.apache.kafka.widespread.serialization.StringSerializer
      value-serializer: org.springframework.kafka.help.serializer.JsonSerializer

Then we’ll outline a service layer to ship outbound messages to Kafka matter in a reactive method:

@Slf4j
@Service
public class ReactiveProducerService {

    @Autowired
    non-public ReactiveKafkaProducerTemplate<String, Order> reactiveKafkaProducerTemplate;

    @Worth(worth = "${spring.kafka.producer.matter}")
    non-public String matter;

    public void ship(Order order) {
        log.data("Document despatched to matter={}, {}={},", matter, Order.class.getSimpleName(), order);
        reactiveKafkaProducerTemplate.ship(matter, order)
                .doOnSuccess(senderResult -> log.data("Despatched Order: {} at offset : {}",
                                                      order,
                                                      senderResult.recordMetadata().offset()))
                .doOnError(throwable -> log.error("Some error occurred whereas consuming an order as a consequence of: {}",
                                                  throwable.getMessage()))
                .subscribe();
    }
}

After defining a primary sink and supply, now we have to modify our present code to course of the orders and e-book a courier firm for the cargo. So first we have to outline a WebClient configuration to utilize it to carry out REST API calls to e-book the cargo.

@Configuration
public class ApplicationConfiguration {

    @Bean
    public WebClient getWebClientBuilder() throws SSLException {

        SslContext sslContext = SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .construct();

        HttpClient httpClient = HttpClient.create()
                .possibility(ChannelOption.CONNECT_TIMEOUT_MILLIS, 120 * 1000)
                .doOnConnected(connection -> connection.addHandlerLast(
                    new ReadTimeoutHandler(120 * 1000, TimeUnit.MILLISECONDS)))
                .wiretap("reactor.netty.http.consumer.HttpClient",
                         LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL)
                .followRedirect(true)
                .safe(sslContextSpec -> sslContextSpec.sslContext(sslContext));

        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .exchangeStrategies(ExchangeStrategies.builder()
                        .codecs(configurer -> configurer.defaultCodecs()
                                .maxInMemorySize(50 * 1024 * 1024))
                        .construct())
                .construct();
    }
}

Subsequent we’ll mock a WebClient request to e-book a cargo and return the order again with standing as COURIER_BOOKED:

non-public Mono<Order> bookDHLShipment(Order order) {

   // To mock the Courier reserving API
   order.setStatus(OrderStatus.COURIER_BOOKED);
   order.setCourierCompany("DHL");

   return webClient
           .submit()
           .uri("https://dhl-shipment-api/dummy") // Dummy API URL to mock cargo reserving
           .contentType(MediaType.APPLICATION_JSON)
           .physique(BodyInserters.fromValue(order))
           .retrieve()
           .bodyToMono(Order.class)
           .log()
           .onErrorReturn(order);
}

Then we have to replace our shopper logic to devour and produce the information again concurrently as and when the order is produced within the matter with the standing READY_TO_PICK:

non-public Flux<Order> consumeAnyOrders() {

   return reactiveKafkaConsumerTemplate
           .receiveAutoAck()
           .delayElements(Length.ofSeconds(5)) //BACKPRESSURE
           .doOnNext(consumerRecord ->
                   log.data("Acquired an Order with key={}, worth={} from matter={} at offset={}",
                   consumerRecord.key(),
                   consumerRecord.worth(),
                   consumerRecord.matter(),
                   consumerRecord.offset()))
           .map(ConsumerRecord::worth)
           .filter(o -> o.getStatus().equals(OrderStatus.READY_TO_PICK))
           .doOnNext(order -> log.data("Processing Order with particulars {}={} to e-book a courier",
                   Order.class.getSimpleName(), order))
           .flatMap(this::bookDHLShipment)
           .doOnNext(reactiveProducerService::ship)
           .doOnNext(order -> log.data("Order despatched {}={} after reserving the courier",
                   Order.class.getSimpleName(), order))
           .doOnError(throwable -> log.error("Some error occurred whereas consuming an order as a consequence of: {}",
                        throwable.getMessage()));
}

At-most-once Supply Mannequin

Spring for Kafka helps disabling of auto-commits to keep away from re-delivery of information. The configurations additionally supplies auto-offset-reset which might be set to newest in order that it could devour solely new information. However with enabling this setting, a lot of the information could possibly be misplaced or not consumed each time the applying restarts or fails. Thus, ReactiveKafkaConsumerTemplate helps receiveAtMostOnce() methodology to devour information with at-most-once semantics with a configurable variety of records-per-partition which may be misplaced if the applying fails or crashes. Offsets are dedicated synchronously earlier than the corresponding file is dispatched and the information are assured to not be re-delivered even when the consuming utility fails. However some information is probably not processed if an utility fails after the commit earlier than the information could possibly be processed.

non-public Flux<Order> consumeAnyOrders() {

   return reactiveKafkaConsumerTemplate
           .receiveAtMostOnce()
           .delayElements(Length.ofSeconds(5)) //BACKPRESSURE
           .doOnNext(consumerRecord ->
                   log.data("Acquired an Order with key={}, worth={} from matter={} at offset={}",
                   consumerRecord.key(),
                   consumerRecord.worth(),
                   consumerRecord.matter(),
                   consumerRecord.offset()))
           .map(ConsumerRecord::worth)
           .filter(o -> o.getStatus().equals(OrderStatus.READY_TO_PICK))
           .doOnNext(order -> log.data("Processing Order with particulars {}={} to e-book a courier",
                   Order.class.getSimpleName(), order))
           .flatMap(this::bookDHLShipment)
           .doOnNext(reactiveProducerService::ship)
           .doOnNext(order -> log.data("Order despatched {}={} after reserving the courier",
                   Order.class.getSimpleName(), order))
           .doOnError(throwable -> log.error("Some error occurred whereas consuming an order as a consequence of: {}",
                        throwable.getMessage()));
}

This mode is bit costly since every file is dedicated individually and information are usually not delivered till the commit operation succeeds. Therefore, this selection must be used with warning.

Concurrent Message Processing with Partition-based Ordering

Generally we have to devour the messages from the Kafka matter, course of it in a number of threads and at last retailer the ends in one other matter. ReactiveKafkaConsumerTemplate helps partition-based ordering the place messages are grouped by partition to ensure ordering throughout processing of these messages and commit operations. Messages from every of those partitions are processed on a single thread.

non-public void concurrentProcessing() {

   Scheduler scheduler = Schedulers.newElastic("order-warehouse", 60, true);

   reactiveKafkaConsumerTemplate
           .obtain()
           .groupBy(m -> m.receiverOffset().topicPartition())
           .flatMap(partitionFlux -> partitionFlux
                   .publishOn(scheduler)
                   .doOnNext(receiverRecord ->
                           log.data("Acquired an Order with key={}, worth={} from matter={} at offset={}",
                                   receiverRecord.key(),
                                   receiverRecord.worth(),
                                   receiverRecord.matter(),
                                   receiverRecord.offset()))
                   .pattern(Length.ofMillis(5000))
                   .concatMap(receiverRecord -> receiverRecord.receiverOffset().commit()));
}

We’re utilizing concatMap on the finish to commit the ultimate messages in a correct sequence.

Precisely-once Supply Mannequin

In Chapter 9, we took a deep-dive and understood the need for transactional help whereas processing the information to realize the exactly-once-delivery mannequin. In the same strains, ReactiveKafkaConsumerTemplate helps receiveExactlyOnce() methodology the place we will go the configurations required by Kafka to help transactional characteristic. We have to outline a SenderOptions and go the TransactionManager to carry out this operation.

non-public Flux<Order> consumeAnyOrders() {

   // For precisely as soon as processing of messages
   Map<String, Object> props = properties.buildProducerProperties();
   SenderOptions<Object, Object> senderOptions = SenderOptions.create(props)
           .producerProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "courier-booking-txn")
           .producerProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);

   return reactiveKafkaConsumerTemplate
           .receiveExactlyOnce(KafkaSender.create(senderOptions).transactionManager())
           .delayElements(Length.ofSeconds(5)) //BACKPRESSURE
           .flatMap(r -> r)
           .doOnNext(consumerRecord ->
                   log.data("Acquired an Order with key={}, worth={} from matter={} at offset={}",
                   consumerRecord.key(),
                   consumerRecord.worth(),
                   consumerRecord.matter(),
                   consumerRecord.offset()))
           .map(ConsumerRecord::worth)
           .filter(o -> o.getStatus().equals(OrderStatus.READY_TO_PICK))
           .doOnNext(order -> log.data("Processing Order with particulars {}={} to e-book a courier",
                   Order.class.getSimpleName(), order))
           .flatMap(this::bookDHLShipment)
           .doOnNext(reactiveProducerService::ship)
           .doOnNext(order -> log.data("Order despatched {}={} after reserving the courier",
                   Order.class.getSimpleName(), order))
           .doOnError(throwable -> log.error("Some error occurred whereas consuming an order as a consequence of: {}",
                   throwable.getMessage()));
}

Conclusion

So on this chapter, we took a glance into the “reactive” world and the way in which issues work. One of the crucial essential factor to bear in mind in case of reactive functions are, not one of the strategies or features might be blocking in nature. Thus, we now have used Reactive Kafka and tried to realize the same sort of information streaming that we now have achieved in our earlier chapters.

The general implementation for this chapter might be present in GitHub.

Now, it’s time for us to have a good time as we had achieved our aim lastly! The aim was to assist Steve and Jane convert their conventional monolithic utility to a full-fledge information streaming occasion pushed microservice. We’ve got lined nearly all of the microservices that had been depicted of their architectural diagram.

Microservice E-commerce Event-Driven app

Though, a few of this implementation won’t be enterprise-grade prepared to be consumed within the manufacturing surroundings however nonetheless we may obtain quite a bit with numerous methods we now have carried out. This brings us to an finish to our guides about Kafka.

Within the subsequent upcoming chapters, we’re going to look into numerous guided tasks, the place we’ll talk about about numerous use-cases and the way we will obtain that utilizing Kafka or event-driven techniques.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments