Contêiner de escuta de mensagens do Apache Kafka + Spring

1. Recepção de mensagens

Recepção de mensagens: As mensagens podem ser recebidas configurando um MessageListenerContainer e fornecendo um ouvinte de mensagens ou usando a anotação @KafkaListener. Neste capítulo, explicamos principalmente como receber mensagens configurando MessageListenerContainer e fornecendo um ouvinte de mensagens.

1.1, ouvinte de mensagens

Ao usar um contêiner de escuta de mensagens, um ouvinte deve ser fornecido para receber os dados. Existem atualmente oito interfaces que suportam ouvintes de mensagens:

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. Os objetos Consumer não são thread-safe; 2. Você não deve executar nenhum método Consumer<?, ?> que afete a posição do consumidor e/ou o deslocamento confirmado no listener; o contêiner precisa gerenciar essas informações .

2. Contêiner de monitoramento de mensagens

2.1. Método de implementação

MessageListenerContainer fornece duas implementações:
1. KafkaMessageListenerContainer,
2. ConcurrentMessageListenerContainer

2.1.1、KafkaMessageListenerContainer

2.1.1.1. Conceitos básicos

KafkaMessageListenerContainer recebe todas as mensagens de todos os tópicos ou partições em um único thread. Delegue ConcurrentMessageListenerContainer a uma ou mais instâncias de KafkaMessageListenerContainer para fornecer consumo multithread.

  • A partir da versão 2.2.7, um contêiner de ouvinte RecordInterceptor pode ser adicionado; ele será chamado antes do ouvinte ser chamado para permitir a inspeção ou modificação do registro. Se o interceptor retornar nulo, o ouvinte não será chamado.
  • A partir da versão 2.7, ele possui um método adicional que é chamado após a saída do ouvinte (geralmente ou lançando uma exceção).
  • Batch Interceptors fornecem funcionalidade semelhante aos Batch Listeners.
  • Além disso, ConsumerAwareRecordInterceptor (e BatchInterceptor) fornecem acesso a Consumer<?, ?>. Por exemplo, isso pode ser usado para acessar métricas de consumo em interceptadores.
  • CompositeRecordInterceptor e CompositeBatchInterceptor podem chamar vários interceptadores.
  • Por padrão, ao usar transações, o interceptor é chamado após o início da transação. A partir da versão 2.3.4, a propriedade interceptBeforeTx do contêiner do ouvinte pode ser configurada para invocar o interceptor antes do início da transação.
  • A partir da versão 2.3.8, 2.4.6, ConcurrentMessageListenerContainer oferece suporte à associação estática quando a simultaneidade é maior que 1. O sufixo de group.instance.id é -n, começando em 1. Isto, juntamente com o aumento do valor de session.timeout.ms, pode ser usado para reduzir eventos de rebalanceamento, por exemplo, quando uma instância de aplicativo é reiniciada.
  • A associação estática visa melhorar a disponibilidade de aplicativos de streaming, grupos de consumidores e outros aplicativos criados com base no protocolo de reequilíbrio de grupo. O protocolo de rebalanceamento depende do coordenador do grupo para atribuir IDs de entidade aos membros do grupo. Esses IDs gerados têm vida curta e mudam conforme os membros reiniciam e ingressam novamente. Para aplicativos baseados no consumidor, essa "associação dinâmica" pode levar à reatribuição de grandes porções de tarefas a diferentes instâncias durante operações administrativas, como implantações de código, atualizações de configuração e reinicializações periódicas. Para aplicativos grandes com estado, as tarefas aleatórias podem levar muito tempo para restaurar seu estado local antes de serem processadas, resultando na indisponibilidade parcial ou total do aplicativo. Inspirado nesta observação, o protocolo de gerenciamento de grupo de Kafka permite que os membros do grupo forneçamID de entidade persistente. Com base nesses IDs, a associação ao grupo permanece a mesma e, portanto, nenhum reequilíbrio é acionado.

Da mesma forma, os interceptadores não devem implementar quaisquer métodos que afetem a posição do consumidor e/ou compensações comprometidas, o contêiner precisa gerenciar essas informações.

Se o interceptador alterar o registro (criando um novo registro), o tópico, a partição e o deslocamento deverão permanecer inalterados para evitar efeitos colaterais não intencionais, como perda de registros.

2.1.1.2, Como usar KafkaMessageListenerContainer

  • Construtor KafkaMessageListenerContainer

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

    O construtor recebe informações do ConsumerFactory sobre os tópicos e partições do objeto, bem como outras configurações.

  • A propriedade container (ContainerProperties) contém 3 construtores e iremos apresentá-los um por um a seguir.
    1. Tome TopicPartitionOffset como parâmetro

    public ContainerProperties(TopicPartitionOffset... topicPartitions)
    

    O construtor usa uma matriz de parâmetros TopicPartitionOffset para indicar explicitamente quais partições o contêiner deve usar (usando o método de atribuição do consumidor ()) com um deslocamento inicial opcional. Por padrão, os valores positivos são deslocamentos absolutos e os valores negativos são relativos ao último deslocamento atual dentro da partição. TopicPartitionOffset fornece um construtor com um parâmetro adicional, booleano. Se verdadeiro, o deslocamento inicial (positivo ou negativo) relativo à posição atual do consumidor quando o contêiner é iniciado.
    2. Tome String como parâmetro

    public ContainerProperties(String... topics)
    

    O construtor pega uma variedade de tópicos e Kafka atribui partições com base em atributos group.id——atribui partições em grupos
    3, tomando Pattern como parâmetro

    public ContainerProperties(Pattern topicPattern)
    

    Este construtor usa uma expressão regular Pattern para selecionar tópicos.

  • Como atribuir o ouvinte ao container
    Com o ouvinte, existe também o container, como atribuir o ouvinte ao container? . Para atribuir um MessageListener a um contêiner, use o método ContainerProps.setMessageListener ao criar o contêiner:

    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;
    

    Observe que ao criar o DefaultKafkaConsumerFactory, usar um construtor que aceita apenas as propriedades acima significa pegar as classes desserializadoras de chave e valor da configuração. Alternativamente, uma instância desserializadora pode ser passada para o construtor DefaultKafkaConsumerFactory para chaves e/ou valores, caso em que todos os consumidores compartilham a mesma instância. Outra opção é fornecer um Fornecedor (desde a versão 2.3), que será utilizado para obter uma instância separada do Deserializer 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 da versão 2.3.5, uma nova propriedade de contêiner chamada AuthorizationExceptionRetryInterval foi introduzida. Isso faz com que o contêiner tente novamente receber mensagens após obter qualquer AuthorizationException do KafkaConsumer. Isso pode acontecer, por exemplo, quando um usuário configurado é negado a leitura de um determinado tópico. A definição deauthorExceptionRetryInterval deve ajudar o aplicativo a ser retomado imediatamente após conceder as permissões apropriadas.

2.1.2、ConcurrentMessageListenerContainer

ConcurrentMessageListenerContainer possui apenas um construtor semelhante ao KafkaListenerContainer.

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

Possui uma propriedade de simultaneidade, a função desta propriedade é criar várias instâncias de KafkaMessageListenerContainer. Por exemplo: container.setConcurrency(3) cria três instâncias KafkaMessageListenerContainer.

Ao ouvir vários tópicos, a distribuição de partição padrão pode não ser a que esperamos. Por exemplo, se houver 3 tópicos com 5 partições cada, e quisermos usar simultaneidade=15, mas veremos apenas 5 consumidores ativos, cada um atribuído a uma partição de cada tópico, e os outros 10 consumidores estão ociosos. Isso ocorre porque o Kafka PartitionAssignor padrão é RangeAssignor. Para este caso, precisamos considerar o uso do RoundRobinAssignor, que atribui partições a todos os consumidores. Em seguida, cada consumidor recebe um tópico ou partição. Podemos definir a propriedade do consumidor partição.assignment.strategy nas propriedades fornecidas para DefaultKafkaConsumerFactory para alterar o PartitionAssignor. (ConsumerConfigs. PARTITION_ASSIGNMENT_STRATEGY_CONFIG).
Isso pode ser feito no springboot:
spring.kafka.consumer.properties.partition.assignment.strategy=
org.apache.kafka.clients.consumer.RoundRobinAssignor

Quando a propriedade do contêiner é configurada com TopicPartitionOffset, ConcurrentMessageListenerContainer distribui instâncias de TopicPartitionOffset entre instâncias delegantes de KafkaMessageListenerContainer.

Suponha que 6 instâncias de TopicPartitionOffset sejam fornecidas com uma simultaneidade de 3; cada contêiner tem duas partições. Com cinco
instâncias de TopicPartitionOffset, dois contêineres recebem duas partições e o terceiro contêiner recebe uma partição. Se o número de simultaneidade for maior que o número de TopicPartitions, reduza o número de simultaneidade para que cada contêiner obtenha uma partição.

3. Deslocamento

Spring oferece várias opções de deslocamento. Se a propriedade do consumidor enable.auto.commit for verdadeira, o Kafka enviará automaticamente o deslocamento de acordo com sua configuração. Se for falso, o contêiner oferece suporte a várias configurações de AckMode. O AckMode padrão é LOTE.

A partir da versão 2.3, a estrutura define enable.auto.commit como false, a menos que seja explicitamente definido na configuração. Anteriormente, se esta propriedade não fosse definida, o padrão Kafka (true) era usado.

O método consumer poll() retorna um ou mais ConsumerRecords. Chame o MessageListener para cada registro. A lista a seguir descreve as ações que o contêiner executa para cada AckMode (quando as transações não são usadas):

  • RECORD: confirma o deslocamento quando o ouvinte retorna após processar o registro.

  • LOTE: confirma as compensações quando todos os registros retornados por poll() foram processados.

  • TEMPO: Quando todos os registros retornados por poll() forem processados, confirme o deslocamento sempre que o ackTime desde o último commit tiver passado.

  • COUNT: Confirma o deslocamento quando todos os registros retornados por poll() tiverem sido processados, desde que os registros ackCount tenham sido recebidos desde o último commit.

  • COUNT_TIME: como TIME e COUNT, mas faz um commit se qualquer uma das condições for verdadeira.

  • MANUAL: O ouvinte da mensagem é responsável pelas confirmações reconhecimento(). Posteriormente, aplica-se a mesma semântica de BATCH.

  • MANUAL_IMMEDIATE: O deslocamento é confirmado imediatamente quando o ouvinte chama o método Acknowledgment.acknowledge().

Ao utilizar transações, são enviados offsets para a transação, semanticamente equivalentes a RECORD ou BATCH, dependendo do tipo de listener (registro ou lote). MANUAL e MANUAL_IMMEDIATE exigem que o ouvinte seja um AcknowledgingMessageListener ou BatchAcknowledgingMessageListener.

Use o método commitSync() ou commitAsync() no consumidor, dependendo da propriedade do contêiner syncCommits. Por padrão,syncCommits é verdadeiro.

A sugestão pessoal do autor sugere a configuração: ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG é falso.

A partir da versão 2.3, a interface Acknowledgment adiciona dois métodos nack(long sleep) e nack(int index, long sleep). O primeiro é usado com ouvintes de registro e o segundo com ouvintes em lote. Chamar o método errado para o tipo de ouvinte gerará uma IllegalStateException. Antes ele era assim:

public interface Acknowledgment {
    
    

    void acknowledge();

}
  • Se você quiser enviar um lote parcial, use nack().
  • Ao usar transações, defina AckMode como MANUAL;
  • Chamar nack() enviará o deslocamento do registro processado com sucesso para a transação.
  • nack() só pode ser chamado no thread consumidor que chamou o ouvinte.
  • Quando nack() é chamado, todos os deslocamentos pendentes são confirmados, os registros restantes da pesquisa anterior são descartados e pesquisas são realizadas em suas partições para que os registros com falha e não processados ​​sejam reenviados na próxima pesquisa ( ).
  • Ao definir o parâmetro sleep, o encadeamento do consumidor pode ser pausado antes da nova entrega. Isso é semelhante a como as exceções são lançadas quando o contêiner é configurado com um SeekToCurrentErrorHandler.

Ao usar a alocação de partição por meio do gerenciamento de grupo, é muito importante garantir que o parâmetro sleep (mais o tempo gasto no processamento de registros pesquisados ​​anteriormente) seja menor que a propriedade max.poll.interval.ms do consumidor .

4. O contêiner do ouvinte é iniciado automaticamente

O contêiner ouvinte implementa SmartLifecycle e os padrões autoStartup são verdadeiros. O contêiner é iniciado posteriormente (Integer.MAX-VALUE - 100). Outros componentes que implementam o SmartLifecycle para processar dados de ouvintes devem começar numa fase inicial. -100 deixa espaço para estágios posteriores, permitindo que os componentes sejam iniciados automaticamente após o contêiner.

Acho que você gosta

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