How to listen for the right ACK message from Kafka

前端 未结 1 1217
执念已碎
执念已碎 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 kafkaTemplate;
    
        @Autowired
        public So48349993Application(Exchanger exchanger,
                KafkaTemplate 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 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 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

    
    
        4.0.0
    
        com.example
        so48349993
        0.0.1-SNAPSHOT
        jar
    
        so48349993
        Demo project for Spring Boot
    
        
            org.springframework.boot
            spring-boot-starter-parent
            1.5.9.RELEASE
             
        
    
        
            UTF-8
            UTF-8
            1.8
        
    
        
            
                org.springframework.boot
                spring-boot-starter-integration
            
    
            
                org.springframework.integration
                spring-integration-kafka
                2.3.0.RELEASE
            
    
            
                org.springframework.kafka
                spring-kafka
                1.3.2.RELEASE
            
    
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
    
            
                org.springframework.boot
                spring-boot-starter-web
            
        
    
        
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    
    

    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 kafkaTemplate;
    
        @Autowired
        public So483499931Application(Exchanger exchanger,
                KafkaTemplate 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 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 consumerFactory,
                KafkaTemplate 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 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)
提交回复
热议问题