Contenedor de escucha de mensajes de Apache Kafka + Spring

1. Recepción de mensajes

Recepción de mensajes: los mensajes se pueden recibir configurando un MessageListenerContainer y proporcionando un detector de mensajes o utilizando la anotación @KafkaListener. En este capítulo, explicamos principalmente cómo recibir mensajes configurando MessageListenerContainer y proporcionando un detector de mensajes.

1.1, escucha de mensajes

Cuando se utiliza un contenedor de escucha de mensajes, se debe proporcionar un oyente para recibir los datos. Actualmente existen ocho interfaces que admiten escuchas de mensajes:

public interface MessageListener<K, V> {
    
     
     // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。
    void onMessage(ConsumerRecord<K, V> data);
}

public interface AcknowledgingMessageListener<K, V> {
    
     
    // 当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。
    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment);
}

public interface ConsumerAwareMessageListener<K, V> extends MessageListener<K, V> {
    
     
    // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(ConsumerRecord<K, V> data, Consumer<?, ?> consumer);

}

public interface AcknowledgingConsumerAwareMessageListener<K, V> extends MessageListener<K, V> {
    
     
    //当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

public interface BatchMessageListener<K, V> {
    
     
   //当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。使用此接口时不支持 AckMode.RECORD,因为侦听器会获得完整的批次。
    void onMessage(List<ConsumerRecord<K, V>> data);

}

public interface BatchAcknowledgingMessageListener<K, V> {
    
     
    // 当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。
    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment);

}

public interface BatchConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> {
    
     
    // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。使用此接口时不支持 AckMode.RECORD,因为侦听器会获得完整的批次。提供对 Consumer 对象的访问。
    void onMessage(List<ConsumerRecord<K, V>> data, Consumer<?, ?> consumer);

}

public interface BatchAcknowledgingConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> {
    
     
    //当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

Nota: 1. Los objetos consumidores no son seguros para subprocesos; 2. No debe ejecutar ningún método Consumer<?, ?> que afecte la posición del consumidor y/o el desplazamiento comprometido en el oyente; el contenedor necesita administrar esta información .

2. Contenedor de monitoreo de mensajes

2.1 Método de implementación

MessageListenerContainer proporciona dos implementaciones:
1. KafkaMessageListenerContainer,
2. ConcurrentMessageListenerContainer

2.1.1、Contenedor KafkaMessageListener

2.1.1.1 Conceptos básicos

KafkaMessageListenerContainer recibe todos los mensajes de todos los temas o particiones en un solo hilo. Delegue ConcurrentMessageListenerContainer en una o más instancias de KafkaMessageListenerContainer para proporcionar consumo de subprocesos múltiples.

  • A partir de la versión 2.2.7, se puede agregar un contenedor de escucha RecordInterceptor; se llamará antes de que se llame al escucha para permitir la inspección o modificación del registro. Si el interceptor devuelve nulo, no se llamará al oyente.
  • A partir de la versión 2.7, tiene un método adicional que se llama después de que el oyente sale (generalmente o lanzando una excepción).
  • Los interceptores por lotes proporcionan una funcionalidad similar a los oyentes por lotes.
  • Además, ConsumerAwareRecordInterceptor (y BatchInterceptor) brindan acceso a Consumer<?, ?>. Por ejemplo, esto se puede utilizar para acceder a métricas de consumidores en interceptores.
  • CompositeRecordInterceptor y CompositeBatchInterceptor pueden llamar a varios interceptores.
  • De forma predeterminada, cuando se utilizan transacciones, se llama al interceptor después de que la transacción haya comenzado. A partir de la versión 2.3.4, la propiedad interceptBeforeTx del contenedor de escucha se puede configurar para invocar el interceptor antes de que comience la transacción.
  • A partir de la versión 2.3.8, 2.4.6, ConcurrentMessageListenerContainer admite membresía estática cuando la simultaneidad es mayor que 1. El sufijo de group.instance.id es -n, comenzando desde 1. Esto, junto con aumentar el valor de session.timeout.ms, se puede utilizar para reducir los eventos de reequilibrio, por ejemplo, cuando se reinicia una instancia de aplicación.
  • La membresía estática está destinada a mejorar la disponibilidad de aplicaciones de transmisión, grupos de consumidores y otras aplicaciones creadas sobre el protocolo de reequilibrio de grupos. El protocolo de reequilibrio depende del coordinador del grupo para asignar ID de entidad a los miembros del grupo. Estas identificaciones generadas son de corta duración y cambian a medida que los miembros se reinician y se vuelven a unir. Para aplicaciones basadas en consumidores, esta "membresía dinámica" puede llevar a la reasignación de grandes porciones de tareas a diferentes instancias durante operaciones administrativas como implementaciones de código, actualizaciones de configuración y reinicios periódicos. Para aplicaciones con estado grandes, las tareas aleatorias pueden tardar mucho en restaurar su estado local antes de ser procesadas, lo que resulta en una indisponibilidad parcial o total de la aplicación. Inspirado en esta observación, el protocolo de gestión de grupos de Kafka permite a los miembros del grupo proporcionaridentificación de entidad persistente. Según estos ID, la membresía del grupo sigue siendo la misma, por lo que no se activa ningún reequilibrio.

Asimismo, los interceptores no deben implementar ningún método que afecte la posición del consumidor y/o las compensaciones comprometidas; el contenedor necesita gestionar esta información.

Si el interceptor muta el registro (creando un nuevo registro), el tema, la partición y el desplazamiento deben permanecer sin cambios para evitar efectos secundarios no deseados, como la pérdida de registros.

2.1.1.2, Cómo utilizar KafkaMessageListenerContainer

  • Constructor KafkaMessageListenerContainer

    public KafkaMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                      ContainerProperties containerProperties)
    

    El constructor recibe información de ConsumerFactory sobre los temas y particiones del objeto, así como otras configuraciones.

  • La propiedad del contenedor (ContainerProperties) contiene 3 constructores y los presentaremos uno por uno a continuación.
    1. Tome TopicPartitionOffset como parámetro

    public ContainerProperties(TopicPartitionOffset... topicPartitions)
    

    El constructor toma una matriz de parámetros TopicPartitionOffset para indicar explícitamente qué particiones usará el contenedor (usando el método de asignación del consumidor) con un desplazamiento inicial opcional. De forma predeterminada, los valores positivos son compensaciones absolutas y los valores negativos son relativos al último desplazamiento actual dentro de la partición. TopicPartitionOffset proporciona al constructor un parámetro adicional, booleano Si es verdadero, el desplazamiento inicial (positivo o negativo) relativo a la posición actual del consumidor cuando se inicia el contenedor.
    2. Tome String como parámetro

    public ContainerProperties(String... topics)
    

    El constructor toma una variedad de temas y Kafka asigna particiones según los atributos group.id: asigna particiones en grupos
    3, tomando Patrón como parámetro

    public ContainerProperties(Pattern topicPattern)
    

    Este constructor utiliza un patrón de expresión regular para seleccionar temas.

  • Cómo asignar el oyente al contenedor
    Con el oyente, también está el contenedor, ¿cómo asignar el oyente al contenedor? . Para asignar un MessageListener a un contenedor, utilice el método ContainerProps.setMessageListener al crear el contenedor:

    ContainerProperties containerProps = new ContainerProperties("topic1", "topic2");
    containerProps.setMessageListener(new MessageListener<Integer, String>() {
          
          
      ...
    });
    DefaultKafkaConsumerFactory<Integer, String> cf =
                          new DefaultKafkaConsumerFactory<>(consumerProps());
    KafkaMessageListenerContainer<Integer, String> container =
                          new KafkaMessageListenerContainer<>(cf, containerProps);
    return container;
    

    Tenga en cuenta que al crear DefaultKafkaConsumerFactory, usar un constructor que acepte solo las propiedades anteriores significa seleccionar las clases deserializadoras de clave y valor de la configuración. Alternativamente, se puede pasar una instancia deserializador al constructor DefaultKafkaConsumerFactory para claves y/o valores, en cuyo caso todos los consumidores comparten la misma instancia. Otra opción es proporcionar un Proveedor (desde la versión 2.3), que se utilizará para obtener una instancia de Deserializer separada para cada consumidor:

     DefaultKafkaConsumerFactory<Integer, CustomValue> cf =
                          new DefaultKafkaConsumerFactory<>(consumerProps(), null, () -> new      CustomValueDeserializer());
     KafkaMessageListenerContainer<Integer, String> container =
                          new KafkaMessageListenerContainer<>(cf, containerProps);
    return container;
    

A partir de la versión 2.3.5, se introdujo una nueva propiedad de contenedor llamada AuthorizationExceptionRetryInterval. Esto hace que el contenedor vuelva a intentar recibir mensajes después de recibir cualquier AuthorizationException de KafkaConsumer. Esto puede suceder, por ejemplo, cuando a un usuario configurado se le niega la lectura de un tema en particular. La definición de AuthorizationExceptionRetryInterval debería ayudar a que la aplicación se reanude inmediatamente después de otorgar los permisos adecuados.

2.1.2、Contenedor de escucha de mensajes concurrentes

ConcurrentMessageListenerContainer tiene solo un constructor que es similar a KafkaListenerContainer.

public ConcurrentMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                            ContainerProperties containerProperties)

Tiene una propiedad de concurrencia, la función de esta propiedad es crear varias instancias de KafkaMessageListenerContainer. Por ejemplo: contenedor.setConcurrency(3) crea tres instancias de KafkaMessageListenerContainer.

Al escuchar varios temas, es posible que la distribución de particiones predeterminada no sea la que esperamos. Por ejemplo, si hay 3 temas con 5 particiones cada uno y queremos usar concurrencia = 15, pero solo veremos 5 consumidores activos, a cada uno de ellos se le asignó una partición de cada tema, y ​​los otros 10 consumidores están inactivos. Esto se debe a que el Kafka PartitionAssignor predeterminado es RangeAssignor. En este caso, debemos considerar el uso de RoundRobinAssignor, que asigna particiones a todos los consumidores. Luego, a cada consumidor se le asigna un tema o partición. Podemos configurar la propiedad del consumidor partición.assignment.strategy en las propiedades proporcionadas a DefaultKafkaConsumerFactory para cambiar PartitionAssignor. (ConsumerConfigs. PARTITION_ASSIGNMENT_STRATEGY_CONFIG).
Esto se puede hacer en springboot:
spring.kafka.consumer.properties.partition.assignment.strategy=
org.apache.kafka.clients.consumer.RoundRobinAssignor

Cuando la propiedad del contenedor está configurada con TopicPartitionOffset, ConcurrentMessageListenerContainer distribuye instancias de TopicPartitionOffset entre las instancias delegadas de KafkaMessageListenerContainer.

Supongamos que se proporcionan 6 instancias de TopicPartitionOffset con una simultaneidad de 3; cada contenedor tiene dos particiones. Con cinco
instancias de TopicPartitionOffset, dos contenedores obtienen dos particiones y el tercer contenedor obtiene una partición. Si el número de simultaneidad es mayor que el número de TopicPartitions, reduzca el número de simultaneidad para que cada contenedor obtenga una partición.

3. Compensación

Spring proporciona varias opciones de compensación: si la propiedad del consumidor enable.auto.commit es verdadera, Kafka enviará automáticamente la compensación de acuerdo con su configuración. Si es falso, el contenedor admite varias configuraciones de AckMode. El modo de confirmación predeterminado es BATCH.

A partir de la versión 2.3, el marco establece enable.auto.commit en falso a menos que se establezca explícitamente en la configuración. Anteriormente, si no se establecía esta propiedad, se usaba el valor predeterminado de Kafka (verdadero).

El método Consumer poll() devuelve uno o más ConsumerRecords. Llame a MessageListener para cada registro. La siguiente lista describe las acciones que realiza el contenedor para cada AckMode (cuando no se utilizan transacciones):

  • RECORD: confirma el desplazamiento cuando el oyente regresa después de procesar el registro.

  • LOTE: confirma las compensaciones cuando se hayan procesado todos los registros devueltos por poll().

  • TIEMPO: cuando todos los registros devueltos por poll() hayan sido procesados, confirme el desplazamiento cada vez que haya transcurrido el tiempo de confirmación desde la última confirmación.

  • CONTAR: confirma el desplazamiento cuando se hayan procesado todos los registros devueltos por poll(), siempre que se hayan recibido registros ackCount desde la última confirmación.

  • COUNT_TIME: como TIME y COUNT, pero realiza una confirmación si alguna de las condiciones es verdadera.

  • MANUAL: El oyente de mensajes es responsable de los reconocimientos de reconocimiento(). Posteriormente se aplica la misma semántica que BATCH.

  • MANUAL_IMMEDIATE: el desplazamiento se confirma inmediatamente cuando el oyente llama al método Acknowledgment.acknowledge().

Cuando se utilizan transacciones, se envían compensaciones a la transacción, semánticamente equivalente a RECORD o BATCH, según el tipo de escucha (registro o lote). MANUAL y MANUAL_IMMEDIATE requieren que el oyente sea AcknowledingMessageListener o BatchAcknowledgingMessageListener.

Utilice el método commitSync() o commitAsync() en el consumidor, según la propiedad del contenedor syncCommits. De forma predeterminada, syncCommits es verdadero.

La sugerencia personal del autor sugiere configurar: ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG es falso.

A partir de la versión 2.3, la interfaz de Reconocimiento agrega dos métodos nack(long sleep) y nack(int index, long sleep). El primero se utiliza con oyentes de discos y el segundo con oyentes por lotes. Llamar al método incorrecto para el tipo de escucha generará una excepción IllegalStateException. Antes de eso era así:

public interface Acknowledgment {
    
    

    void acknowledge();

}
  • Si desea enviar un lote parcial, utilice nack().
  • Cuando utilice transacciones, configure AckMode en MANUAL;
  • Llamar a nack() enviará el desplazamiento del registro procesado exitosamente a la transacción.
  • nack() solo se puede invocar en el hilo del consumidor que llamó al oyente.
  • Cuando se llama a nack(), se confirman todas las compensaciones pendientes, se descartan los registros restantes del sondeo anterior y se realizan búsquedas en sus particiones para que los registros fallidos y no procesados ​​se vuelvan a entregar en el siguiente sondeo ( ).
  • Al configurar el parámetro de suspensión, el hilo del consumidor se puede pausar antes de volver a enviarlo. Esto es similar a cómo se generan excepciones cuando el contenedor está configurado con SeekToCurrentErrorHandler.

Cuando se utiliza la asignación de particiones a través de la administración de grupos, es muy importante asegurarse de que el parámetro de suspensión (más el tiempo dedicado a procesar registros previamente sondeados) sea menor que la propiedad max.poll.interval.ms del consumidor .

4. El contenedor de escucha se inicia automáticamente.

El contenedor de escucha implementa SmartLifecycle y los valores predeterminados de autoStartup son verdaderos. El contenedor se inicia en una etapa tardía (Integer.MAX-VALUE - 100). Otros componentes que implementan SmartLifecycle para procesar datos de los oyentes deberían comenzar en una etapa temprana. -100 deja espacio para etapas posteriores, lo que permite que los componentes se inicien automáticamente después del contenedor.

Supongo que te gusta

Origin blog.csdn.net/qq_35241329/article/details/132312378
Recomendado
Clasificación