前言
如果有任何错误, 请及时留言, 我们一起讨论一起学习
kafka消息发送基本流程
- P发送一个带着topic的消息给broker
- broker将消息划分给topic下的leader
- leader存储消息到partition中
- 消费者根据topic到broker中拉取消息
消费者怎么知道要去哪个partition中拉取消息?
答: 消费者不管消息被存放到哪个partition中, 它只需要根据topic就能找到消息, 就跟查数据库一样, 带着topicId 就能在众多partition中找到你想要的, 所以这个问题基本不考虑
而且消费者在创建时会订阅topic下的所有partition, 所以也可以不需要根据topicId去查, 只要消费者拉取消息就行
怎么决定消息发送给哪个partition?
有三种方法
- key计算hash
- 没有key, 使用负载均衡, 也就是粘性分区策略(说白了就是选个没满的分区)
- 轮训
消费者分组
消费者分组后, 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重试一次, 达到次数后报错
代码
生产者
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);
}
消费者
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);
}