kafka初體驗-會用階段

1. 楔子

公司項目開發用到了kafka,可是某個菜鳥不會,於是不得不進行的爲期一週的晚自習去惡補相關知識。

但是惡補來知識很零散,所以趁這次週末寫一篇博客,總結一下得失,順便查缺補漏。

2. Kafka

2.1 Kafka是什麼

Kafka是最初由Linkedin公司開發,是一個分佈式、支持分區的(partition)、多副本的(replica),基於zookeeper協調的分佈式消息系統,它的最大的特性就可以實時的處理大量數據以滿足各種需求場景:比如基於hadoop的批處理系統、低延遲的實時系統、storm/Spark流式處理引擎,web/nginx日誌、訪問日誌,消息服等等,用scala語言編寫,Linkedin於2010年貢獻給了Apache基金會併成爲頂級開源項目。

2.2 kafka相關概念

Broker:即Kafka的服務器,用戶存儲消息,Kafa集羣中的一臺或多臺服務器統稱爲 broker。

Producer:消息的生產者,是消息的產生的源頭,負責生成消息併發送到Kafka服務器上。

Consumer:消息消費者,是消息的使用方,負責消費Kafka服務器上的消息。

Group:消費者分組,用於歸組同類消費者,在Kafka中,多個消費者可以共同消息一個Topic下的消息,每個消費者消費其中的部分消息,這些消費者就組成了一個分組,擁有同一個分組名稱,通常也被稱爲消費者集羣。group下訂閱的topic下的每個分區只能分配給某個group下的一個consumer(當然該分區還可以被分配給其他group)

Offset:消息存儲在Kafka的Broker上,消費者拉取消息數據的過程中需要知道消息在文件中的偏移量,這個偏移量就是所謂的Offset。

Topic:主題,由用戶定義並配置在Kafka服務器,用於建立生產者和消息者之間的訂閱關係:生產者發送消息到指定的Topic下,消息者從這個Topic下消費消息。

Partition:消息分區,一個Topic下面會分爲很多分區,例如:“kafka-test”這個Topic下可以分爲6個分區,分別由兩臺服務器提供,那麼通常可以配置爲讓每臺服務器提供3個分區,假如服務器ID分別爲0、1,則所有的分區爲0-0、0-1、0-2和1-0、1-1、1-2。Topic物理上的分組,一個 topic可以分爲多個 partition,每個 partition 是一個有序的隊列。partition中的每條消息都會被分配一個有序的 id(offset)。

2.2 kafka的優缺點

優點:
高吞吐量
低延遲
分佈式
消息代理能力
高併發
批處理能力(ETL之類的功能)
實時處理

缺點 

沒有完整的監控工具集 
消息調整的問題
不支持使用通配符選擇主題
缺乏一致性
性能降低 
表現笨拙

 

2.3 kafka和其他MQ對比
 

參考:https://blog.csdn.net/yunfeng482/article/details/72856762

 

2.4 kafka集成springboot

假設你已經安裝好自己的kafka了

引入依賴和設置application.yml,版本號根據自己的kafka版本決定

<dependency>
	<groupId>org.springframework.kafka</groupId>
	<artifactId>spring-kafka</artifactId>
</dependency>
kafka:
  bootstrap_servers_config: 192.168.126.130:9092
  retries_config: 0
  batch_size_config: 16384
  buffer_memory_config: 33554432
  topic: second
  group_id: asdf
  auto_offset_reset: earliest
  enable-auto-commit: true
  auto_commit_interval: 100

 

編寫kafka生產者配置類 KafkaConsumerConfig.java

package com.example.demokafka.config;

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;

import java.util.HashMap;
import java.util.Map;

/**
 * className: KafkaConfig <br/>
 * packageName:com.example.demokafka.config <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:44 <br/>
 */
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
    @Value("${kafka.bootstrap_servers_config}")
    private String bootstrap_servers_config;

    @Value("${kafka.retries_config}")
    private String retries_config;

    @Value("${kafka.batch_size_config}")
    private String batch_size_config;

    @Value("${kafka.buffer_memory_config}")
    private String buffer_memory_config;

    @Value("${kafka.topic}")
    private String topic;

    @Value("${kafka.group_id}")
    private String groupId;

    @Value("${kafka.auto_offset_reset}")
    private String autoOffsetReset;

    @Value("${kafka.enable-auto-commit}")
    private String enableAutoCommit;

    @Value("${kafka.auto_commit_interval}")
    private String autoCommitInterval;

    @Bean
    public Map<String, Object> consumerConfigs() {
        HashMap<String, Object> configs = new HashMap<>();
        //消費者
        configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap_servers_config);
        configs.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        configs.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
        configs.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        configs.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        configs.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        return configs;
    }

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setPollTimeout(1000);
        return factory;
    }

    @Bean
    public Consumer<String, String> consumer() {
        return consumerFactory().createConsumer();
    }

    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs());
    }
}

 

編寫消費者配置類 KafkaConsumerConfig.java

package com.example.demokafka.config;

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;

import java.util.HashMap;
import java.util.Map;

/**
 * className: KafkaConfig <br/>
 * packageName:com.example.demokafka.config <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:44 <br/>
 */
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
    @Value("${kafka.bootstrap_servers_config}")
    private String bootstrap_servers_config;

    @Value("${kafka.retries_config}")
    private String retries_config;

    @Value("${kafka.batch_size_config}")
    private String batch_size_config;

    @Value("${kafka.buffer_memory_config}")
    private String buffer_memory_config;

    @Value("${kafka.topic}")
    private String topic;

    @Value("${kafka.group_id}")
    private String groupId;

    @Value("${kafka.auto_offset_reset}")
    private String autoOffsetReset;

    @Value("${kafka.enable-auto-commit}")
    private String enableAutoCommit;

    @Value("${kafka.auto_commit_interval}")
    private String autoCommitInterval;

    @Bean
    public Map<String, Object> consumerConfigs() {
        HashMap<String, Object> configs = new HashMap<>();
        //消費者
        configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap_servers_config);
        configs.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        configs.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitInterval);
        configs.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        configs.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        configs.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        return configs;
    }

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties().setPollTimeout(1000);
        return factory;
    }

    @Bean
    public Consumer<String, String> consumer() {
        return consumerFactory().createConsumer();
    }

    public ConsumerFactory<String, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs());
    }
}

編寫kafka的消息發送者  KafkaSender.java

package com.example.demokafka.producer;

import com.alibaba.fastjson.JSON;
import com.example.demokafka.entity.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.Executors;

/**
 * className: KafkaSender <br/>
 * packageName:com.example.demokafka.producer <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:45 <br/>
 */
@Component
@Slf4j
public class KafkaSender {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    //發送消息方法
    public void send(String topic) {
        Executors.newSingleThreadExecutor().submit(() -> {
            while (true) {
                Message message = new Message();
                message.setId(System.currentTimeMillis());
                message.setMsg(UUID.randomUUID().toString());
                kafkaTemplate.send(topic, JSON.toJSONString(message));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("send success");
            }
        });
    }

}

編寫kafka的消息消費者,先寫一個消費者基類 MsgConsumer.java,所有消費者實現 dowork() 方法

package com.example.demokafka.consumer.base;

import org.apache.kafka.clients.consumer.ConsumerRecord;

/**
 * className: MsgConsumer <br/>
 * packageName:com.example.demokafka.consumer.base <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-8 22:21 <br/>
 */
public interface MsgConsumer {

    void doWork(ConsumerRecord consumerRecord);

}

然後新建一個消費者類 KafkaReceive.java

package com.example.demokafka.consumer;

import com.example.demokafka.consumer.base.MsgConsumer;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.Executors;

/**
 * className: KafkaConsumer <br/>
 * packageName:com.example.demokafka.consumer <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-8 22:14 <br/>
 */

@Component
public class KafkaReceive {

    @Autowired
    private Consumer consumer;

    public void consumer(String topic, MsgConsumer msgConsumer) {
        Executors.newSingleThreadExecutor().submit(()->{
            consumer.subscribe(Collections.singletonList(topic));
            while (true) {
                ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                    msgConsumer.doWork(consumerRecord);
                }
            }
        });
    }
}

當然最後咱再加一個消息實體類 Message.java,用於封裝消息,作爲載體

package com.example.demokafka.entity;

import lombok.Data;

/**
 * className: Message <br/>
 * packageName:com.example.demokafka.entity <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:44 <br/>
 */
@Data
public class Message {

    private Long id;    //id

    private String msg; //消息

}

 

ok,配置完成  現在可以測試一下,新建一個 KafkaController.java,寫兩個接口測試一下

package com.example.demokafka.controller;

import com.example.demokafka.consumer.KafkaReceive;
import com.example.demokafka.producer.KafkaSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * className: KafkaController <br/>
 * packageName:com.example.demokafka.controller <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:51 <br/>
 */
@RestController
public class KafkaController {

    @Autowired
    private KafkaSender kafkaSender;

    @Autowired
    private KafkaReceive kafkaReceive;

    @GetMapping("/send")
    public String send(@RequestParam(name = "topic") String topic) {
        kafkaSender.send(topic);
        return "SUCCESS";
    }

    @GetMapping("/receive")
    public String receive(@RequestParam(name = "topic") String topic) {
        kafkaReceive.consumer(topic,(m)->{
            System.out.println(m.key());
            System.out.println(m.value());
        });
        return "SUCCESS";
    }

}

分別訪問發送和消費接口:http://localhost:8080/send?topic=yuwen    http://localhost:8080/receive?topic=yuwen

 控制檯打印信息如下:

嗯,大功告成,會用階段已完成。 

 

除了上面的那種消費方法,還可以使用註解監聽的方式消費消息,如下 KafkaListen.java

package com.example.demokafka.consumer;

import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.util.Optional;

/**
 * className: KafkaReceiver <br/>
 * packageName:com.example.demokafka.consumer <br/>
 * description:  <br/>
 *
 * @author yuwen <br/>
 * @date: 2020-4-7 22:48 <br/>
 */
@Component
@Slf4j
public class KafkaListen {

    @KafkaListener(topics = {"yuwen"})
    public void listen(ConsumerRecord<?, ?> record) {

        Optional<?> kafkaMessage = Optional.ofNullable(record.value());

        if (kafkaMessage.isPresent()) {
            Object message = kafkaMessage.get();
            log.info("----------------- record =" + record);
            log.info("------------------ message =" + message);
        }

    }

}

 

3. 最後

kafka初步的學習已經達成目標,處於會用階段,至於kafka原理什麼的,這些需要深入瞭解才能寫出博客,不然就是誤己誤人。因此在這裏就暫告一段落吧。

寫了一篇博客,算是一個小小的學習產物吧,而且以後再次回顧kakfa也非常的方便。

 

end

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