1、背景
针对Kafka进行测试的结果中出现的ack为0消费时延比ack为1、-1情况下反而要长,异步生产消费时延较大等疑点,都非常有必要一番配置和代码的梳理。
2、核心配置
以下所有默认参数是针对kafka 0.9,且主要针对时延这块涉及的核心参数做分析:
producer端:
buffer.memory:默认32m,每个producer实例可用来存储消息的最大内存空间(在实例中作为一个内存池存在)。
retries:kafka默认0次,mafka默认3次,异步发送失败重试次数。
batch.size: 默认16k,可以把一个batch.size大小的空间认为是一个槽。
linger.ms:默认0ms,在异步IO线程被触发后(任何一个topic,partition满都可以触发),如果其他的
broker端:
replica.fetch.max.bytes:默认1m,follower每次拉消息过程中,针对每个
consumer端:
fetch.message.max.bytes:默认1m,consumer每次拉消息过程中,针对每个
3、术语说明
ISR:同步副本列表
kafka0.8 通过2个因素来控制ISR:1、最近追上的时间和当前时间的差和配置的阈值的比较。 2、replica partition和leader partition的消息条数的差和配置的阈值的比较。
kafka0.9 抛弃了0.8中消息条数的控制,只通过最近追上的时间和当前时间的差和配置的阈值的比较来控制ISR的变化。
LEO:每个replica partition存储的最后一条消息的offset。
HW:partition的高水位,取这个partition对应的ISR中最小的LEO作为HW,消费者最多只能消费到HW所在的位置。
4、延时分析
这里不针对每种情况进行逐一分析,仅以一个非常具有代表性的case(异步发送,replica为3,ack为-1)来进行分析。
4.1 producer
针对每个producer实例:
创建一个大小为buffer.memory的内存池,所有存放发送消息的缓存都是从这个内存池捞出来的空间。
创建一个batches的Map,key为
4.2 broker
ack设置为0的情况下,类似于客户端进行了一次one way的rpc操作,不需要等待broker端的response。
针对ack为1和-1的情况,下面做些分析:
broker端接收到producer端发送过来的消息,先往leader partition上顺序写入这条消息,更新LEO和HW。
针对ack为1的情况,直接response给Producer端。
针对ack为-1的情况,创建一个延迟操作(DelayedProduce),延迟操作有个超时时间timeout.ms。
4.2 延时分类
- 延时生产
- 延时拉取
- 延时数据删除
延时生产
如果在使用生产者客户端发送消息的时候将 acks 参数设置为-1,那么就意味着需要等待ISR集合中的所有副本都确认收到消息之后才能正确地收到响应的结果,或者捕获超时异常。假设某个分区有3个副本:leader、follower1和follower2,Kafka在收到客户端的生产请求(ProduceRequest)后,将消息3和消息4写入leader副本的本地日志文件。由于客户端设置了acks为-1,那么需要等到follower1和follower2两个副本都收到消息3和消息4后才能告知客户端正确地接收了所发送的消息。如果在一定的时间内,follower1副本或follower2副本没能够完全拉取到消息3和消息4,那么就需要返回超时异常给客户端。生产请求的超时时间由参数request.timeout.ms配置,默认值为30s,
这里有个问题就是等待follower1和follower3的副本收到消息3和消息4,并返回给客户端相应,是由谁来操作的?答案就是延时生产操作,当消息写入到leader副本后,kafka会创建一个延时生产操作,用来处理消息正常写入所有副本或者超时的情况,然后把结果返回给客户端。
延时操作需要延时返回相应结果,所以必须有一个超时时间,如果在超时时间内没有完成任务,那么就会强制完成以返回相应结果给客户端。延时操作和定时操作还是有一定的区别,定时操作指的是特定的时间之后立刻执行某操作,延时操作是有可能在延时期间提前执行某操作,也就是延时操作是支持外部事件触发的。
就延时生产操作而言,它的外部事件是所要写入消息的某个分区的 HW(高水位)发生增长。随着follower副本不断地与leader副本进行消息同步,进而促使HW进一步增长,HW每增长一次都会检测是否能够完成此次延时生产操作,如果可以就执行以此返回响应结果给客户端;如果在超时时间内始终无法完成,则强制执行。
延时操作创建之后会被加入延时操作管理器(DelayedOperationPurgatory)来做专门的处理,延时操作有可能会超时,每个延时操作管理器都会配备一个定时器来做超时管理,定时器的底层就是采用时间轮实现的。
客户端在请求写入消息到收到响应结果的过程中与延时生产操作的过程如下,
1、如果客户端设置的 acks 参数不为-1,或者没有成功的消息写入,那么就直接返回结果给客户端
2、否则就需要创建延时生产操作并存入延时操作管理器,最终要么由外部事件触发,要么由超时触发而执行
延时拉取
follower副本都已经拉取到了leader副本的最新位置,此时又向leader副本发送拉取请求,而leader副本并没有新的消息写入,那么此时leader副本该如何处理呢?可以直接返回空的拉取结果给follower副本,不过在leader副本一直没有新消息写入的情况下,follower副本会一直发送拉取请求,并且总收到空的拉取结果,这样会徒耗资源,所以可以进行延时拉取来控制。
Kafka在处理拉取请求时,会先读取一次日志文件,如果收集不到足够多(由参数fetch.min.bytes配置,默认值为1)的消息,那么就会创建一个延时拉取操作以等待拉取到足够数量的消息。当延时拉取操作执行时,会再读取一次日志文件,然后将拉取结果返回给 follower 副本。
如果拉取进度一直没有追赶上leader副本,那么在拉取leader副本的消息时一般拉取的消息大小都会不小于fetchMinBytes,这样Kafka也就不会创建相应的延时拉取操作,而是立即返回拉取结果
延时拉取操作同样是由超时触发或外部事件触发而被执行的,外部事件触发就稍复杂了一些,因为拉取请求不单单由 follower 副本发起,也可以由消费者客户端发起。如果是follower副本的延时拉取,它的外部事件就是消息追加到了leader副本的本地日志文件中;如果是消费者客户端的延时拉取,它的外部事件可以简单地理解为HW的增长