spring使用kafka的三種方式(listener、container、stream)

本文介紹spring中使用Kafka的三種方式,其中container方式最靈活,但是開發相對較複雜,stream方式使用最簡便,listener方式由於提供的最早,使用的較普遍。 具體的代碼參照 示例項目 https://github.com/qihaiyan/springcamp/tree/master/spring-kafka

一、概述

在實際項目中,用到kafka的場景非常普遍,特別是事件驅動的編程模式,kafka基本是標配。

二、KafkaListener

KafkaListener應該是目前使用比較多的一種方式,開發簡單,易於理解。但是從易用性的角度,應該會逐步被spring-cloud-stream所替代。 KafkaListener是一個註解,在對應的方法上加上這個註解,方法就可以處理接收到的kakfa消息,註解中通過topics參數指定需要消費的kakfa的topic,topics參數支持SPEL表達式,可以同時消費多個kafka topic:

@KafkaListener(topics = "test-topic")
    public void receive(ConsumerRecord<String, String> consumerRecord) {
        this.payload = consumerRecord.value();
        log.info("received payload='{}'", payload);
    }

帶註解的方法的入參是一個ConsumerRecord變量,存放了kafka中接收到的消息。

同時需要對kafka進行配置,可以指定kakfa服務器的地址,以及序列化方式:

spring.kafka:
    bootstrap-servers: 192.168.1.1:2181
    consumer:
      group-id: utgroup
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

三、ConcurrentMessageListenerContainer

通過ConcurrentMessageListenerContainer可以已可編程的方式來處理kafka消息,這種方式的好處是topic是在程序中指定的,這樣可以將topic的配置存貯在任何地方,比如數據庫中,也可以按照不同的條件分支指定不同的topic,非常靈活。這是其它兩種方式做不到的。

ConcurrentMessageListenerContainer的配置:

@Component
public class MessageListenerContainerConsumer {

    public static final String LISTENER_CONTAINER_TOPIC = "container-topic";

    public Set<String> consumedMessages = new HashSet<>();

    @PostConstruct
    void start() {
        MessageListener<String, String> messageListener = record -> {
            System.out.println("MessageListenerContainerConsumer received message: " + record.value());
            consumedMessages.add(record.value());
        };

        ConcurrentMessageListenerContainer<String, String> container =
                new ConcurrentMessageListenerContainer<>(
                        consumerFactory(),
                        containerProperties(LISTENER_CONTAINER_TOPIC, messageListener));

        container.start();
    }

    private DefaultKafkaConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(
                new HashMap<String, Object>() {
                    {
                        put(BOOTSTRAP_SERVERS_CONFIG, System.getProperty("spring.kafka.bootstrap-servers"));
                        put(GROUP_ID_CONFIG, "groupId");
                        put(AUTO_OFFSET_RESET_CONFIG, "earliest");
                    }
                },
                new StringDeserializer(),
                new StringDeserializer());
    }

    private ContainerProperties containerProperties(String topic, MessageListener<String, String> messageListener) {
        ContainerProperties containerProperties = new ContainerProperties(topic);
        containerProperties.setMessageListener(messageListener);
        return containerProperties;
    }
}

以上代碼定義了MessageListenerContainerConsumer這個類,是一個spring的bean,在PostConstruct這個bean的初始化代碼中,我們使用了ConcurrentMessageListenerContainer,並指定了topic public static final String LISTENER_CONTAINER_TOPIC = "container-topic",在這個爲了便於演示我們使用了一個常量,實際上這個topic的值可以是任意變量,可以從數據庫中讀取,也可以通過實際的場景動態計算,這樣就做到了topic的靈活配置。

四、spring-cloud-stream

spring-cloud-stream是springcloud的一個子項目,這個項目的目標是一個事件驅動(Event-Driven)的編程框架。spring-cloud-stream對kafka進行了非常好的抽象,除了kakfa,還支持RabbitMQ,程序中除了配置文件外,完全看不到kafka的痕跡,意味着我們在開發的時候不需要關心底層的kakfa的細節,如果像從kafka切換到RabbitMQ,只需要修改一下引入的jar包和配置文件。

詳細介紹見spring的官方文檔: https://spring.io/projects/spring-cloud-stream

spring-cloud-stream是用了spring-cloud-function,我們只需要在程序中實現一個function接口,就可以處理kafka消息,編寫非常簡單。

i@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Function<String, Object> handle() {
        return String::toUpperCase;
    }
}

除了配置文件外,代碼中只需要一行代碼public Function<String, Object> handle()就實現了kafka消息的處理,這行代碼完全看不出跟kakfa有什麼關係,就是一個普通的方法,把抽象做到了極致。 需要注意方法的名字於配置文件中的名字要匹配。

配置:

spring:
  cloud.stream:
    bindings:
      handle-in-0:
        destination: testEmbeddedIn
        content-type: text/plain
        group: utgroup
      handle-out-0:
        destination: testEmbeddedOut
    kafka:
      binder:
        brokers: 192.168.1.1:2181
        configuration:
          key.serializer: org.apache.kafka.common.serialization.ByteArraySerializer
          value.serializer: org.apache.kafka.common.serialization.ByteArraySerializer

注意配置文件中 handle-in-0handle-out-0 這2行配置,handle指的就是前面代碼中的public Function<String, Object> handle() handle這個方法。spring-cloud-stream就是通過方法名和配置文件中配置項的名字,來確立代碼和配置的匹配關係,這也是約定優於配置的編程思想的體現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章