kafka必须知道的基础知识

140 阅读3分钟

前言

如果有任何错误, 请及时留言, 我们一起讨论一起学习

kafka消息发送基本流程

kafka消息推送基本流程.png

  1. P发送一个带着topic的消息给broker
  2. broker将消息划分给topic下的leader
  3. leader存储消息到partition中
  4. 消费者根据topic到broker中拉取消息

消费者怎么知道要去哪个partition中拉取消息?

答: 消费者不管消息被存放到哪个partition中, 它只需要根据topic就能找到消息, 就跟查数据库一样, 带着topicId 就能在众多partition中找到你想要的, 所以这个问题基本不考虑
而且消费者在创建时会订阅topic下的所有partition, 所以也可以不需要根据topicId去查, 只要消费者拉取消息就行

怎么决定消息发送给哪个partition?

有三种方法

  • key计算hash
  • 没有key, 使用负载均衡, 也就是粘性分区策略(说白了就是选个没满的分区)
  • 轮训

image.png

image.png

消费者分组

image.png

消费者分组后, kafka就会将一个消息发送给多个组, 只要这个组中的消费者订阅了该topic

在同一个组中, 消息只会发送给组中的一个消费者

同一个组有消息先后顺序

leader, follower

在一个partition中通常有一个leader和多个follower

leader负责消息的读写, follower负责同步leader的消息

leader 和 follower 通常分布在不同的 broker 之中(也就是不同的机器中)

同步leader消息的方式

同步leader消息的方式有两种:

  • 同步
  • 异步

同步方式同步消息的follower被称之为in-sync replica(ISR)

异步方式同步消息的叫普通follower

leader故障后会发生什么?

kafka借助zookeeper选举出leader, 并且follower会将自己的partition的偏移量截断, 通信同步新leader的partition的偏移量

优先使用ISR的follower节点, 不行就使用普通follower

无法保证消息不丢失, 不重复发送消息

kafka如何保证重新选举leader后kafka的消息不丢失不重复消费?

消息不丢失

使用上面的ISR机制防止消息丢失, 但并不是百分百的, 需要将acks标记为 -1

-1 之后, 意味着follower已经同步了leader的消息, 这样broker才会发送ack给生产者

如果是1的话 生产者会收到服务器的ack消息, 表示消息发送到leader, 小概率丢失数据, 如果follower没同步消息的话

0 表示生产者只发送消息, 服务器不会发送ack个生产者, 效率最高, 但是会丢数据

消息不重复

记住消费者持有偏移量, 该偏移量相当于消息的id, 而这个id是自增的, 所以生产者知道自己消费到哪个消息了

只要消费者在消息相关业务执行完成后, 手动进行一个commit 就行, 不论是同步 commit 还是异步

不过基本上都是先异步, 不行在 try-catch-finally的finally中执行一次同步 commit

也就是异同混搭

手动commit需要先配置kafka将enable.auto.commit配置为false, 否则手动commit不生效

生产者发送给broker失败了怎么办?

配置retries

在 java 中是 org.apache.kafka.clients.producer.ProducerConfig#RETRIES_CONFIG key, 相应的我们只要配置个次数就行了

生产者会每100ms重试一次, 达到次数后报错

代码

生产者

image.png

Properties prop = new Properties();  
prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");  
prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");  
prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");  
prop.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");  
prop.put(ProducerConfig.RETRIES_CONFIG, 10);  
prop.put(ProducerConfig.ACKS_CONFIG, -1);  
KafkaProducer<String, String> producer = new KafkaProducer<>(prop);  
try (producer) {  
    ProducerRecord<String, String> producerRecord = new ProducerRecord<>("my_topic", 0, "key", "value");  
    Future<RecordMetadata> future = producer.send(producerRecord);  
    future.get();  
} catch (InterruptedException | ExecutionException e) {  
    throw new RuntimeException(e);  
}

消费者

image.png

Properties prop = new Properties();  
prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");  
prop.put(ConsumerConfig.GROUP_ID_CONFIG, "groupB");  
prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");  
prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");  
prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);  
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(prop);  
try (consumer) {  
    consumer.subscribe(new ArrayList<String>() {{  
    add("my-topic");  
    }});  
    ConsumerRecords<String, String> records =  
    consumer.poll(Duration.ZERO);  
    records.forEach(System.err::println);  
} catch (Exception e) {  
    throw new RuntimeException(e);  
}