【背景】
kafka基于k8s容器化部署后,对容器设置了存活探针,即检测监听端口是否存在。然而一次kill kafka进程的操作,服务的重启时间(supervisor会自动再拉起kafka进程)超过了存活探针的监测时间,导致pod重启。本文就该问题展开进行分析。
【kill背后的逻辑】
对于以SIGTERM信号(不带参数的默认发送信号)进行的kill操作,kafka broker会捕获该信号,进行服务停止的相关处理动作,其中比较重要的两个动作为:
1)controlledShutdown
这一步的具体流程包括:
a. 待停止的broker节点向zk(2.8.0以前版本)获取controller节点的信息
b. 向controller建立连接并发送controlledShutdown请求,
c. controller收到请求后,对leader位于该broker上的分区进行必要的迁移动作,即分区副本数大于1,且有存活的其他broker节点中选出新的leader,然后发送请求通知被选中的broker成为新的分区leader;对待停止的broker上处于follower状态的分区以rpc请求形式告知停止进行fetch动作。
d. 最后controller给待停止的broker节点进行controlledShutdown请求响应。
之所以要这么做,是可以将分区不可服务的时间缩短为毫秒级别。否则,zk需要一段时间才能感知到该节点的离线,而controller的broker监听了对应znode目录的变化,因此感知broker的离线后才触发进行相应的处理动作,在controller未感知到其他节点离线的这段时间内,leader位于停止的broker节点上的分区是不可服务的,因此不可服务时间基本上就取决于kafka与zk之间连接的超时时长。
2)关闭所有打开的日志文件,并创建相关文件以标记文件是正常关闭。
关闭所有打开的文件,并在目录中写入".kafka_cleanshutdown"文件。在启动后,加载segment时,会判断是否存在".kafka_cleanshutdown"文件,从而决定是否需要进行日志的恢复动作(这里暂不展开)。
这种关闭服务的方式被称之为优雅的关闭服务,而不是kill -9强行结束。在官方文档中,也有相关的介绍。
另外,"kafka-server-stop.sh"脚本本质上也是这么操作的。
代码语言:javascript复制SIGNAL=${SIGNAL:-TERM}
PIDS=$(ps ax | grep -i 'kafka.Kafka' | grep java | grep -v grep | awk '{print $1}')
if [ -z "$PIDS" ]; then
echo "No kafka server to stop"
exit 1
else
kill -s $SIGNAL $PIDS
fi
正常情况下, controlledShutdown这个操作都是非常快的。但是由于该操作细化后的各个步骤都会涉及网络的交互,那么,在一些异常情况下,比如与zk的tcp连接异常、与controller的网络连接异常、controller触发的分区leader重新选举异常等,这都会导致controlledShutdown请求的重试,直到请求成功或者达到最大重试次数才结束,这时,controlledShutdown请求的整体耗时可能会超过30s,甚至更长。这也是我们业务中导致pod重启的原因。
代码语言:javascript复制[2023-04-10 14:18:03,597] INFO [KafkaServer id=1] shutting down (kafka.server.KafkaServer)
[2023-04-10 14:18:03,599] INFO [KafkaServer id=1] Starting controlled shutdown (kafka.server.KafkaServer)
[2023-04-10 14:18:11,010] WARN [KafkaServer id=1] Retrying controlled shutdown after the previous attempt failed... (kafka.server.KafkaServer)
[2023-04-10 14:18:16,049] WARN [KafkaServer id=1] Retrying controlled shutdown after the previous attempt failed... (kafka.server.KafkaServer)
[2023-04-10 14:18:21,081] WARN [KafkaServer id=1] Retrying controlled shutdown after the previous attempt failed... (kafka.server.KafkaServer)
[2023-04-10 14:18:21,084] WARN [KafkaServer id=1] Proceeding to do an unclean shutdown as all the controlled shutdown attempts failed (kafka.server.KafkaServer)
[2023-04-10 14:18:27,279] INFO [KafkaServer id=1] shut down completed (kafka.server.KafkaServer)
实际上,controlledShutdown这个请求操作是可选进行的(由配置参数进行控制)。也就是说,在关闭时可以控制不进行controlledShutdown请求。这样,可以一定程度上加速服务的重启,甚至可能在zk感知到broker节点离线前,就已经完成了重启流程。
涉及的相关配置包括:
代码语言:javascript复制// 与controller的socket超时时间, 默认为30000, 即30秒
controller.socket.timeout.ms
// controlled shutdown请求的最大重试次数, 默认3次
controlled.shutdown.max.retries
// controlled shutdown请求的重试间隔, 默认5000, 即5s
controlled.shutdown.retry.backoff.ms
// 是否启用 controlled shutdown, 默认为true
controlled.shutdown.enable
【总结】
本文通过一个重启耗时较长的问题,讲述了一个简单的知识点:kafka优雅关闭时的controlledShutdown请求操作。当然是否要禁用该请求,需要结合实际业务的可用性、zk连接超时时长等因素一并考虑。
另外,重启过程中的另外一个耗时操作,日志的加载与恢复,这里没有展开讲解,下篇文章我们再来聊聊该内容。