分区消息的有序性
Kafka可以保证同一个分区消息的先入先出。生产者默认的分区策略是轮询+按键哈希映射,在分区数量不变的情况下,具有相同键的消息会被生产到同一个分区。但是需要注意的是,在max.in.flight大于1的情况下,重试可能会导致消息乱序。
max.in.flight.requests.per.connection参数表示生产者在收到Broker上一个响应之前可以发送多少个消息批次。当该参数大于1时,重试就可能会导致分区消息乱序。譬如:第一个批次提交失败,第二个批次提交成功,而后上一个批次重试成功,两个批次的顺序就反过来了。
Kafka对已提交消息作有限度的持久化保证
当消息被写入所有同步副本时,但不一定要落盘,则认为是已提交,但生产者可以选择接受不同类型的确认。
有限度表示至少一台保存该消息的Broker存活,Kafka就能保证该消息不会丢失。
消费者只能读取到已提交消息
Kafka利用高水位(HW)来定义消息的可见性,表示分区下哪些消息是可以被消费者消费的。
Kafka在每个副本对象中都保存两个属性:高水位和LEO值。LEO值表示日志末端位移(Log End Offset),即下一条消息的位移值。在同一个副本对象中,高水位值永远小于LEO值。
首领副本的高水位代表着分区的高水位,为了确定这个值,Kafka在首领副本上还额外保存了所有跟随者副本的LEO值。当跟随者副本从首领副本拉取消息时,首领副本会用其请求的偏移量来更新该副本的LEO值。由于在LEO值之前的消息已经被跟随者副本保存,那么首领副本的高水位 = 所有同步副本LEO值的最小值。利用高水位实现了消费者只能获取到已提交消息的限制。
复制和分区多副本机制是Kafka Broker高可用、高持久性的核心。Kafka既不是完全的同步复制,也不是完全的异步复制,而是基于ISR实现了动态复制方案。
Kafka将副本分为两类:领导者副本(Leader Replica)和跟随者副本(Follower Replica)。每个分区都仅有一个领导者副本,所在broker称为分区首领,负责处理所有生产和消费请求;跟随者副本只是异步地与领导者副本进行同步,不对外提供服务,并在领导者故障时参与首领选举。
既然是异步的,就存在与领导者不实时同步的风险。为了精准的定义同步的含义,Kafka提出了同步副本的概念,并利用ISR集合来管理同步副本。ISR是一个动态调整的集合。
跟随者成为同步副本需要满足的条件:
只有被复制到所有同步副本的已提交消息才能被消费者消费。
滞后的同步副本会拖慢生产者和消费者。
1. 配置合适的复制系数 replication.factor >= 3
分区的副本数量,默认值为3。更大的复制系数意味着更高的可用性,但会占据更多的空间。这需要在可用性和存储空间作出权衡。
2. 配置合适的最少同步副本 min.insync.replicas > 1
最小同步副本保证了已提交消息至少被写入多少个副本,当同步副本数量不足时,Broker会停止接收新的写请求,此时该分区变成了只读模式。该参数需要在可用性A和数据一致性C之间做出权衡。
Kafka只对已提交消息作有限度的持久化保证。消息被写入所有同步副本即被认为是已提交的,有限度是指至少一个保存该消息的Broker存活,Kafka才能保证消息不丢失。如果当前只有一个同步副本,那么当该副本不可用时,数据就可能会丢失,因为数据可能没有落盘。
配合生产者的发送确认acks = all,就可以确定在返回确认前,至少有多少个副本能够收到消息。这种确认参数可靠性最高。
3. 禁用不完全的首领选举
分区首领选举时不允许非同步副本成为首领。当首领副本宕机时,一个同步副本会被选举为新的首领,这样已提交的消息不会丢失。但若在选举时,副本都是不同步的,这个时候就需要在可用性A和数据一致性C之间做出选择。若允许不同步的副本成为首领,就有数据丢失和不一致的风险;若不允许,那么分区在原首领副本恢复前将处于不可用状态。
建议禁用不完全的首领选举,保证数据一致性,并通过增加副本数来提高可用性。
1. 配置合适的发送确认参数acks
只有当消息被写入所有同步副本时才被认为是已提交,但生产者可以选择接受不同类型的确认,该参数会导致生产者和Broker之间对已提交存在歧义。
2. 配置合适的重试参数,利用Kafka客户端进行重试
在需要至少一次语义,不允许消息丢失时,可重试错误尽量交给Kafka自动完成,业务代码里只处理非可重试错误以及重试次数超出的情况。
重试可能会导致消息重复、乱序。
max.in.flight.requests.per.connection参数表示生产者在收到Broker上一个响应之前可以发送多少个消息批次。当该参数大于1时,重试可能导致分区消息乱序。譬如:第一个批次提交失败,第二个批次提交成功,而后上一个批次重试成功,两个批次的顺序就反过来了。
3. 妥善地处理异常
推荐使用异步发送,并在回调函数里处理异常。
1. 应尽量减少计划外的再均衡,这部分情况主要集中于群组成员的意外离线:
未能及时发送心跳而被群组协调器踢出群组
这需要合理的设置心跳超时时间和心跳发送频率,使得在超时之前,消费者至少能够发送三次心跳。另外,群组协调器需要利用心跳响应来传递再均衡开启信息,较高的心跳发送频率能够使得群组更快地进入再均衡。
消费时间过长超出最大轮询间隔从而导致的主动退群
若消费者消息处理时间过长,没能及时poll下一批消息,消费者会主动发起LeaveGroup请求,退出群组。适当调高轮询间隔时间,缩短消息消费时间,减小每次获取的消息数量。或者多线程消费消息,此时无法保证消费顺序,且需要小心地处理位移提交。
GC
消费者频繁的Full GC导致长时间停顿,在实际场景中也很常见。
2. 妥善地处理偏移量
推荐使用手动提交,更加灵活、可控。要确保消息处理完成之后,在提交位移,否则容易出现消息丢失。在不同场景混用同步提交与异步提交:
这样既不影响TPS,也改善了消费者的高可用性。
另外,自动提交和手工提交均不能规避重复消费,提高提交频率只能减少重复数量,但不能避免。仅一次消费语义需要借助外部系统。
3. 配置合适的偏移量重置参数 auto.offset.reset
消费者在读取一个没有偏移量或者偏移量无效(消费者长时间离线,偏移量对应的消息已经被删除)的情况下,应该如何处理。默认值是latest,表示从最新的消息开始消费。另一个取值是earliest。
消息可能会丢失,但绝不会被重复发送。
Kafka可以提供最多一次交付保障,生产者禁止重试,消费者需要妥善处理位移,防止重复消费。
消息不会丢失,但有可能被重复发送。Kafak默认提供至少一次交付保障。
Kafka为了提供至少一次的交付保障,对消息可靠性做出了两点保障:
当消息被写入所有同步副本时,但不一定要落盘,则认为是已提交,Kafak利用复制和分区多副本机制来保证消息的高持久性。生产者可以选择接受不同类型的确认。有限度表示至少一台保存该消息的Broker存活,Kafka就能保证该消息不会丢失。
消息的可见性则是利用高水位(HW)来定义。
实现至少一次语义,需要对生产者、消费者以及Broker进行合适的配置:
生产者
配置合适的重试参数,利用Kafka客户端进行重试
可重试错误尽量交给Kafka自动完成,业务代码里只处理非可重试错误以及重试次数超出的情况。需要注意的是,重试可能会导致消息重复、乱序。
配置发送确认参数acks = all
只有当消息被写入所有同步副本时才被认为是已提交,但生产者可以选择接受不同类型的确认,该参数会导致生产者和Broker之间对消息是否成功提交存在歧义。
配置acks=all,只当所有同步副本收到消息并成功写入,Broker才会给生产者返回成功。配合Broker最小同步副本数参数,就可以确定在返回确认前,至少有多少个副本能够收到消息。这种确认参数可靠性最高。
妥善的处理发送异常
使用异步发送方式时,在异步回调函数里第异常进行处理;
Broker
配置复制系数 replication.factor >= 3
复制系数表示分区的副本数量,更大的复制系数意味着更高的可用性,但会占据更多的空间。这需要在可用性和存储空间作出权衡。
配置最少同步副本 min.insync.replicas > 1
最小同步副本保证了已提交消息至少被写入多少个副本,当同步副本数量不足时,Broker会停止接收新的写请求,此时该分区变成了只读模式。该参数需要在可用性A和数据一致性C之间做出权衡。
由于Kafka只对已提交消息作有限度的持久化保证。如果当前只有一个同步副本,那么当该副本不可用时,数据就可能会丢失,因为数据可能没有落盘。
禁用不完全的首领选举
当首领副本宕机时,一个同步副本会被选举为新的首领,这样已提交的消息不会丢失。但若在选举时,副本都是不同步的,这个时候就需要在可用性A和数据一致性C之间做出选择。若允许不同步的副本成为首领,就有数据丢失和不一致的风险;若不允许,那么分区在原首领副本恢复前将处于不可用状态。
为了保证数据不丢失,需要禁用不完全的首领选举,并通过增加副本数来提高可用性。
消费者
为了保证消费者不丢失消息,要妥善的处理位移,确保处理完消息后,再提交位移。同时,偏移量自动重置参数要设置为earliest。
推荐使用手动提交,更加灵活、可控。在不同场景混用同步提交与异步提交:
这样既不影响TPS,也改善了消费者的高可用性。
Kafka不完全支持精确一次语义,生产者可利用幂等或者事务生产者,而消费者则需要借助外部系统。
幂等生产者
幂等生产者可以在分区、会话作用域级别上实现消息生产的幂等性,我们可以安全地进行重试,而不会破坏系统状态。
幂等生产者的实现是利用Producer ID和自增的Sequence Number。每个Producer初始化时会分配一个唯一的ID,其发送的每条消息都对应一个自增的Sequence Number。Broker端摒弃具有相同Sequence Number的消息来实现去重。
通过enable.idempotence参数开启幂等生产者。
事务生产者
事务生产者可以实现跨分区、跨会话的消息去重。Kafka事务的隔离级别read committed 提交读,可以保证多条消息原子地写入到目标分区,也可以保证消费者只能读取到事务已提交的消息。
事务生产者利用两阶段提交(2PC),利用事务协调器来完成分布式事务。
事务生产者需要先开启幂等,然后在send方法前后调用事务方法:beginTransaction、commitTransaction。
消费者实现精确一次语义需要借助外部系统:
精确一次交付还需要一些持久化配置,保证消息不丢失:(参见至少一次)
Broker
生产者
《Kafka权威指南》
《Kafka核心技术与实战》
http://matt33.com/2018/11/04/kafka-transaction/
https://www.jianshu.com/p/b753527b91a6