Grupo de Consumidores (CG ): Um grupo de consumidores que consiste em vários consumidores. A condição para formar um grupo de consumidores é que todos os consumidores tenham o mesmo groupid.
• Cada consumidor do grupo de consumidores é responsável por consumir dados de diferentes partições e uma partição só pode ser consumida por um consumidor do grupo.
• Os grupos de consumidores não afetam uns aos outros. Todos os consumidores pertencem a um determinado grupo de consumidores, ou seja, um grupo de consumidores é logicamente um assinante.
Processo de inicialização do grupo de consumidores
1. Coordenador: Auxilia na inicialização de grupos de consumidores e na alocação de partições
seleção do nó coordenador = valor do hashcode do groupid % 50 (número de partições para __consumer_offsets)
1) Cada consumidor envia uma solicitação JoinGroup
2) Selecione um consumidor como líder
3) Envie o tópico a ser consumido para o consumidor líder
4) O líder será responsável por formular planos de consumo
5) Envie o plano de consumo para o coordenador
6) O Coordenador envia o plano de consumo para cada consumidor
7) Cada consumidor manterá uma pulsação com o coordenador (padrão 3s), uma vez esgotado o tempo limite (session.timeout.ms=45s), o consumidor será removido e o rebalanceamento será acionado; ou o consumidor demora muito para processar as mensagens ( max.poll.interval.ms5min), também aciona o rebalanceamento
Observação: o ID do grupo de consumidores deve ser configurado no código da API do consumidor. Se o consumidor for iniciado a partir da linha de comando sem preencher o ID do grupo de consumidores, um ID aleatório do grupo de consumidores será preenchido automaticamente
Código
public class CustomConsumer {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.6.100:9092");
// 配置序列化 必须
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
// 配置消费者组(组名任意起名) 必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<String, String>(properties);
// 注册要消费的主题(可以消费多个主题)
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 拉取数据打印
while (true) {
// 设置 1s 中消费一批数据
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
Designar para consumir uma determinada partição
public class CustomConsumer {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.6.100:9092");
// 配置序列化 必须
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
// 配置消费者组(必须),名字可以任意起
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<>(properties);
// 消费某个主题的某个分区数据
ArrayList<TopicPartition> topicPartitions = new
ArrayList<>();
topicPartitions.add(new TopicPartition("first", 0));
kafkaConsumer.assign(topicPartitions);
while (true){
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
Estratégia de alocação de partição
Quando vários consumidores começam a consumir, os consumidores consumirão automaticamente em partições e cada consumidor consumirá o conteúdo da partição correspondente
Kafka tem quatro estratégias principais de alocação de partições: Range, RoundRobin, Sticky e CooperativeSticky. A estratégia de atribuição de partição pode ser modificada configurando o parâmetro partition.assignment.strategy. A estratégia padrão é Range + CooperativeSticky. Kafka pode usar várias estratégias de alocação de partições ao mesmo tempo
O intervalo é para cada tópico. Primeiro, as partições no mesmo tópico são classificadas por número de série e os consumidores são classificados em ordem alfabética
Se houver 7 partições e 3 consumidores agora, as partições classificadas serão 0,1,2,3,4,5,6; depois que os consumidores forem classificados, eles serão C0,C1,C2, através do número de partições / contagem de consumidores para determinar quantas partições cada consumidor deve consumir. Caso contrário, os primeiros consumidores consumirão mais 1 partição.
RoundRobin e rebalanceamento
A estratégia de partição de polling RoundRobin é listar todas as partições e todos os consumidores, classificá-los de acordo com o hashcode e, finalmente, alocar partições para cada consumidor por meio do algoritmo de polling.
Reequilibrar :
Pare o consumidor 0 e reenvie a mensagem (dentro de 45s)
A tarefa do consumidor nº 0 dividirá a pesquisa de dados em diferentes áreas de acordo com o método RoundRobin, que será executado por outros consumidores
(Após 45s) O consumidor 0 foi expulso do grupo de consumidores
// Modifica a estratégia de atribuição de partição
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFI
G, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
Pegajoso e reequilibrado
Sticky partition é um tipo de estratégia de alocação introduzida pelo Kafka desde a versão 0.11.x. Primeiro, as partições serão colocadas nos consumidores da forma mais uniforme possível. Quando houver um problema com consumidores no mesmo grupo de consumidores, a partição alocada original será mantida tanto quanto possível, não muda.
Semelhante ao intervalo, mas as partições adesivas são aleatórias
// Modifica a estratégia de alocação de partição
ArrayList<String> startegys = new ArrayList<>();
startegys.add("org.apache.kafka.clients.consumer.StickyAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
startegys);
deslocamento de compensação
O offset existe no tópico do sistema para evitar interação com zookeeper, o que traz muita comunicação de rede
Visualize o conteúdo do tópico do sistema, adicione a configuração exclude.internal.topics=false no arquivo de configuração config/consumer.properties,
O padrão é true, o que significa que os temas do sistema não podem ser consumidos. Para visualizar os dados do tema do sistema, este parâmetro é modificado para false
consumidor
bin/kafka-console-consumer.sh --bootstrap-server 192.168.100:9092 --topic test --group test
Veja o tópico de consumo do consumidor __consumer_offsets
bin/kafka-console-consumer.sh --topic
__consumer_offsets --bootstrap-server 192.168.100:9092192.168.100:9092 --consumer.config config/consumer.properties --formatter
"kafka.coordinator.group.GroupMetadataManager\$ OffsetsMessageForm
atter" --desde-início
O tópico __consumer_offsets usa chave e valor para armazenar dados. A chave é group.id+topic+ número da partição e o valor é o valor de deslocamento atual. De vez em quando, o Kafka compactará esse tópico internamente, ou seja, cada group.id+topic+partition number manterá os dados mais recentes
Comprometer compensações automaticamente
enable.auto.commit: se a função de deslocamento de confirmação automática deve ser ativada, o padrão é true
auto.commit.interval.ms: O intervalo de tempo para confirmação automática de deslocamentos, o padrão é 5s
// Se deve enviar automaticamente o deslocamento
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,
true);
//O período de tempo para enviar o deslocamento é 1000ms, o padrão é 5s
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,
1000);
Confirme manualmente o deslocamento
Há duas maneiras de confirmar manualmente os deslocamentos: commitSync (commit síncrono) e commitAsync (commit assíncrono). O mesmo ponto entre os dois é que o deslocamento mais alto do lote de dados enviado desta vez será enviado; a diferença é que o envio síncrono bloqueia o encadeamento atual até que o envio seja bem-sucedido e falhará automaticamente e tentará novamente; enquanto o envio assíncrono não not Falha ao tentar novamente mecanismo, por isso é possível enviar falhou.
• commitSync (commit síncrono): Você deve esperar que o deslocamento seja confirmado antes de consumir o próximo lote de dados.
• commitAsync (commit assíncrono): Após enviar a solicitação de compensação de commit, comece a consumir o próximo lote de dados.
Confirmar deslocamento de forma síncrona
public class CustomConsumerByHandSync {
public static void main(String[] args) {
// 1. 创建 kafka 消费者配置类
Properties properties = new Properties();
// 2. 添加配置参数
// 添加连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.6.100:9092");
// 配置序列化 必须
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringDeserializer");
// 配置消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 是否自动提交 offset
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,
false);
//3. 创建 kafka 消费者
KafkaConsumer<String, String> consumer = new
KafkaConsumer<>(properties);
//4. 设置消费主题 形参是列表
consumer.subscribe(Arrays.asList("first"));
//5. 消费数据
while (true){
// 读取消息
ConsumerRecords<String, String> consumerRecords =
consumer.poll(Duration.ofSeconds(1));
// 输出消息
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord.value());
}
// 同步提交 offset
consumer.commitSync();
}
}
}
Confirmar deslocamento de forma assíncrona
// 异步提交 offset
consumer.commitAsync();
Especificar o consumo de compensação
auto.offset.reset = mais antigo | mais recente | nenhum O padrão é mais recente
O que fazer quando não há deslocamento inicial no Kafka (primeiro consumo pelo grupo de consumidores) ou quando o deslocamento atual não existe mais no servidor (por exemplo, os dados foram excluídos)?
(1) mais cedo: redefine automaticamente o deslocamento para o deslocamento mais antigo, --desde-início.
(2) mais recente (padrão): Redefinir automaticamente o deslocamento para o deslocamento mais recente.
(3) nenhum: se o deslocamento anterior do grupo de consumidores não for encontrado, lançar uma exceção para o consumidor
Especifique arbitrariamente o deslocamento de deslocamento para iniciar o consumo
public class CustomConsumerSeek {
public static void main(String[] args) {
// 0 配置信息
Properties properties = new Properties();
// 连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.6.100:9092");
// key value 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test2");
// 1 创建一个消费者
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<>(properties);
// 2 订阅一个主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
Set<TopicPartition> assignment= new HashSet<>();
while (assignment.size() == 0) {
kafkaConsumer.poll(Duration.ofSeconds(1));
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)
assignment = kafkaConsumer.assignment();
}
// 遍历所有分区,并指定 offset 从 1700 的位置开始消费
for (TopicPartition tp: assignment) {
kafkaConsumer.seek(tp, 1700);
}
// 3 消费该主题数据
while (true) {
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
Nota: Após cada execução, o nome do grupo de consumidores precisa ser modificado;
Consumo de tempo especificado
Demanda: No ambiente de produção, você encontrará dados anormais nas últimas horas de consumo e deseja voltar a consumir de acordo com o tempo. Por exemplo, se for necessário consumir os dados do dia anterior de acordo com o horário, como lidar com isso?
public class CustomConsumerForTime {
public static void main(String[] args) {
// 0 配置信息
Properties properties = new Properties();
// 连接
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.6.100:9092");
// key value 反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test2");
// 1 创建一个消费者
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<>(properties);
// 2 订阅一个主题
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
Set<TopicPartition> assignment = new HashSet<>();
while (assignment.size() == 0) {
kafkaConsumer.poll(Duration.ofSeconds(1));
// 获取消费者分区分配信息(有了分区分配信息才能开始消费)
assignment = kafkaConsumer.assignment();
}
HashMap<TopicPartition, Long> timestampToSearch = new
HashMap<>();
// 封装集合存储,每个分区对应一天前的数据
for (TopicPartition topicPartition : assignment) {
timestampToSearch.put(topicPartition,
System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
// 获取从 1 天前开始消费的每个分区的 offset
Map<TopicPartition, OffsetAndTimestamp> offsets =
kafkaConsumer.offsetsForTimes(timestampToSearch);
// 遍历每个分区,对每个分区设置消费时间。
for (TopicPartition topicPartition : assignment) {
OffsetAndTimestamp offsetAndTimestamp =
offsets.get(topicPartition);
// 根据时间指定开始消费的位置
if (offsetAndTimestamp != null){
kafkaConsumer.seek(topicPartition,
offsetAndTimestamp.offset());
}
}
// 3 消费该主题数据
while (true) {
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
Consumo em falta e consumo duplo
Consumo repetido: Os dados foram consumidos, mas a compensação não foi enviada. Consumo ausente: Enviar o deslocamento primeiro e depois consumi-lo pode resultar em consumo ausente de dados.
Cenário 1: Repetir o consumo. Comprometer automaticamente o deslocamento causado por
1) Consumidor envia compensação a cada 5 segundos
2) Se 2s após o envio do offset, o consumidor trava
3) Reinicie o consumidor novamente e continue consumindo a partir do deslocamento enviado pela última vez, resultando em consumo repetido
consumo perdido
Defina o deslocamento para envio manual. Quando o deslocamento é enviado, os dados ainda estão na memória e não foram colocados no disco. Neste momento, o encadeamento do consumidor é eliminado, o deslocamento foi enviado, mas os dados foram não foi processado, resultando na perda de dados nesta parte da memória.
assuntos do consumidor
Se você deseja concluir o consumo único preciso no lado do consumidor, precisa vincular atomicamente o processo de consumo e o processo de envio de compensação no lado do consumidor Kafka. Neste ponto, precisamos salvar o deslocamento do Kafka em uma mídia personalizada que suporte transações
acúmulo de dados
1) Se a capacidade de consumo do Kafka for insuficiente, você pode considerar aumentar o número de partições do tópico e, ao mesmo tempo, aumentar o número de consumidores no grupo de consumidores, o número de consumidores = o número de partições
2) Se o processamento de dados downstream não for oportuno: aumente o número de pulls por lote. Poucos dados são extraídos em lotes (puxando dados/tempo de processamento < velocidade de produção), para que os dados processados sejam menores que os dados de produção, o que também causará atrasos de dados