1 消息路由策略
在通过API方式发布消息时,生产者是以Record为消息进行发布的。Record中包含key与value,value才是我们真正的消息本身,而key用于路由消息所要存放的Partition。消息要写入到哪个Partition并不是随机的,而是有路由策略的。
1) 若指定了partition,则直接写入到指定的partition;
2) 若未指定partition但指定了key,则通过对key的hash值与partition数量取模,该取模结果就是要选出的partition索引;
3) 若partition和key都未指定,则使用轮询算法选出一个partition;
4) 若即指定了partition同时也指定了key,那么优先按照partition写入,如果指定的partition无效,则通过key取模写入。
public class OneProducer {
// 第一个泛型:当前生产者所生产消息的key
// 第二个泛型:当前生产者所生产的消息本身
private KafkaProducer<Integer, String> producer;
public OneProducer() {
Properties properties = new Properties();
// 指定kafka集群
properties.put("bootstrap.servers", "kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092");
// 指定key与value的序列化器
properties.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
this.producer = new KafkaProducer<Integer, String>(properties);
}
public void sendMsg() {
// 创建消息记录(包含主题、消息本身) (String topic, V value)
// ProducerRecord<Integer, String> record = new ProducerRecord<>("cities", "tianjin");
// 创建消息记录(包含主题、key、消息本身) (String topic, K key, V value)
// ProducerRecord<Integer, String> record = new ProducerRecord<>("cities", 1, "tianjin");
// 创建消息记录(包含主题、partition、key、消息本身) (String topic, Integer partition, K key, V value)
ProducerRecord<Integer, String> record = new ProducerRecord<>("cities", 0, 1, "tianjin");
producer.send(record);
}
}
2 消息写入算法(流程)
消息生产者将消息发送给broker,并形成最终的可供消费者消费的log,是一个比较复杂的过程。
1) producer向broker集群提交连接请求,其所连接上的任意broker都会向其发送broker controller的通信URL,即配置文件中的listeners地址
2) 当producer指定了要生产的topic后,其会向broker controller发送请求,请求当前topic中所有partition的leader列表地址
3) broker controller在接收到请求后,会从zk中查找到指定topic的所有partition的leader,并返回给producer
4) producer在接收到leader列表地址后,根据消息路由策略找到当前要发送消息所要发送的partition leader,然后将消息发送给该leader
5) leader将消息写入本地log,并通知ISR中的followers
6) ISR中的followers从leader中同步消息后向leader发送ACK(正常情况要发送ACK应答)
7) leader收到所有ISR中的followers的ACK后,增加HW,表示消费者已经可以消费到该位置了
3 HW截断机制
如果partition leader接收到了新的消息, ISR中其它Follower正在同步过程中,还未同步完毕时leader宕机;此时就需要选举出新的leader,若没有HW截断机制,将会导致partition中leader与follower数据的不一致。
Kafka无HW截断机制
当原Leader宕机后又恢复时,将其LEO回退到其宕机时的HW,然后再与新的Leader进行数据同步,这种机制称为HW截断机制。HW截断机制可能会引发消息的丢失。
Kafka-HW截断机制
4 消息发送的可靠性机制
生产者向kafka发送消息时,可以选择需要的可靠性级别。通过acks参数的值进行设置。
(1) 0值
异步发送。生产者向kafka发送消息而不需要kafka反馈成功ack。该方式效率最高,但可靠性最低,其可能会存在消息丢失的情况。
- 在传输过程中丢失:由于网络原因,生产者发送的消息根本就没有到达Kafka,但生产者不知道,其会一直生产并发送消息给Kafka,这种情况可能会出现大量的消息丢失。
- 在broker中丢失:当broker的缓存满时正准备给partation中写入时,此时到达的新的消息会丢失。
(2) 1值
同步发送,默认值。生产者发送消息给kafka,broker的partition leader在收到消息后马上发送成功ack,生产者收到后知道消息发送成功,然后会再发送消息。如果一直未收到kafka的ack,则生产者会认为消息发送失败,会重发消息。
该方式不能保证消息发送成功,该方式仍会出现消息丢失的情况。例如,当leader收到 消息后马上向producer发送的ack,但此时在follower还没有同步数据时,该leader挂了。此时写入到原来leader中的消息就丢失了。因为这条消息对于producer来说,是发送成功了。但对于剩余的follower来说根本就没有存在过。
(3) -1值
同步发送。生产者发送消息给kafka,kafka收到消息后要等到ISR列表中的所有副本都同步消息完成后,才向生产者发送成功ack。如果一直未收到kafka的ack,则认为消息发送失败,会自动重发消息。
该方式很少会出现消息丢失的情况,但其存在消息重复接收的情况;为了解决重复接收(注意,重复接收,不是重复消费,这是两个概念)问题,Kafka允许为消息指定唯一标识,并允许用户自定义去重机制。
5 消费者消费过程解析
生产者将消息发送到topic中,消费者即可对其进行消费,其消费过程如下:
1) consumer向broker集群提交连接请求,其所连接上的任意broker都会向其发送broker controller的通信URL,即配置文件中的listeners地址
2) 当consumer指定了要消费的topic后,其会向broker controller发送消费请求
3) broker controller会为consumer分配一个或几个partition leader,并将该partitioin的当前offset发送给consumer
4) consumer会按照broker controller分配的partition对其中的消息进行消费
5) 当消费者消费完该条消息后,消费者会向broker发送一个该消息已被消费的反馈,即该消息的offset
6) 当broker接到消费者的offset后,会更新到相应的__consumer_offset中
7) 以上过程一直重复,直到消费者停止请求消息
8) 消费者可以重置offset,从而可以灵活消费存储在broker上的消息
6 Partition Leader选举范围
当leader宕机后broker controller会从ISR中选一个follower成为新的leader。但,若ISR中没有其它的副本怎么办?可以通过unclean.leader.election.enable的取值来设置Leader选举的范围。
(1) false
必须等待ISR列表中有副本活过来才进行新的选举。该策略可靠性有保证,但可用性低。
(2) true
在ISR中没有副本的情况下可以选择任何一个没有宕机主机中该topic的partition副本作为新的leader,该策略可用性高,但可靠性没有保证。
7 重复消费问题及解决方案
最常见的重复消费有两种:
(1) 同一个consumer重复消费
当Consumer由于消费能力较低而引发了消费超时时,则可能会形成重复消费。在时间到达时恰好某数据消费完毕,正准备提交offset但还没有提交时,时间到了。此时就会产生重复消费问题。
其解决方案是,延长offset提交时间。
(2) 不同的consumer重复消费
当Consumer消费了消息但还未提交offset时宕机,则这些已被消费过的消息会被重复消费。
其解决方案是将自动提交改为手动提交。