kafka 02 - tres clientes importantes de kafka

kafka 02 - tres clientes importantes de kafka

1. Introducción

1.1 Acerca de la instalación de Kafka

1.2 Introducción a los clientes de uso común

  • API AdminClient:
    permite la gestión y detección de temas, corredores y otros objetos de Kafka.
  • API de productor:
    publique mensajes en una o más API.
  • API de consumidor:
    suscríbase a uno o más temas y procese los mensajes generados.

1.3 Dependencias

  • como sigue:
    Insertar descripción de la imagen aquí

            <!--kafka客户端-->
            <dependency>
                <groupId>org.apache.kafka</groupId>
                <artifactId>kafka-clients</artifactId>
                <version>2.8.2</version>
            </dependency>
            
    
  • pompón completo

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.liu.susu</groupId>
        <artifactId>kafka-api</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>kafka-api</name>
        <description>kafka-api</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.11</version>
            </dependency>
    
            <!--kafka客户端-->
            <dependency>
                <groupId>org.apache.kafka</groupId>
                <artifactId>kafka-clients</artifactId>
                <version>2.8.2</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

2. Cliente administrador

2.1 Configuraciones de administrador

2.2 API de administrador-cliente

2.2.1 Establecer objeto AdminClient

  • Consulte el sitio web oficial para obtener una configuración detallada. La configuración simple es la siguiente:

    package com.liu.susu.admin;
    
    import org.apache.kafka.clients.admin.AdminClient;
    import org.apache.kafka.clients.admin.AdminClientConfig;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.Properties;
    
    /**
     * @Description
     * @Author susu
     */
    public class AdminExample1 {
          
          
    
        public final static String TOPIC_NAME = "";
    
        /**
         * 1. 创建并设置 AdminClient 对象
         */
        public static AdminClient getAdminClient(){
          
          
            Properties properties = new Properties();
            properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "Kafka服务IP:9092");
    
            AdminClient adminClient = AdminClient.create(properties);
            return adminClient;
        }
    
        public static void main(String[] args) {
          
          
            //1. 测试 创建并设置 AdminClient 对象
            AdminClient adminClient = AdminExample1.getAdminClient();
            System.out.println("adminClient==>" + adminClient);
        }
    }
    
    

    Insertar descripción de la imagen aquí

2.2.2 Crear tema + obtener lista de temas

  • como sigue:

        /**
         * 2. 创建topic
         */
        public static void createTopic(){
          
          
            AdminClient adminClient = getAdminClient();
            // 副本因子
            short rs = 1;
            NewTopic newTopic = new NewTopic("new_topic_test", 1, rs);//new_topic_test 是 topic的name
            CreateTopicsResult topics = adminClient.createTopics(Arrays.asList(newTopic));
            System.out.println("创建的新topic为::::" + topics);
        }
    
        /**
         * 3. 获取已经创建的 topic 的列表
         */
        public static ListTopicsResult getTopicList(){
          
          
            AdminClient adminClient = getAdminClient();
            ListTopicsResult topicList = adminClient.listTopics();
            return topicList;
        }
    
  • La prueba es la siguiente:

        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            //1. 测试 创建并设置 AdminClient 对象
    //        AdminClient adminClient = AdminExample1.getAdminClient();
    //        System.out.println("adminClient==>" + adminClient);
    
            //2. 测试 创建topic
            createTopic();
    
            //3. 获取已经创建的 topic 的列表
            ListTopicsResult topicList = getTopicList();
            Collection<TopicListing> topicListings = topicList.listings().get();
            for (TopicListing topic : topicListings) {
          
          
                System.out.println(topic);
            }
    
        }
    

    Insertar descripción de la imagen aquí
    Insertar descripción de la imagen aquí

2.2.3 Eliminar tema

  • como sigue:
        /**
         * 4. 删除 topic
         */
        public static void deleteTopic(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            DeleteTopicsResult deleteTopicsResult = adminClient.deleteTopics(Arrays.asList(topicName));
            deleteTopicsResult.all().get();
        }
    

2.2.4 Ver información de descripción del tema

  • como sigue:

        /**
         * 5. 获取描述 topic 的信息
         */
        public static void getDescribeTopics(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            DescribeTopicsResult result = adminClient.describeTopics(Arrays.asList(topicName));
            Map<String, TopicDescription> descriptionMap = result.all().get();
            descriptionMap.forEach((k,v)->{
          
          
                System.out.println("k==>"+k +",v===>"+v);
            });
        }
    

    Insertar descripción de la imagen aquí

    k==>susu-topic,v===>(name=susu-topic, internal=false, partitions=(partition=0, leader=IP:9092 (id: 0 rack: null), replicas=IP:9092 (id: 0 rack: null), isr=IP:9092 (id: 0 rack: null)), authorizedOperations=null)
    

2.2.5 Ver información de configuración del tema

  • como sigue:
        /**
         * 6. 获取 topic 的配置信息
         */
        public static void getDescribeConfig(String topicName) throws ExecutionException, InterruptedException{
          
          
            AdminClient adminClient = getAdminClient();
            ConfigResource resource = new ConfigResource(ConfigResource.Type.TOPIC,topicName);
            DescribeConfigsResult configsResult = adminClient.describeConfigs(Arrays.asList(resource));
            Map<ConfigResource, Config> configMap = configsResult.all().get();
            configMap.forEach((k,v)->{
          
          
                System.out.println("k==>"+k +",v===>"+v);
            });
        }
        
        //查看某一项配置(eg:message.downconversion.enable)的值
        Config config = configMap.get(resource);
        ConfigEntry configEntry = config.get("message.downconversion.enable");
        System.out.println("message.downconversion.enable===>" + configEntry.value());
    
    Insertar descripción de la imagen aquí

2.2.6 Modificar la información de configuración del tema

  • como sigue:
        /**
         * 7. 修改 topic 的配置信息
         *    本例修改 message.downconversion.enable,将默认的 true 改为 false
         */
        public static void editConfig(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            Map<ConfigResource,Config> configMap = new HashMap<>();
    
            ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC,topicName);
    
            String keyName = "message.downconversion.enable";
            String value = "false";
            ConfigEntry configEntry = new ConfigEntry(keyName, value);
            Config config = new Config(Arrays.asList(configEntry));
    
            configMap.put(configResource,config);
    
            AlterConfigsResult alterConfigsResult = adminClient.alterConfigs(configMap);
            alterConfigsResult.all().get();
        }
    
  • El efecto es el siguiente:
    Insertar descripción de la imagen aquí

2.2.7 Agregar partición

2.2.7.1 Conceptos relacionados

  • Tema: El tema es un concepto virtual que consta de 1 a múltiples particiones, puede entenderse como una cola, tanto los productores como los consumidores están orientados a un tema.
  • Partición: partición, la unidad de almacenamiento de mensajes real. Para lograr escalabilidad, se puede distribuir un tema muy grande a varios corredores.Un tema se puede dividir en varias particiones, cada partición es una cola ordenada (no se puede garantizar el orden de las particiones y el orden global)
  • Productor: Productor de mensajes, el rol que publica mensajes en Kafka.
  • Consumidor: consumidor de mensajes, el cliente que extrae el consumo de mensajes de Kafka.
  • Broker: Broker, un servidor Kafka es un Broker, un clúster consta de varios Brokers y un Broker puede acomodar varios temas.

2.2.7.2 Demostración

  • El código se muestra a continuación:
        /**
         * 8. 增加 topic 的Partitions
         */
        public static void addPartitionNum(String topicName, int partitionNum) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            Map<String,NewPartitions> partitionsMap = new HashMap<>() ;
    
            NewPartitions newPartitions = NewPartitions.increaseTo(partitionNum);//增加到的数量
    
            partitionsMap.put(topicName,newPartitions);
    
            CreatePartitionsResult request = adminClient.createPartitions(partitionsMap);
            request.all().get();
        }
    
  • El efecto es el siguiente:
    Insertar descripción de la imagen aquí

2.3 Código adjunto

  • como sigue:
    package com.liu.susu.admin;
    
    import org.apache.kafka.clients.admin.*;
    import org.apache.kafka.common.KafkaFuture;
    import org.apache.kafka.common.config.ConfigResource;
    import org.apache.kafka.common.requests.CreatePartitionsRequest;
    
    import java.util.*;
    import java.util.concurrent.ExecutionException;
    
    /**
     * @Description
     * @Author susu
     */
    public class AdminExample1 {
          
          
    
        public final static String TOPIC_NAME = "new_topic_test";
    
        /**
         * 1. 创建并设置 AdminClient 对象
         */
        public static AdminClient getAdminClient(){
          
          
            Properties properties = new Properties();
            properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "43.143.190.116:9092");
    
            AdminClient adminClient = AdminClient.create(properties);
            return adminClient;
        }
    
    
        /**
         * 2. 创建topic
         */
        public static void createTopic(){
          
          
            AdminClient adminClient = getAdminClient();
            // 副本因子
            short rs = 1;
            NewTopic newTopic = new NewTopic("new_topic_test", 1, rs);
            CreateTopicsResult topics = adminClient.createTopics(Arrays.asList(newTopic));
            System.out.println("创建的新topic为::::" + topics);
        }
    
        /**
         * 3. 获取已经创建的 topic 的列表
         */
        public static ListTopicsResult getTopicList(){
          
          
            AdminClient adminClient = getAdminClient();
            ListTopicsResult topicList = adminClient.listTopics();
            return topicList;
        }
    
        /**
         * 4. 删除 topic
         */
        public static void deleteTopic(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            DeleteTopicsResult deleteTopicsResult = adminClient.deleteTopics(Arrays.asList(topicName));
            deleteTopicsResult.all().get();
        }
    
        /**
         * 5. 获取描述 topic 的信息
         */
        public static void getDescribeTopics(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            DescribeTopicsResult result = adminClient.describeTopics(Arrays.asList(topicName));
            Map<String, TopicDescription> descriptionMap = result.all().get();
            descriptionMap.forEach((k,v)->{
          
          
                System.out.println("k==>"+k +",v===>"+v);
            });
        }
    
        /**
         * 6. 获取 topic 的配置信息
         */
        public static void getDescribeConfig(String topicName) throws ExecutionException, InterruptedException{
          
          
            AdminClient adminClient = getAdminClient();
            ConfigResource resource = new ConfigResource(ConfigResource.Type.TOPIC,topicName);
            DescribeConfigsResult configsResult = adminClient.describeConfigs(Arrays.asList(resource));
            Map<ConfigResource, Config> configMap = configsResult.all().get();
            configMap.forEach((k,v)->{
          
          
                System.out.println("\nk==>"+k +",v===>"+v);
            });
    
            //查看某一项配置(eg:message.downconversion.enable)的值
            Config config = configMap.get(resource);
            ConfigEntry configEntry = config.get("message.downconversion.enable");
            System.out.println("message.downconversion.enable===>" + configEntry.value());
        }
    
        /**
         * 7. 修改 topic 的配置信息
         *    本例修改 message.downconversion.enable,将默认的 true 改为 false
         */
        public static void editConfig(String topicName) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            Map<ConfigResource,Config> configMap = new HashMap<>();
    
            ConfigResource configResource = new ConfigResource(ConfigResource.Type.TOPIC,topicName);
    
            String keyName = "message.downconversion.enable";
            String value = "false";
            ConfigEntry configEntry = new ConfigEntry(keyName, value);
            Config config = new Config(Arrays.asList(configEntry));
    
            configMap.put(configResource,config);
    
            AlterConfigsResult alterConfigsResult = adminClient.alterConfigs(configMap);
            alterConfigsResult.all().get();
        }
    
        /**
         * 8. 增加 topic 的Partitions
         */
        public static void addPartitionNum(String topicName, int partitionNum) throws ExecutionException, InterruptedException {
          
          
            AdminClient adminClient = getAdminClient();
            Map<String,NewPartitions> partitionsMap = new HashMap<>() ;
    
            NewPartitions newPartitions = NewPartitions.increaseTo(partitionNum);//增加到的数量
    
            partitionsMap.put(topicName,newPartitions);
    
            CreatePartitionsResult request = adminClient.createPartitions(partitionsMap);
            request.all().get();
        }
    
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            //1. 测试 创建并设置 AdminClient 对象
    //        AdminClient adminClient = AdminExample1.getAdminClient();
    //        System.out.println("adminClient==>" + adminClient);
    
    
            //2. 测试 创建topic
    //        createTopic();
    
            //3. 获取已经创建的 topic 的列表
            ListTopicsResult topicList = getTopicList();
            Collection<TopicListing> topicListings = topicList.listings().get();
            for (TopicListing topic : topicListings) {
          
          
                System.out.println(topic);
            }
    
            // 4. 删除topic
    //        deleteTopic("new_topic_test");
    
            // 5.
    //        getDescribeTopics("susu-topic");
    
            //6. 获取 topic 的配置信息
    //        getDescribeConfig("susu-topic");
    
            // 7. 修改 topic 的配置信息
    //        editConfig("susu-topic");
    //
    //        System.out.println("\n=============修改之后的配置===========\n");
    //
    //        getDescribeConfig("susu-topic"); //修改之后再查看配置
    
            //8. 增加 topic 的Partitions
            addPartitionNum("susu-topic",2);
            System.out.println("添加完毕");
    
        }
    
    }
    
    

3. API de productor

3.1 Configuraciones del productor

3.1.1 Consulte el sitio web oficial.

3.1.2 Sobre la configuración de acks (garantía de entrega de mensajes)

En cuanto a la configuración de acks, la cantidad de acuses de recibo que el productor solicita al líder haber recibido antes de considerar completa la solicitud. Esto controla la persistencia de los registros enviados. Se permiten las siguientes configuraciones:

  • acks=0, si se establece en 0, entonces el productorNo esperará ninguna confirmación del servidor.. (Es decir: ignore el mensaje después de enviarlo, independientemente de si el mensaje se escribió correctamente)

    • El registro se agrega inmediatamente al búfer del socket y se considera enviado.
    • En este caso, no hay garantía de que el servidor haya recibido el registro y la configuración del reintento no tendrá efecto (ya que el cliente generalmente no se dará cuenta de ningún fallo). El desplazamiento devuelto para cada registro siempre se establecerá en -1.
    • Es decir: en este caso, después de enviar el mensaje, no se recibe en absoluto o se recibe una vez, por lo tanto 最多收到一次消息(收到0次或多次).
  • ataques = 1, lo que significaría que el líder escribirá el registro en su registro local, pero responderá sin esperar el reconocimiento completo de todos los seguidores.

    • En este caso, si el líder falla inmediatamente después de reconocer el registro, pero antes de que los seguidores lo repliquen, el registro se perderá.
    • Dos situaciones:
      • Una es que si el mensaje no se recibe y no hay respuesta al envío repetido, todavía se recibe una vez en este momento;
      • En segundo lugar, si se recibe un mensaje pero hay un problema con la respuesta, es decir, si solo no se recibe el reenvío de la respuesta, el mensaje se recibirá repetidamente, es decir, varias veces.
    • Es decir: esta situación 至少收到一次消息(一次或多次).
  • acks=todos (o acks=-1), lo que significa que el líder esperará a que llegue el conjunto completo de réplicas sincronizadas.Confirmar registro

    • Esto garantiza que los registros no se perderán siempre que al menos una réplica sincronizada permanezca activa. Ésta es la garantía más sólida.
    • Es decir: en este caso 收到消息有且仅有一次, si se envía repetidamente, será rechazado.
  • Tenga en cuenta que habilitar la idempotencia requiere que este valor de configuración sea "todos". Si se establecen configuraciones conflictivas y la idempotencia no está habilitada explícitamente, la idempotencia se deshabilita.

3.2 API de productor

3.2.1 Envío asincrónico

  • El código se muestra a continuación:
    Insertar descripción de la imagen aquí
    package com.liu.susu.producer;
    
    import org.apache.kafka.clients.producer.KafkaProducer;
    import org.apache.kafka.clients.producer.Producer;
    import org.apache.kafka.clients.producer.ProducerConfig;
    import org.apache.kafka.clients.producer.ProducerRecord;
    
    import java.util.Properties;
    
    /**
     * @Description
     * @Author susu
     */
    public class ProducerExample1 {
          
          
    
        public static Properties getProperties(){
          
          
            Properties properties = new Properties();
            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "IP:9092");
            properties.put(ProducerConfig.ACKS_CONFIG, "all");
            properties.put(ProducerConfig.RETRIES_CONFIG, "0");
            properties.put(ProducerConfig.BATCH_SIZE_CONFIG, "16348");
            properties.put(ProducerConfig.LINGER_MS_CONFIG, "1");
            properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, "33554432");
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            return properties;
        }
    
        /**
         * 1. 发送消息(异步发送)
         *    1.1 一次发一条消息
         */
        public static void producerSendOne(String topicName){
          
          
            Properties properties = getProperties();
            //Producer对象
            Producer<String, String> producer = new KafkaProducer<>(properties);
            //消息对象
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName,"num1","A-10001");
            //发送消息
            producer.send(producerRecord);
            //所有的通道打开都要记得关闭
            producer.close();
        }
        /**
         * 1. 发送消息(异步发送)
         *    1.2 一次发多条消息
         */
        public static void producerSendMore(String topicName){
          
          
            Properties properties = getProperties();
            //Producer对象
            Producer<String, String> producer = new KafkaProducer<>(properties);
            for (int i = 0; i < 5; i++) {
          
          
                //消息对象
                ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName,"Record-"+i,"R-1000"+i);
                //发送消息
                producer.send(producerRecord);
            }
            producer.close();
        }
    
        public static void main(String[] args) {
          
          
            //1.1 一次发一条消息
    //        producerSendOne("susu-topic");
    
            //1.2 一次发多条消息
            producerSendMore("susu-topic");
        }
    
    }
    
    
  • Los resultados de la prueba son los siguientes:
    Insertar descripción de la imagen aquí

3.2.2 Envío de bloqueo asincrónico (envío sincrónico)

  • El código se muestra a continuación:

    package com.liu.susu.producer;
    
    import org.apache.kafka.clients.producer.*;
    
    import java.util.Properties;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    /**
     * @Description
     * @Author susu
     */
    public class ProducerExample2 {
          
          
    
        public static Properties getProperties(){
          
          
            Properties properties = new Properties();
            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "43.143.190.116:9092");
            properties.put(ProducerConfig.ACKS_CONFIG, "all");
            properties.put(ProducerConfig.RETRIES_CONFIG, "0");
            properties.put(ProducerConfig.BATCH_SIZE_CONFIG, "16348");
            properties.put(ProducerConfig.LINGER_MS_CONFIG, "1");
            properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, "33554432");
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            return properties;
        }
        
        /**
         * 1. 异步阻塞发送(同步发送)
         */
        public static void producerSendMore(String topicName) throws ExecutionException, InterruptedException {
          
          
            Properties properties = getProperties();
            //Producer对象
            Producer<String, String> producer = new KafkaProducer<>(properties);
            for (int i = 0; i < 5; i++) {
          
          
                //消息对象
                ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName,"Z-Record-"+i,"Z-R-1000"+i);
                //发送消息
    //            producer.send(producerRecord);
                Future<RecordMetadata> send = producer.send(producerRecord);
                RecordMetadata recordMetadata = send.get();//future.get会进行阻塞直到返回数据表示发送成功,才会继续下一条消息的发送
    
                System.out.println("Z-Record-"+i + ",partition-->"+recordMetadata.partition() + ",offset-->"+recordMetadata.offset());
    
            }
            producer.close();
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException{
          
          
            //1. 异步阻塞发送(同步发送)
            producerSendMore("susu-topic");
        }
    
    }
    
    
  • La prueba es la siguiente:
    Insertar descripción de la imagen aquí
    Insertar descripción de la imagen aquí

3.2.3 Envío asincrónico y devolución de llamada

  • El productor envía un mensaje y ejecuta directamente la siguiente lógica empresarial sin esperar a que el corredor responda después del envío. Se puede proporcionar un método de devolución de llamada para permitir que el intermediario llame a la devolución de llamada de forma asincrónica para informar al productor del resultado del envío del mensaje. De esta manera, no es necesario bloquear y esperar después del envío, como el bloqueo asincrónico.

  • El efecto es el siguiente:
    Insertar descripción de la imagen aquí

  • El código se muestra a continuación:

    package com.liu.susu.producer;
    
    import org.apache.kafka.clients.producer.*;
    
    import java.util.Properties;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    /**
     * @Description
     * @Author susu
     */
    public class ProducerExample3 {
          
          
    
        public static Properties getProperties(){
          
          
            Properties properties = new Properties();
            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "43.143.190.116:9092");
            properties.put(ProducerConfig.ACKS_CONFIG, "all");
            properties.put(ProducerConfig.RETRIES_CONFIG, "0");
            properties.put(ProducerConfig.BATCH_SIZE_CONFIG, "16348");
            properties.put(ProducerConfig.LINGER_MS_CONFIG, "1");
            properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, "33554432");
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            return properties;
        }
        
        /**
         * 1. 异步发送并回调
         */
        public static void producerSendMore(String topicName) throws ExecutionException, InterruptedException {
          
          
            Properties properties = getProperties();
            //Producer对象
            Producer<String, String> producer = new KafkaProducer<>(properties);
            for (int i = 0; i < 5; i++) {
          
          
                //消息对象
                ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName,"H4-Record-"+i,"H4-R-1000"+i);
    
                //1 发送消息:异步发送并回调
                producer.send(producerRecord, (recordMetadata, exception) -> {
          
          
                    if(exception == null) {
          
          
                        System.out.println("partition-->"+recordMetadata.partition() + ",offset-->"+recordMetadata.offset());
                    }
                    else {
          
          
                        exception.printStackTrace();
                    }
                });
    
                //2 发送消息:异步发送并回调
    //            producer.send(producerRecord, new Callback() {
          
          
    //                @Override
    //                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
          
          
    //                    if(e == null) {
          
          
    //                        System.out.println("partition-->"+recordMetadata.partition() + ",offset-->"+recordMetadata.offset());
    //                    }
    //                    else {
          
          
    //                        e.printStackTrace();
    //                    }
    //                }
    //            });
    
            }
            producer.close();// 要关闭Producer实例
        }
        public static void main(String[] args) throws ExecutionException, InterruptedException{
          
          
            //1. 异步发送并回调
            producerSendMore("susu-topic");
        }
    
    }
    
    

3.2.4 Resumen (envío de bloqueo asíncrono y envío asíncrono)

3.2.3.1 Envío de bloqueo asíncrono

  • Este método puede entenderse como envío sincrónico (es decir, sincronización significa enviar un elemento a la vez).
    • Se deben enviar uno por uno, luego de que llegue la primera respuesta se solicitará la segunda. Se juzgará el resultado de cada mensaje future.get()y se bloqueará hasta que los datos devueltos indiquen que el envío se realizó correctamente, y luego continuará enviando el siguiente mensaje hasta el estado de envío de cada mensaje.
    • De esta manera siSi el envío falla, se volverá a intentar y se generará una excepción., hasta alcanzar el número máximo de reintentos. Este método también garantiza la mayor confiabilidad de los datos y puede registrar el registro de resultados correspondiente.

3.2.3.2 Envío asincrónico

  • Asincrónico significa enviar en lotes.
    • Si se configura en modo asincrónico, puede ejecutar el productor para enviar datos en lotes, lo que mejorará en gran medida el rendimiento del corredor, pero aumentará el riesgo de pérdida de datos.
    • Modo asincrónico, puede enviar uno o varios lotes. La característica es que no necesita esperar la primera respuesta (tenga en cuenta que la unidad aquí es tiempos, porque una sola respuesta puede ser un solo elemento o un lote de datos) , y el segundo se enviará inmediatamente. .

3.2.3.3 Referencia

3.3 Reglas de partición personalizadas del productor (equilibrio de carga)

3.3.1 Aumentar partición a 3

  • Como sigue, 0, 1, 2:
    Insertar descripción de la imagen aquí

3.3.2 Código central

  • como sigue:
    Insertar descripción de la imagen aquí
    Insertar descripción de la imagen aquí

  • MiPartición.java

    package com.liu.susu.producer;
    
    import org.apache.kafka.clients.producer.Partitioner;
    import org.apache.kafka.common.Cluster;
    
    import java.util.Map;
    
    /**
     * @Description
     * @Author susu
     */
    public class MyPartition implements Partitioner {
          
          
    
    
        @Override
        public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
          
          
            String newsKey = key + "";  //格式:"P-Record-"+i
    
            String newKeyNum = newsKey.substring(newsKey.length()-1);//取最后一位
            int keyNum = Integer.parseInt(newKeyNum);
    
            int partition = keyNum % 3;
    
            System.out.println("newsKey--->"+newsKey + ",newKeyNum-->"+newKeyNum+",partition-->"+partition);
    
            return partition;
        }
    
        @Override
        public void close() {
          
          
    
        }
    
        @Override
        public void configure(Map<String, ?> map) {
          
          
    
        }
    
    }
    
    
  • ejemplo:

    package com.liu.susu.producer;
    
    import org.apache.kafka.clients.producer.*;
    
    import java.util.Properties;
    import java.util.concurrent.ExecutionException;
    
    /**
     * @Description
     * @Author susu
     */
    public class ProducerExample4 {
          
          
    
        public static Properties getProperties(){
          
          
            Properties properties = new Properties();
            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "43.143.190.116:9092");
            properties.put(ProducerConfig.ACKS_CONFIG, "all");
            properties.put(ProducerConfig.RETRIES_CONFIG, "0");
            properties.put(ProducerConfig.BATCH_SIZE_CONFIG, "16348");
            properties.put(ProducerConfig.LINGER_MS_CONFIG, "1");
            properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, "33554432");
    
            properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.liu.susu.producer.MyPartition");
    
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
            return properties;
        }
        
        /**
         * 1. 异步发送并回调
         */
        public static void producerSendMore(String topicName) throws ExecutionException, InterruptedException {
          
          
            Properties properties = getProperties();
            //Producer对象
            Producer<String, String> producer = new KafkaProducer<>(properties);
            for (int i = 1; i <= 15; i++) {
          
          
                //消息对象
                ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName,"P-Record-"+i,"P-R-1000"+i);
    
                //发送消息:异步发送并回调
                producer.send(producerRecord, new Callback() {
          
          
                    @Override
                    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
          
          
                        if(e == null) {
          
          
                            System.out.println("partition-->"+recordMetadata.partition() + ",offset-->"+recordMetadata.offset());
                        }
                        else {
          
          
                            e.printStackTrace();
                        }
                    }
                });
    
            }
            producer.close();// 要关闭Producer实例
        }
        public static void main(String[] args) throws ExecutionException, InterruptedException{
          
          
            // 异步发送并回调
            producerSendMore("susu-topic");
        }
    
    }
    
    

3.3.3 Efecto

  • Al utilizar envío y devolución de llamada asincrónicos, el efecto es el siguiente:
    Insertar descripción de la imagen aquí

4. Consumidores

4.1 Configuraciones del consumidor

4.2 Ejemplos de consumo de consumo

4.2.1 Referencia del sitio web oficial

4.2.2 Ejemplo sencillo de introducción: envío automático de compensación

  • En este caso lo consumido no se volverá a consumir, el código es el siguiente:
    Insertar descripción de la imagen aquí

    package com.liu.susu.consumer;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    
    import java.time.Duration;
    import java.util.Arrays;
    import java.util.Properties;
    
    /**
     * @Description
     * @Author susu
     */
    public class ConsumerExample1 {
          
          
    
        public static void consumerTest(){
          
          
            Properties props = new Properties();
            props.setProperty("bootstrap.servers", "IP:9092");
            props.setProperty("group.id", "test");
            props.setProperty("enable.auto.commit", "true");//设置enable.auto.commit意味着自动提交偏移量,其频率由配置auto.commit.interval.ms控制
            props.setProperty("auto.commit.interval.ms", "1000");
            props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    
            /**
             * 消费订阅哪一个topic或者哪几个topic
             *   我这里:消费者订阅了主题susu-topic和susu-topic-2,作为消费者组test的一部分,并配置了group.id。
             */
            consumer.subscribe(Arrays.asList("susu-topic", "susu-topic-2"));
    
            while (true) {
          
          
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));//每100毫秒拉取一次
                for (ConsumerRecord<String, String> record : records)
                    System.out.printf("topic = %s,partition = %d, offset = %d, key = %s, value = %s%n",
                            record.topic(),record.partition(),record.offset(), record.key(), record.value());
            }
        }
    
        public static void main(String[] args) {
          
          
            consumerTest();
        }
    
    }
    
    
  • El efecto es el siguiente:
    Insertar descripción de la imagen aquí

4.2.2 Control de compensación manual

4.2.3.1 Explicación

  • Los usuarios también pueden controlar cuándo un registro se considera consumido, comprometiendo así sus compensaciones, en lugar de depender de que los consumidores confirmen periódicamente las compensaciones consumidas. Esto resulta útil cuando el consumo de un mensaje se combina con alguna lógica de procesamiento, por lo que el mensaje no debe considerarse consumido hasta que haya completado el procesamiento.
  • En este ejemplo, tomaremos un lote de registros y los almacenaremos en la memoria. Cuando tengamos suficientes registros, los insertaremos en la base de datos.Si permitimos que las compensaciones se confirmen automáticamente como en el ejemplo anterior, los registros se consideran consumidos después de que se devuelven al usuario en la encuesta.De esta manera, es posible que nuestro proceso falle después de agrupar los registros, pero antes de insertarlos en la base de datos.
    Para evitar esto solo haremos esto después de insertar el registro correspondiente en la base de datos.
    Confirmar compensaciones manualmente
    . Esto nos brinda un control preciso sobre cuándo se consumen los registros. Esto plantea la posibilidad opuesta: el proceso podría fallar en el intervalo posterior a la inserción en la base de datos pero antes de confirmarse (aunque esto podría ser solo unos pocos milisegundos, es posible). En este caso, el proceso que se hace cargo del consumo consumirá desde el último desplazamiento comprometido e insertará repetidamente el último lote de datos. Utilizando este enfoque, Kafka proporciona lo que a menudo se conoce como una garantía de entrega "al menos una vez", ya que cada registro puede entregarse sólo una vez, peroSe puede copiar en caso de fallo.

4.2.3.2 Código

  • El código se muestra a continuación:
    Insertar descripción de la imagen aquí
    Insertar descripción de la imagen aquí

    package com.liu.susu.consumer;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    
    import java.time.Duration;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Properties;
    
    /**
     * @Description 手动提交
     * @Author susu
     */
    public class ConsumerExample2 {
          
          
    
        public static void consumerTest(){
          
          
            Properties props = new Properties();
            props.setProperty("bootstrap.servers", "43.143.190.116:9092");
            props.setProperty("group.id", "test");
            props.setProperty("enable.auto.commit", "false");//false 手动提交
            props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    
    //        consumer.subscribe(Arrays.asList("susu-topic", "susu-topic-2"));
            consumer.subscribe(Arrays.asList("susu-topic"));
    
            final int minBatchSize = 20;
            List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
    
            while (true) {
          
          
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
          
          
                    buffer.add(record);
                    System.out.printf("topic = %s,partition = %d, offset = %d, key = %s, value = %s%n",
                            record.topic(),record.partition(),record.offset(), record.key(), record.value());
                }
    
                if (buffer.size() >= minBatchSize) {
          
          
                    System.out.println(buffer);
    
                    try {
          
          
                        /**
                         * 这里是业务逻辑,把数据保存到数据库中
                         *    如果失败,则回滚
                         */
    //                insertIntoDb(buffer);
    
                        //如果成功,则手动通知offset提交
                        consumer.commitSync();//消费过之后不会再重复消费
                    } catch (Exception e) {
          
          
                        System.out.println("失败,不提交");//失败不执行commitSync,后续重复发送会消费
                        throw new RuntimeException(e);
                    }
    
                    buffer.clear();
                }
            }
    
    
        }
    
        public static void main(String[] args) {
          
          
            consumerTest();
        }
    
    }
    
    

4.2.3 Cada partición se procesa por separado

4.2.3.1 Explicación

  • El ejemplo anterior se utiliza commitSyncpara marcar todos los registros recibidos como confirmados. En algunos casos, es posible que desee tener más control sobre los registros confirmados especificando compensaciones explícitamente. En este ejemplo nosotrosConfirmar compensaciones después de procesar registros en cada partición

4.2.3.2 Código

  • El código se muestra a continuación:
    Insertar descripción de la imagen aquí

    package com.liu.susu.consumer;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    import org.apache.kafka.clients.consumer.OffsetAndMetadata;
    import org.apache.kafka.common.TopicPartition;
    
    import java.time.Duration;
    import java.util.*;
    
    /**
     * @Description 处理完每个分区中的记录后提交偏移量
     * @Author susu
     */
    public class ConsumerExample3 {
          
          
    
        public static void consumerTest(){
          
          
            Properties props = new Properties();
            props.setProperty("bootstrap.servers", "43.143.190.116:9092");
            props.setProperty("group.id", "test");
            props.setProperty("enable.auto.commit", "false");//false 手动提交
            props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    
    //        consumer.subscribe(Arrays.asList("susu-topic", "susu-topic-2"));
            consumer.subscribe(Arrays.asList("susu-topic"));
    
            try {
          
          
                while(true) {
          
          
                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(Long.MAX_VALUE));
                    // 每个partition单独处理
                    for (TopicPartition partition : records.partitions()) {
          
          
    
                        List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                        for (ConsumerRecord<String, String> record : partitionRecords) {
          
          
    //                        System.out.println(record.offset() + ": " + record.value());
                            System.out.printf("topic = %s,partition = %d, offset = %d, key = %s, value = %s%n",
                                    record.topic(),record.partition(),record.offset(), record.key(), record.value());
                        }
    
                        long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                        // 循环一个partition,提交一次
                        OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(lastOffset + 1);
                        consumer.commitSync(Collections.singletonMap(partition, offsetAndMetadata));
                    }
                }
            } finally {
          
          
                consumer.close();
            }
    
        }
    
        public static void main(String[] args) {
          
          
            consumerTest();
        }
    
    }
    
    
  • El efecto es el siguiente:
    Insertar descripción de la imagen aquí

4.2.3.3 Nota

  • NOTA: El desplazamiento confirmado siempre debe ser el desplazamiento del siguiente mensaje que leerá la aplicación. Por lo tanto, al llamar a commitSync (compensaciones), se debe agregar uno al desplazamiento del último mensaje procesado.

4.2.4 Controlar manualmente qué partición se consume (asignación manual de particiones)

4.2.4.1 Descripción

  • En el ejemplo anterior, nos suscribimos a los temas que nos interesaban y permitimos que Kafka asignara dinámicamente una parte justa de las particiones a estos temas en función de los consumidores activos del grupo. Sin embargo, en algunos casos es posible que necesite un mejor control sobre las particiones específicas asignadas. Por ejemplo:
    • Si un proceso mantiene algún estado local relacionado con esa partición (como un almacén clave-valor en el disco local), entonces solo debe recuperar registros para la partición que mantiene en el disco.
    • Si el proceso en sí tiene alta disponibilidad y se reiniciará en caso de falla (quizás usando un marco de administración de clústeres como YARN, Mesos o instalaciones de AWS, o como parte de un marco de procesamiento de flujo). En este caso, Kafka no necesita detectar el fallo y reasignar las particiones porque el proceso consumidor se reiniciará en otra máquina.
  • Para usar este modo, no es necesario suscribirse al tema mediante suscripción, simplemente llame assign(Collection)con la lista completa de particiones a usar.

4.2.4.2 Código

  • como sigue:
    Insertar descripción de la imagen aquí

    package com.liu.susu.consumer;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    import org.apache.kafka.clients.consumer.OffsetAndMetadata;
    import org.apache.kafka.common.TopicPartition;
    
    import java.time.Duration;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    import java.util.Properties;
    
    /**
     * @Description 指定消费某个分区
     * @Author susu
     */
    public class ConsumerExample4 {
          
          
    
        public static void consumerTest(){
          
          
            Properties props = new Properties();
            props.setProperty("bootstrap.servers", "43.143.190.116:9092");
            props.setProperty("group.id", "test");
            props.setProperty("enable.auto.commit", "false");//false 手动提交
            props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    
            String topicName = "susu-topic";
            TopicPartition partition0 = new TopicPartition(topicName, 0);
            TopicPartition partition1 = new TopicPartition(topicName, 1);
            TopicPartition partition2 = new TopicPartition(topicName, 2);
    
            consumer.assign(Arrays.asList(partition2)); //只有partition2消费
    //        consumer.assign(Arrays.asList(partition0, partition1));  //只有partition0, partition1消费
    
    
            try {
          
          
                while(true) {
          
          
                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(Long.MAX_VALUE));
                    // 每个partition单独处理
                    for (TopicPartition partition : records.partitions()) {
          
          
    
                        List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                        for (ConsumerRecord<String, String> record : partitionRecords) {
          
          
    //                        System.out.println(record.offset() + ": " + record.value());
                            System.out.printf("topic = %s,partition = %d, offset = %d, key = %s, value = %s%n",
                                    record.topic(),record.partition(),record.offset(), record.key(), record.value());
                        }
    
                        long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                        // 循环一个partition,提交一次
                        OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(lastOffset + 1);
                        consumer.commitSync(Collections.singletonMap(partition, offsetAndMetadata));
                    }
                }
            } finally {
          
          
                consumer.close();
            }
    
        }
    
        public static void main(String[] args) {
          
          
            consumerTest();
        }
    
    }
    
    

4.2.4.3 Efecto

  • como sigue:
    Insertar descripción de la imagen aquí

    Insertar descripción de la imagen aquí

4.2.5 Procesamiento multiproceso del consumidor

4.2.5.1 El hilo del consumidor no es seguro

  • Los consumidores de Kafka no son seguros para subprocesos. Todas las E/S de la red se producen dentro del subproceso de la aplicación que llama. Es responsabilidad del usuario garantizar que el acceso multiproceso esté sincronizado correctamente. El acceso no sincronizado dará lugar a ConcurrentModificationException.
  • La única excepción a esta regla es wakeup(), que interrumpe de forma segura las operaciones activas de subprocesos externos. En este caso, el hilo de la operación de bloqueo generará WakeupException. Esto se puede utilizar para cerrar un consumidor de otro hilo.
    • Luego, en un hilo separado, se puede cerrar el consumidor configurando la bandera de cerrado y despertando al consumidor.
      closed.set(true);
      consumer.wakeup ();
      

4.2.5.2 Dos formas de implementar

4.2.5.2.1 Un consumidor por hilo
  • Una opción sencilla es proporcionar a cada hilo su propia instancia de consumidor. Estos son los pros y los contras de este enfoque:
    • Ventaja: este es el más fácil de implementar.
    • Ventajas: suele ser el más rápido porque no se requiere coordinación entre subprocesos.
    • Ventajas: hace que el procesamiento ordenado por partición sea muy fácil de implementar (cada hilo solo procesa los mensajes en el orden en que se reciben).
    • Contras: más consumidores significa más conexiones TCP al clúster (una por subproceso). En general, Kafka maneja las conexiones de manera muy eficiente, por lo que suele representar un costo pequeño.
    • Desventajas: múltiples consumidores significan que se envían más solicitudes al servidor y un poco menos de procesamiento por lotes de datos, lo que puede generar un menor rendimiento de E/S.
    • Desventaja: el número total de subprocesos para todos los procesos estará limitado por el número total de particiones.
4.2.5.2.1 Consumo y procesamiento separados
  • Este enfoque permite que uno o más subprocesos consumidores realicen todo el consumo de datos y entrega la instancia de ConsumerRecords a una cola de bloqueo, que es utilizada por el grupo de subprocesos del procesador que realmente maneja el procesamiento de registros. Esta opción también tiene pros y contras:
    • Ventajas: Esta opción permite escalar el número de consumidores y procesadores de forma independiente. Esto permite que un único consumidor atienda múltiples subprocesos de procesador, evitando restricciones en la partición.
    • Desventajas: Garantizar el orden entre los procesadores requiere una atención especial porque los subprocesos se ejecutarán de forma independiente y, debido a la suerte del tiempo de ejecución del subproceso, es posible que los bloques de datos anteriores se procesen después de los bloques de datos posteriores. Para el procesamiento sin requisitos de pedido, esto no es un problema.
    • Desventaja: confirmar manualmente una ubicación se vuelve más difícil porque requiere la coordinación de todos los subprocesos para garantizar que el procesamiento de esa partición esté completo.
      Hay muchas variantes posibles de este enfoque. Por ejemplo, cada subproceso del procesador puede tener su propia cola, y los subprocesos del consumidor pueden ingresar a estas colas utilizando TopicPartitions para garantizar el consumo ordenado y simplificar el envío.

4.2.5.3 Patrón típico (un consumidor por hilo)

  • El código se muestra a continuación:
    Insertar descripción de la imagen aquí

    package com.liu.susu.consumer.thread;
    
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.apache.kafka.clients.consumer.ConsumerRecords;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    import org.apache.kafka.clients.consumer.OffsetAndMetadata;
    import org.apache.kafka.common.TopicPartition;
    import org.apache.kafka.common.errors.WakeupException;
    
    import java.time.Duration;
    import java.util.*;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * @Description
     * @Author susu
     */
    public class KafkaConsumerRunner implements Runnable {
          
          
        private final AtomicBoolean closed = new AtomicBoolean(false);
        private final KafkaConsumer consumer;
        public KafkaConsumerRunner(KafkaConsumer consumer) {
          
          
            this.consumer = consumer;
        }
    
        @Override
        public void run() {
          
          
            try {
          
          
                consumer.subscribe(Arrays.asList("susu-topic"));//订阅
                while (!closed.get()) {
          
          
                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(10000));
                    // Handle new records
                    for (TopicPartition partition : records.partitions()) {
          
          
    
                        List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                        for (ConsumerRecord<String, String> record : partitionRecords) {
          
          
                            System.out.printf("Thread = %s,topic = %s,partition = %d, offset = %d, key = %s, value = %s%n",
                                    Thread.currentThread().getName(),
                                    record.topic(),record.partition(),record.offset(), record.key(), record.value());
                        }
    
                        long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                        // 循环一个partition,提交一次
                        OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(lastOffset + 1);
                        consumer.commitSync(Collections.singletonMap(partition, offsetAndMetadata));
                    }
                }
            } catch (WakeupException e) {
          
          
                // Ignore exception if closing
                if (!closed.get()) throw e;
            } finally {
          
          
                consumer.close();
            }
        }
    
        // Shutdown hook which can be called from a separate thread
        public void shutdown() {
          
          
            closed.set(true);
            consumer.wakeup();
        }
    
    
        /**
         * 构建 consumer
         * @return consumer
         */
        public static KafkaConsumer<String, String> getKafkaConsumer(){
          
          
            Properties props = new Properties();
            props.setProperty("bootstrap.servers", "43.143.190.116:9092");
            props.setProperty("group.id", "test");
            props.setProperty("enable.auto.commit", "false");//false 手动提交
            props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
            props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
    
            return consumer;
        }
    
        public static void main(String[] args) {
          
          
            KafkaConsumer<String, String> consumer = getKafkaConsumer();
    
            KafkaConsumerRunner runner = new KafkaConsumerRunner(consumer);
    
            Thread thread = new Thread(runner);
            thread.start();
    
    //        runner.shutdown();
        }
    
    }
    
    

4.2.5.4 Separación de consumo y procesamiento (procesamiento del grupo de subprocesos)

4.2.6

4.3

Supongo que te gusta

Origin blog.csdn.net/suixinfeixiangfei/article/details/132223203
Recomendado
Clasificación