Organisation de base de Kafka, intégration simple de Springboot

définition:

  • Kafka est une file d'attente de messages par défaut de publication/abonnement distribuée
  • Il s'agit d'une plate-forme de streaming d'événements distribués open source couramment utilisée pour les pipelines de données, l'analyse de flux, l'intégration de données et les applications critiques.

Modèle de consommation:

  • Les consommateurs en mode point à point (moins utilisé)
    extraient activement les données et effacent les messages après leur réception.
    Insérer la description de l'image ici
  • Modèle de publication/abonnement :
    les producteurs envoient les messages vers les files d'attente et les consommateurs s'abonnent aux messages dont ils ont besoin.
    Insérer la description de l'image ici

concept de base:

  • Producteur : producteur de messages
  • Consommateur : consommateur
  • Consommateur : groupe de consommateurs de groupe, les consommateurs ayant le même ID de groupe de consommateurs constituent un groupe de consommateurs ; un consommateur consomme également pour un groupe de consommateurs.
  • Courtier : serveur Kafka
  • Sujet : sujet du message, classification des données
  • Partition : Partition, un Tpoic se compose de plusieurs partitions
  • Réplique : Réplique, chaque partition correspond à plusieurs répliques
  • Leader : La copie contient le leader et le suiveur ; la production et la consommation sont uniquement destinées au leader.

Processus d'envoi du producteur :

  • producteur -> envoyer (producteurRecord) -> intercepteur d'interception -> sérialiseur sérialiseur -> partitionneur de partitionnement
  • Lorsque les données sont accumulées batch.size, l'expéditeur enverra les données ; la valeur par défaut est 16 Ko.
  • Si les données n'atteignent pas batch.size, l'expéditeur attendra linger.msle temps défini puis enverra les données. Unité ms. Valeur par défaut 0ms, indiquant l'absence de délai
  • compression.typeMéthode de compression des données
  • RecordAccumulatorTaille du tampon, par défaut 32 m
  • mode de réponseack
    • 0 : Une fois que le producteur a envoyé les données, il n'est pas nécessaire d'attendre la réponse des données.
    • 1 : Les données envoyées par le producteur, le leader répond après réception des données
    • 1 : tous les leaders répondent après avoir collecté les données de tous les autres nœudsInsérer la description de l'image ici

Logique générale de consommation :

  • Insérer la description de l'image ici

Groupe de consommateurs (CG) :

  • groupidDes consommateurs identiques forment un groupe de consommateurs
  • Chaque consommateur du groupe de consommateurs est responsable de la consommation des données de différentes partitions. Une partition ne peut être consommée que par un seul consommateur dans un groupe.
  • Les groupes de consommateurs n’ont aucune influence les uns sur les autres
  • Lorsque le nombre de groupes de consommateurs est supérieur au nombre de partitions, il y aura闲置
  • coordinator: Aide à réaliser 初始化la somme des groupes de consommateurs分区的分配
    • Chaque nœud en possède un coordinator. En groupid % 50sélectionnant coordinatorle nœud 50, le nombre de partitions est _consumer_offset.
    • 1%50 = 1, _consumer_offsetle numéro sur la partition coordinatorest le leader
    • coordinatorSélectionnez au hasard un consommateur dans le groupe de consommateurs pour devenir le leader. Le leader formulera un plan de consommation et le rendra au groupe de consommateurs coordinator, puis coordinatorattribuera la technologie de consommation à d'autres consommateurs.
    • coordinator心跳Le temps de rétention chez le consommateur 3秒, 45秒 超时- supprimera le consommateur et déclenchera再平衡
    • Le temps de consommation du consommateur est trop long. Par défaut 5分钟, le déclencheur du consommateur sera supprimé.再平衡

Processus de consommation :

  • Créer un client de connexion réseau grand public ConsumerNetworkClientpour interagir avec kafka
  • Initialisation de la demande de consommation : à chaque lot 最小抓取大小, les données n'atteignent pas le délai d'attente de 500 ms et la limite supérieure de la taille des données capturées
  • Envoyer une demande de consommation-》rappel onSuccess(), extraire des données-》Mettez-les dans la file d'attente des messages par lots
  • Les consommateurs consomment les données de la file d'attente des messages dans chaque lot (500 éléments) -》Désérialisation-》Intercepteur-》Traitement des données
  • 1

Plan de consommation (stratégie d'allocation de partition) Range par défaut + CooperativeSticky :

  • Plage : pour 每一个topictrier les partitions de sujet et les consommateurs de messages, déterminez le nombre de partitions que chaque messager consomme en fonction du nombre de partitions/nombre de consommateurs, en excluant les consommateurs précédents inépuisables qui consomment davantage.容易产生数据倾斜
    Insérer la description de l'image ici
  • RoundRobin : stratégie de partitionnement d'interrogation, 针对所有topicrépertorie toutes les partitions de sujet et les consommateurs, les trie en fonction du hashcode et 轮询算法alloue des partitions aux consommateurs
  • Sticky : Sticky (lors de l'exécution d'une nouvelle allocation, essayez d'être aussi proche que possible du dernier résultat d'allocation), essayez d'abord d'être aussi uniforme que possible et attribuez des partitions au hasard aux consommateurs.
  • CooperativeSticky : Sticky des collaborateurs, la stratégie de Sticky est la même, mais prend en charge le rééquilibrage coopératif. Les consommateurs peuvent continuer à consommer à partir de partitions qui n'ont pas été réaffectées.

déplacement décalé : marque la position de consommation

  • <0,9 : Il est maintenu dans zookeeper
  • Après la version 0.9 : les compensations sont conservées dans une rubrique intégrée : _consumer_offsets
  • Utilisez la méthode clé-valeur pour stocker les données, clé : groupid + sujet + numéro de partition
  • offset自动提交 : Par défaut, le décalage est automatiquement soumis toutes les 5 secondes, 默认ce qui est vrai
  • offset手动提交 : lors de la consommation, soumettre manuellement l'offset
    • Synchronisation : attendez que l'offset soit soumis avec succès avant de consommer le suivant
    • Asynchrone : pas d'attente, consommation directe, pas de mécanisme de nouvelle tentative après échec
  • Spécifiez la consommation compensée :
    • earliest: Réinitialiser automatiquement le décalage au décalage le plus ancien --from-beginning
    • latest(Par défaut) : recharger automatiquement le décalage jusqu'au dernier décalage
    • nono: lève une exception au consommateur si le décalage précédent du groupe de consommateurs n'est pas trouvé.
//设置自动提交offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
//自动提交时间 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"5000");
//offset 手动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);

KafkaConsumer kafkaConsumer = new KafkaConsumer<String,String>(properties);
//定义主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
//订阅
kafkaConsumer.subscribe(topics);
while (true){
    
    
    ConsumerRecords<String,String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
    if (CollectionUtil.isNotEmpty(consumerRecords)){
    
    
        for (ConsumerRecord<String, String> record : consumerRecords) {
    
    
            System.out.println(record);
        }
    }
    //手动提交offset
    kafkaConsumer.commitAsync();
}

Consommation à heure précisée :

 //查询对应分区
 Set<TopicPartition> partitions = kafkaConsumer.assignment();

  //保证分区分配方案定制完毕
  while (partitions.size()==0){
    
    
      kafkaConsumer.poll(Duration.ofSeconds(1));
      partitions=kafkaConsumer.assignment();
  }
  //把时间转换成对应的 offset
  Map<TopicPartition,Long> map = new HashMap<>(6);
  Map<TopicPartition,Long> offsetmap = kafkaConsumer.offsetsForTimes(map);
  for (TopicPartition topicPartition : partitions) {
    
    
      //一天前
      offsetmap.put(topicPartition,System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
  }
  Map<TopicPartition, OffsetAndTimestamp> offsetsForTimeMap = kafkaConsumer.offsetsForTimes(offsetmap);
  for (TopicPartition partition : partitions) {
    
    
      OffsetAndTimestamp timestamp = offsetsForTimeMap.get(partition);
      kafkaConsumer.seek(partition,timestamp.offset());
  }

Mécanisme de stockage de fichiers Kafka :

  • TopicC'est un concept logique et partitionun concept physique, et chacun partitioncorrespond à un log文件. Le fichier journal stocke les données produites par le producteur.
  • Les données produites par le producteur seront continuellement ajoutées à la fin du fichier journal. Afin d'éviter que le fichier journal ne soit trop volumineux et n'entraîne une faible efficacité de positionnement des données, Kafka adopte un mécanisme de partitionnement et d'indexation.
  • Chaque partition est divisée en plusieurs segments segmentet chaque segment contient des fichiers .index .log .timeindex .snapshot
  • Ces fichiers se trouvent dans un dossier et la règle de dénomination du dossier est : nom du sujet + numéro de partition en premier-0
    1
    2
  • Index fragmenté : environ tous les 4 Ko de données écrites dans le fichier journal, un index sera écrit dans le fichier d'index.
    • L'odffset enregistré dans le fichier d'index est 相对offset, cela peut garantir que l'espace occupé par la valeur de décalage ne sera pas trop grand, de sorte que la valeur de décalage puisse être contrôlée à une taille fixe

Stratégies de nettoyage et de compression des fichiers :

  • La durée de stockage des journaux par défaut de Kafka est de 7 jours
  • Stratégie de compression : compacte, correspondant à la valeur de la même clé, seule la dernière version est conservée.

Kafka lecture et écriture efficaces :

  • Kafka lui-même est un cluster distribué, qui peut utiliser la technologie de partitionnement et présente un degré élevé de parallélisme.
  • Utilisé pour lire les données 稀疏索引, vous pouvez localiser rapidement les données à consommer
  • Écrivez sur le disque de manière séquentielle. Le producteur de Kafka produit les données qui doivent être écrites log文件. Le processus d'écriture est ajouté à la fin du fichier.顺序写
  • 零拷贝: Les opérations de traitement des données de Kaka sont gérées par les producteurs de Kaka et les consommateurs de Kaka. La couche application Kaka Broker ne se soucie pas des données stockées, il n'est donc pas nécessaire de passer par la couche application et l'efficacité de la transmission est élevée.
  • Page Cache : Kaka s'appuie fortement sur la fonction PageCache fournie par le système d'exploitation sous-jacent. Lorsqu'il y a une opération d'écriture dans la couche supérieure, le système d'exploitation écrit simplement les données dans PageCache. Lorsqu'une opération de lecture se produit, elle est d'abord recherchée dans PageCache. Si elle ne peut pas être trouvée, elle est lue à partir du disque. En fait, PageCache utilise autant de mémoire libre que possible comme cache disque.

Noms de scripts couramment utilisés :

  • commandes liées au sujet :
  • Liste des sujets de requête :sh kafka-topics.sh --bootstrap-server localhost:9092 --list
  • Créez un sujet (nom : première partition : 1 réplique et 3 répliques). Le nombre de répliques ne peut pas dépasser le nombre de clusters.
    • sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --create --partitions 1 --replication-factor 3
  • informations sur le sujet
    • sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --describe
  • Modifier le nombre de partitions de sujet (ne peut être augmenté que)
    • sh kafka-topics.sh --bootstrap-server localhost:9092 --topic first --describe --partitions 3
  • Nouvelles de la production :
    • sh kafka-console-producer.sh --bootstrap-server localhost:9092 --topic first
  • Consommation Consommation :
    • sh kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first
    • sh kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first --from-beginning

Intégration simple de Spring Boot :

<dependency>
      <groupId>org.springframework.kafka</groupId>
      <artifactId>spring-kafka</artifactId>
</dependency>
server:
  port: 8200

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  application:
    name: @artifactId@
  kafka:
    bootstrap-servers:
      - 192.168.1.250:32010
    # 生产配置
    producer:
      #序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      properties:
        linger.ms: 10 #sender 等待事件
         #ssl认证配置相关
#        sasl.mechanism: PLAIN
#        security.protocol: SASL_PLAINTEXT
#        sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin";
      #缓存区大小 32m
      buffer-memory: 33554432
      #批次大小 16k
      batch-size: 16
      # ISR 全部应答
      #acks: -1
      #事务ID前缀 ,配合 @Transactional ,保证多个消息的原子性
      #transaction-id-prefix: "transaction-id-xx"
    #消费配置
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      #group-id: xiaoshu-1
      enable-auto-commit: false
      # 从最早消息开始消费,但是消费后,会记录offset、相同 group-id不会再次消费 
      # offset 是针对每个消费者组
      auto-offset-reset: earliest
      #批量消费,每次最多消费多少条
      #max-poll-records: 50
       #ssl认证配置相关
#      properties:
#        sasl.mechanism: PLAIN
#        security.protocol: SASL_PLAINTEXT
#        sasl.jaas.config: org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin";

    listener:
      # 手动调用Acknowledgment.acknowledge()后立即提交
      ack-mode: manual
      #批量消费,配合 @KafkaListener - batch="true"
      #type: batch

Production:

	@Resource
    private KafkaTemplate<String,String> kafkaTemplate;
    //@Transactional(rollbackFor = RuntimeException.class),配合 ack配置 实现多条消息发送,原子性
    @ApiOperation(value = "推送消息到kafak")
    @GetMapping("/sendMsg")
    public String sendMsg(String topic,String msg){
    
    
        kafkaTemplate.send(topic,msg).addCallback(success -> {
    
    
            if (success==null){
    
    
                System.out.println("消息发送失败");
                return;
            }
            // 消息发送到的topic
            String topicName = success.getRecordMetadata().topic();
            // 消息发送到的分区
            int partition = success.getRecordMetadata().partition();
            // 消息在分区内的offset
            long offset = success.getRecordMetadata().offset();
            System.out.println("发送消息成功:" + topic + "-" + partition + "-" + offset);
        }, failure -> {
    
    
            System.out.println("发送消息失败:" + failure.getMessage());
        });
        return "ok";
    }

Consommation:

@Configuration
public class KafkaConsumer {
    
    

    private static final String TOPIC_DLT=".DLT";

    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;

    /**
     * 每个分区由消费者组种得一个消费者消费,每个消费者独立
     * 分区 -》 消费 、2分区2个消费监听
     * @param record
     * @param consumer
     */
    @KafkaListener(groupId = "group-1", topicPartitions ={
    
     @TopicPartition(topic = "four",partitions = {
    
    "0"})},batch = "false")
    public void consumerTopic1(ConsumerRecord<String, String> record, Consumer consumer){
    
    
        String value = record.value();
        String topic1 = record.topic();
        long offset = record.offset();
        int partition = record.partition();
        try {
    
    
            log.info("收到消息:"+value+"topic:"+topic1+"offset:"+offset+"分区"+partition);
            //TODO 异常,推送到 对应死信 ↓
            //int i=1/0;
        } catch (Exception e) {
    
    
            System.out.println("commit failed");
            kafkaTemplate.send(topic1+TOPIC_DLT,value);
        } finally {
    
    
            consumer.commitAsync();
        }
    }

    @KafkaListener(groupId = "group-1", topicPartitions ={
    
     @TopicPartition(topic = "four",partitions = {
    
    "1"})},batch = "false")
    public void consumerTopic2(ConsumerRecord<String, String> record, Consumer consumer){
    
    
        String value = record.value();
        String topic1 = record.topic();
        long offset = record.offset();
        int partition = record.partition();
        try {
    
    
            log.info("收到消息:"+value+"topic:"+topic1+"offset:"+offset+"分区"+partition);
            //TODO 异常,推送到 对应死信 ↓
            //int i=1/0;
        } catch (Exception e) {
    
    
            System.out.println("commit failed");
            kafkaTemplate.send(topic1+TOPIC_DLT,value);
        } finally {
    
    
            consumer.commitAsync();
        }
    }

}

	/**
     * 监听 topic1 ->转发到 topic2
     */
    @KafkaListener(topics = {
    
    "topic1"},groupId = "group-4")
    @SendTo("topic2")
    public String onMessage7(ConsumerRecord<?, ?> record) {
    
    
        return record.value()+"-转发消息";
    }

    @KafkaListener(topics = {
    
    "topic2"},groupId = "group-5")
    public void onMessage8(ConsumerRecord<?, ?> record) {
    
    
        System.out.println("收到转发消息"+record.value());
    }

Je suppose que tu aimes

Origine blog.csdn.net/hesqlplus730/article/details/129614091
conseillé
Classement