How to listen for the right ACK message from Kafka

前端 未结 1 1216
执念已碎
执念已碎 2021-01-24 06:57

I am doing a POC with Spring Boot & Kafka for a transactional project and I have the following doubt:

Scenario: One microservices MSPUB

相关标签:
1条回答
  • 2021-01-24 07:40

    One way to do it is to use a Spring Integration BarrierMessageHandler.

    Here is an example app. Hopefully, it's self-explanatory. Kafka 0.11 or higher is needed...

    @SpringBootApplication
    @RestController
    public class So48349993Application {
    
        private static final Logger logger = LoggerFactory.getLogger(So48349993Application.class);
    
        private static final String TRANSACTION_TOPIC1 = "TRANSACTION_TOPIC1";
    
        private static final String TRANSACTION_RESULT1 = "TRANSACTION_RESULT1";
    
        public static void main(String[] args) {
            SpringApplication.run(So48349993Application.class, args);
        }
    
        private final Exchanger exchanger;
    
        private final KafkaTemplate<String, String> kafkaTemplate;
    
        @Autowired
        public So48349993Application(Exchanger exchanger,
                KafkaTemplate<String, String> kafkaTemplate) {
            this.exchanger = exchanger;
            this.kafkaTemplate = kafkaTemplate;
            kafkaTemplate.setDefaultTopic(TRANSACTION_RESULT1);
        }
    
        @RequestMapping(path = "/foo/{id}/{other}", method = RequestMethod.GET)
        @ResponseBody
        public String foo(@PathVariable String id, @PathVariable String other) {
            logger.info("Controller received: " + other);
            String reply = this.exchanger.exchange(id, other);
            // if reply is null, we timed out
            logger.info("Controller replying: " + reply);
            return reply;
        }
    
        // Client side
    
        @MessagingGateway(defaultRequestChannel = "outbound", defaultReplyTimeout = "10000")
        public interface Exchanger {
    
            @Gateway
            String exchange(@Header(IntegrationMessageHeaderAccessor.CORRELATION_ID) String id,
                    @Payload String out);
    
        }
    
        @Bean
        public IntegrationFlow router() {
            return IntegrationFlows.from("outbound")
                    .routeToRecipients(r -> r
                            .recipient("toKafka")
                            .recipient("barrierChannel"))
                    .get();
        }
    
        @Bean
        public IntegrationFlow outFlow(KafkaTemplate<String, String> kafkaTemplate) {
            return IntegrationFlows.from("toKafka")
                    .handle(Kafka.outboundChannelAdapter(kafkaTemplate).topic(TRANSACTION_TOPIC1))
                    .get();
        }
    
        @Bean
        public IntegrationFlow barrierFlow(BarrierMessageHandler barrier) {
            return IntegrationFlows.from("barrierChannel")
                    .handle(barrier)
                    .transform("payload.get(1)") // payload is list with input/reply
                    .get();
        }
    
        @Bean
        public BarrierMessageHandler barrier() {
            return new BarrierMessageHandler(10_000L);
        }
    
        @KafkaListener(id = "clientReply", topics = TRANSACTION_RESULT1)
        public void result(Message<?> reply) {
            logger.info("Received reply: " + reply.getPayload() + " for id "
                    + reply.getHeaders().get(IntegrationMessageHeaderAccessor.CORRELATION_ID));
            barrier().trigger(reply);
        }
    
        // Server side
    
        @KafkaListener(id = "server", topics = TRANSACTION_TOPIC1)
        public void service(String in,
                @Header(IntegrationMessageHeaderAccessor.CORRELATION_ID) String id) throws InterruptedException {
            logger.info("Service Received " + in);
            Thread.sleep(5_000);
            logger.info("Service Replying to " + in);
            // with spring-kafka 2.0 (and Boot 2), you can return a String and use @SendTo instead of this.
            this.kafkaTemplate.send(new GenericMessage<>("reply for " + in,
                    Collections.singletonMap(IntegrationMessageHeaderAccessor.CORRELATION_ID, id)));
        }
    
        // Provision topics if needed
    
        // provided by Boot in 2.0
        @Bean
        public KafkaAdmin admin() {
            Map<String, Object> config = new HashMap<>();
            config.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
            return new KafkaAdmin(config);
        }
    
        @Bean
        public NewTopic topic1() {
            return new NewTopic(TRANSACTION_TOPIC1, 10, (short) 1);
        }
    
        @Bean
        public NewTopic result1() {
            return new NewTopic(TRANSACTION_RESULT1, 10, (short) 1);
        }
    
    }
    

    Result

    2018-01-20 17:27:54.668  INFO 98522 --- [   server-1-C-1] com.example.So48349993Application        : Service Received foo
    2018-01-20 17:27:55.782  INFO 98522 --- [nio-8080-exec-2] com.example.So48349993Application        : Controller received: bar
    2018-01-20 17:27:55.788  INFO 98522 --- [   server-0-C-1] com.example.So48349993Application        : Service Received bar
    2018-01-20 17:27:59.673  INFO 98522 --- [   server-1-C-1] com.example.So48349993Application        : Service Replying to foo
    2018-01-20 17:27:59.702  INFO 98522 --- [ientReply-1-C-1] com.example.So48349993Application        : Received reply: reply for foo for id 1
    2018-01-20 17:27:59.705  INFO 98522 --- [nio-8080-exec-1] com.example.So48349993Application        : Controller replying: reply for foo
    2018-01-20 17:28:00.792  INFO 98522 --- [   server-0-C-1] com.example.So48349993Application        : Service Replying to bar
    2018-01-20 17:28:00.798  INFO 98522 --- [ientReply-0-C-1] com.example.So48349993Application        : Received reply: reply for bar for id 2
    2018-01-20 17:28:00.800  INFO 98522 --- [nio-8080-exec-2] com.example.So48349993Application        : Controller replying: reply for bar
    

    Pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>so48349993</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>so48349993</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.9.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-integration</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.integration</groupId>
                <artifactId>spring-integration-kafka</artifactId>
                <version>2.3.0.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.kafka</groupId>
                <artifactId>spring-kafka</artifactId>
                <version>1.3.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    

    application.properties

    spring.kafka.consumer.enable-auto-commit=false
    spring.kafka.consumer.auto-offset-reset=earliest
    spring.kafka.listener.concurrency=2
    

    EDIT

    And here is a version that uses Spring Integration on the server side, instead of @KafkaListener...

    @SpringBootApplication
    @RestController
    public class So483499931Application {
    
        private static final Logger logger = LoggerFactory.getLogger(So483499931Application.class);
    
        private static final String TRANSACTION_TOPIC1 = "TRANSACTION_TOPIC3";
    
        private static final String TRANSACTION_RESULT1 = "TRANSACTION_RESULT3";
    
        public static void main(String[] args) {
            SpringApplication.run(So483499931Application.class, args);
        }
    
        private final Exchanger exchanger;
    
        private final KafkaTemplate<String, String> kafkaTemplate;
    
        @Autowired
        public So483499931Application(Exchanger exchanger,
                KafkaTemplate<String, String> kafkaTemplate) {
            this.exchanger = exchanger;
            this.kafkaTemplate = kafkaTemplate;
            kafkaTemplate.setDefaultTopic(TRANSACTION_RESULT1);
        }
    
        @RequestMapping(path = "/foo/{id}/{other}", method = RequestMethod.GET)
        @ResponseBody
        public String foo(@PathVariable String id, @PathVariable String other) {
            logger.info("Controller received: " + other);
            String reply = this.exchanger.exchange(id, other);
            logger.info("Controller replying: " + reply);
            return reply;
        }
    
        // Client side
    
        @MessagingGateway(defaultRequestChannel = "outbound", defaultReplyTimeout = "10000")
        public interface Exchanger {
    
            @Gateway
            String exchange(@Header(IntegrationMessageHeaderAccessor.CORRELATION_ID) String id,
                    @Payload String out);
    
        }
    
        @Bean
        public IntegrationFlow router() {
            return IntegrationFlows.from("outbound")
                    .routeToRecipients(r -> r
                            .recipient("toKafka")
                            .recipient("barrierChannel"))
                    .get();
        }
    
        @Bean
        public IntegrationFlow outFlow(KafkaTemplate<String, String> kafkaTemplate) {
            return IntegrationFlows.from("toKafka")
                    .handle(Kafka.outboundChannelAdapter(kafkaTemplate).topic(TRANSACTION_TOPIC1))
                    .get();
        }
    
        @Bean
        public IntegrationFlow barrierFlow(BarrierMessageHandler barrier) {
            return IntegrationFlows.from("barrierChannel")
                    .handle(barrier)
                    .transform("payload.get(1)") // payload is list with input/reply
                    .get();
        }
    
        @Bean
        public BarrierMessageHandler barrier() {
            return new BarrierMessageHandler(10_000L);
        }
    
        @KafkaListener(id = "clientReply", topics = TRANSACTION_RESULT1)
        public void result(Message<?> reply) {
            logger.info("Received reply: " + reply.getPayload() + " for id "
                    + reply.getHeaders().get(IntegrationMessageHeaderAccessor.CORRELATION_ID));
            barrier().trigger(reply);
        }
    
        // Server side
    
        @Bean
        public IntegrationFlow server(ConsumerFactory<String, String> consumerFactory,
                KafkaTemplate<String, String> kafkaTemplate) {
            return IntegrationFlows.from(Kafka.messageDrivenChannelAdapter(consumerFactory, TRANSACTION_TOPIC1))
                .handle("so483499931Application", "service")
                .handle(Kafka.outboundChannelAdapter(kafkaTemplate).topic(TRANSACTION_RESULT1))
                .get();
        }
    
        public String service(String in) throws InterruptedException {
            logger.info("Service Received " + in);
            Thread.sleep(5_000);
            logger.info("Service Replying to " + in);
            return "reply for " + in;
        }
    
        // Provision topics if needed
    
        // provided by Boot in 2.0
        @Bean
        public KafkaAdmin admin() {
            Map<String, Object> config = new HashMap<>();
            config.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
            return new KafkaAdmin(config);
        }
    
        @Bean
        public NewTopic topic1() {
            return new NewTopic(TRANSACTION_TOPIC1, 10, (short) 1);
        }
    
        @Bean
        public NewTopic result1() {
            return new NewTopic(TRANSACTION_RESULT1, 10, (short) 1);
        }
    
    }
    

    and

    spring.kafka.consumer.enable-auto-commit=false
    spring.kafka.consumer.auto-offset-reset=earliest
    spring.kafka.listener.concurrency=2
    spring.kafka.consumer.group-id=server
    
    0 讨论(0)
提交回复
热议问题