Spring Boot + Kafka的使用

一、快速瞭解Kafka

在把Kafka集成到spring之前,我們首先要了解Kafka是什麼?由什麼東西組成?主要的使用場景是哪些?

Apache Kafka是一個開源消息系統,由Scala寫成。是由Apache軟件基金會開發的一個開源消息系統項目。

Kafka是一個分佈式消息隊列。Kafka對消息保存時根據Topic進行歸類,發送消息者稱爲Producer,消息接受者稱爲Consumer,此外kafka集羣有多個kafka實例組成,每個實例(server)稱爲broker。

無論是kafka集羣,還是consumer都依賴於zookeeper集羣保存一些meta信息,來保證系統可用性。

簡單架構理解圖
Kafka簡單架構圖
詳細架構圖
在這裏插入圖片描述

  • 1)Producer :消息生產者,就是向kafka broker發消息的客戶端;

  • 2)Consumer :消息消費者,向kafka broker取消息的客戶端;

  • 3)Topic :可以理解爲一個隊列;

  • 4) Consumer Group (CG):這是kafka用來實現一個topic消息的廣播(發給所有的consumer)和單播(發給任意一個consumer)的手段。一個topic可以有多個CG。topic的消息會複製(不是真的複製,是概念上的)到所有的CG,但每個partion只會把消息發給該CG中的一個consumer。如果需要實現廣播,只要每個consumer有一個獨立的CG就可以了。要實現單播只要所有的consumer在同一個CG。用CG還可以將consumer進行自由的分組而不需要多次發送消息到不同的topic;

  • 5)Broker :一臺kafka服務器就是一個broker。一個集羣由多個broker組成。一個broker可以容納多個topic;

  • 6)Partition:爲了實現擴展性,一個非常大的topic可以分佈到多個broker(即服務器)上,一個topic可以分爲多個partition,每個partition是一個有序的隊列。partition中的每條消息都會被分配一個有序的id(offset)。kafka只保證按一個partition中的順序將消息發給consumer,不保證一個topic的整體(多個partition間)的順序;

  • 7)Offset:kafka的存儲文件都是按照offset.kafka來命名,用offset做名字的好處是方便查找。例如你想找位於2049的位置,只要找到2048.kafka的文件即可。當然the first offset就是00000000000.kafka。

二、環境準備

這邊的話,我們簡單的在windows搭建一個環境即可

主要的就是以下三個環境:

  • jdk
  • zookeeper
  • kafka

具體步驟這裏就不詳細介紹了,網上有很多案例,照着做一遍即可。
本地測試的話,搭建個單機的即可。

三、Spring Kafka集成

1、添加架包依賴

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.1.0</version>
</dependency>

注意:這裏有一個很大的坑,因爲版本的問題,spring-kafka和kafka-clients的版本一定要按照下圖對應。
在這裏插入圖片描述

2、簡單配置

推薦使用spring-boot的項目,配置既簡單又方便

直接在application.yml配置文件加入以下內容即可。

spring:
  kafka:
    # 消費者
    consumer: 
      group-id: foo
      auto-offset-reset: earliest
      bootstrap-servers: localhost:9092 
    # 生產者
    producer: 
      bootstrap-servers: localhost:9092 
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

如果你想了解更多的配置,可以參考文檔 Apache Kafka Documentation

注意:運行項目之前一定要先開啓zookeeper和kafka服務

3、簡單的例子

監聽器主要是使用@KafkaListenter註解即可,可以監聽多個topic也可以監聽單個。

@Component
public class SimpleListener {
    @KafkaListener(topics = {"topic1", "topic2"})
    public void listen1(String data) {
        System.out.println(data);
    }
}

消息發送主要是使用KafkaTemplate,它具有多個方法可以發送消息,這裏我們用簡單的。

@RestController
@AllArgsConstructor
public class SimpleController {
    
    private final KafkaTemplate<Object, Object> kafkaTemplate;

    @GetMapping("/send/{messge}")
    public String send(@PathVariable String messge) {
        kafkaTemplate.send("topic1", "topci1:" + messge);
        kafkaTemplate.send("topic2", "topci2:" + messge);
        return messge;
    }
}

我們用postman測試一下,看看控制檯有沒有輸出,有沒有接受到消息。
在這裏插入圖片描述

4、發送實體類封裝的消息

4.1實體類
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Bar {
    private Integer id;
    private Integer age;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Foo {
    private Integer id;
    private String name;
}
4.2 配置文件
@Configuration
public class KafkaConfig {

	@Bean
	public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
			ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
			ConsumerFactory<Object, Object> kafkaConsumerFactory,
			KafkaTemplate<Object, Object> template) {
		ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
		configurer.configure(factory, kafkaConsumerFactory);
		factory.setErrorHandler(new SeekToCurrentErrorHandler(
				new DeadLetterPublishingRecoverer(template), 3));
		return factory;
	}

	// 當傳輸的是個實體類時,進行消息格式轉換
	@Bean
	public RecordMessageConverter converter() {
		StringJsonMessageConverter converter = new StringJsonMessageConverter();
		DefaultJackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper();
		typeMapper.setTypePrecedence(TypePrecedence.TYPE_ID);
		typeMapper.addTrustedPackages("com.lzx.kafka.example2");
		Map<String, Class<?>> mappings = new HashMap<>();
		mappings.put("foo", Foo.class);
		mappings.put("bar", Bar.class);
		typeMapper.setIdClassMapping(mappings);
		converter.setTypeMapper(typeMapper);
		return converter;
	}

	@Bean
	public NewTopic foos() {
		return new NewTopic("foo", 1, (short) 1);
	}

	@Bean
	public NewTopic bars() {
		return new NewTopic("bar", 1, (short) 1);
	}
}
4.3 application.yml配置文件
spring:
  kafka:
    consumer:
      group-id: foo
      auto-offset-reset: earliest
      bootstrap-servers: localhost:9092 
    producer:
      bootstrap-servers: localhost:9092 
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      properties:
        spring.json.type.mapping: foo:com.lzx.kafka.entity.Foo,bar:com.lzx.kafka.entity.Bar
4.4 代碼

監聽器

@Component
@KafkaListener(id = "handler", topics = {"foo", "bar"})
public class ListenHandler {
    @Autowired
    private KafkaTemplate<Object, Object> kafkaTemplate;

    @KafkaHandler
    public void foo(@Payload Foo foo, @Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) String key) {
        System.out.println("key:" + key);
        System.out.println("foo:" + foo.toString());
    }

    @KafkaHandler
    public void foo(Bar bar) {
        System.out.println("bar:" + bar.toString());
    }
}

Controlller

@RestController
@AllArgsConstructor
public class Example2Controller {

    private final KafkaTemplate kafkaTemplate;

    @PostMapping("/foo")
    public void send(Foo foo){
        kafkaTemplate.send("foo", "modelOne", foo);
    }

    @PostMapping("/bar")
    public void send(Bar bar){
        kafkaTemplate.send("bar", bar);
    }
}
4.5結果

在這裏插入圖片描述

5、消息發送的同步方法和異步方法

方法

@Service
@AllArgsConstructor
public class SendService {

    private final KafkaTemplate<Object, Object> template;

    // 異步
    public void sendAnsyc(final Bar bar) {
//        ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>("ansyc", bar);

        ListenableFuture<SendResult<Object, Object>> future = template.send("ansyc",bar);
        future.addCallback(new ListenableFutureCallback<SendResult<Object, Object>>() {
            @Override
            public void onSuccess(SendResult<Object, Object> result) {
                System.out.println("發送消息成功:" + result);
            }

            @Override
            public void onFailure(Throwable ex) {
                System.out.println("發送消息失敗:"+ ex.getMessage());
            }
        });
    }

    // 同步
    public void sendSync(final Bar bar) {
        ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>("sync", bar);
        try {
            template.send(producerRecord).get(10, TimeUnit.SECONDS);
            System.out.println("發送成功");
        }
        catch (ExecutionException e) {
            System.out.println("發送消息失敗:"+ e.getMessage());
        }
        catch (TimeoutException | InterruptedException e) {
            System.out.println("發送消息失敗:"+ e.getMessage());
        }
    }
}

監聽器

@Component
public class Example3Listenter {

    @KafkaListener(topics = "ansyc")
    public void listenAnsyc(Bar bar) {
        System.out.println(bar);
    }

    @KafkaListener(topics = "sync")
    public void listenSync(Bar bar) {
        System.out.println(bar);
    }
}

Controller

@RestController
@AllArgsConstructor
public class Example3Controller {
    private final SendService sendService;

    @PostMapping("/ansyc")
    public void sendAnsyc(Bar bar){
        sendService.sendAnsyc(bar);
    }

    @PostMapping("/sync")
    public void sendSync(Bar bar){
        sendService.sendSync(bar);
    }
}

異步結果
在這裏插入圖片描述
同步結果
在這裏插入圖片描述

6、使用事務的消息發送方式

在4.3application.yml中的properties配置上方添加這樣的一句配置即可

transaction-id-prefix: tx.

代碼

@RestController
@AllArgsConstructor
public class Example1Controller {

    private final KafkaTemplate<Object, Object> kafkaTemplate;

    @PostMapping("/send/foo")
    public void sendFoo(Foo foo) {
        kafkaTemplate.executeInTransaction(kafkaTemplate -> {
            kafkaTemplate.send("foo", foo);
            return true;
        });
    }
}

四、相關資料連接

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