Impala graceful shutdown功能介绍
了解Impala的同学都知道,Impala是一个典型的MPP架构,节点都是无状态的,随时可以拉起和停掉,所以我们可以方便地对整个集群进行扩容/缩容。但是在缩容的时候,会遇到一个问题:当节点上有SQL在跑的时候,如果我们直接停掉节点的话,那么这个节点上的所有SQL都会失败,这个对用户的影响还是非常不友好的。在物理机部署的时候,由于扩容/缩容操作相对比较小,所以这个问题影响较小。如果我们在使用容器化部署的时候,节点的上线和下线比较频繁,那这个影响比较大了。
Impala在3.x版本的时候提供了graceful shutdown的功能来解决这个问题。当我们使用graceful shutdown功能关闭节点的时候,该节点会等到SQL执行完之后(在一定时间内)再关闭服务,同时不再接受其他的请求。在介绍graceful shutdown功能之前,我们需要先说明以下两个相关的配置项:
- shutdown_grace_period_s,默认为120s,表示节点在关闭之前至少会等待shutdown_grace_period_s这个时间间隔,用于进行一些信息的更新和同步,但是在此期间,节点也不会接受外部的请求;
- shutdown_deadline_s,默认为3600s,表示节点一定会在这个时间间隔之后关闭,即使此时节点上仍然有SQL在跑,也会强制关闭;
下面就结合使用和代码来看一下是Impala是如何实现这个graceful shutdown的功能。
使用方式
Impala目前提供了两种方式来执行graceful shutdown功能:提交SQL和执行脚本。我们就来一一看下这两种使用方式。
SHUTDOWN()函数
我们可以直接在coordinator节点上执行如下所示的函数来对指定的节点执行graceful shutdown,如下所示:
:SHUTDOWN()
:SHUTDOWN([host_name[:port_number] )
:SHUTDOWN(deadline)
:SHUTDOWN([host_name[:port_number], deadline)
- 不带任何参数表示关闭本节点,且使用的是配置的shutdown_grace_period_s和shutdown_deadline_s;
- 关闭指定的remote端节点,在3.1中使用的是be_port端口,默认是22000,在3.2及以上的版本中,使用的是krpc_port的端口,默认27000,需要注意;
- 关闭本地的节点,并且shutdown_deadline_s的值为deadline,可以使用表达式,例如:60*60;
- 关闭指定的remote端节点,并且shutdown_deadline_s的值为deadline;
当我们执行了相应的SQL之后,相应地输出如下所示:
其中executor是一个节点的hostname,端口号是默认的27000,我们可以看到shutdown_grace_period_s为10s,shutdown_deadline_s为1min。
更多详细的说明可以参考:SHUTDOWN Statement
graceful_shutdown_backends.sh脚本
Impala还提供了另外一种执行graceful shutdown的功能,就是通过执行bin/graceful_shutdown_backends.sh脚本,该脚本在impala编译的image中也有。因此当我们启动container的时候,也可以通过执行该脚本来进行graceful shutdown。该脚本可以接受一个参数,默认是120+10s(在默认的shutdown_grace_period_s基础上增加了10s),该参数只是用来控制输出打印服务退出的信息,实际并不会影响服务本身的退出,该脚本可以关闭当前系统上的所有impalad服务。我们执行脚本之后,相关的输出信息如下所示:
当我们执行./bin/graceful_shutdown_backends.sh 5的时候,日志信息输出5s之后就停止了,此时节点并不一定真的退出了(如果shutdown_grace_period_s大于5s,服务仍然还在)。
执行结果
当我们执行graceful shutdown之后,在相应节点的web页面(服务退出之前)可以看到相应的进度信息,如下所示:
我们分别对coordinator和executor节点进行了graceful shutdown,结果如下所示:
对于coordinator节点,如果执行graceful shutdown:
- 当前已经执行的SQL不受影响,继续执行;
- 在coordinator进程未kill之前,新建连接会提示如下错误信息:
Error: Could not open client transport with JDBC Uri: jdbc:hive2://localhost:21050/default;auth=noSasl: Server is being shut down: shutdown grace period left: 2s992ms, deadline left: 8759h59m, queries registered on coordinator: 1, queries executing: 1, fragment instances: 1. (state=08S01,code=0)
- 在coordinator进程未kill之前,已经新建的连接,提交SQL也会返回如下错误:
Error: Server is being shut down: shutdown grace period left: 6s879ms, deadline left: 8759h59m, queries registered on coordinator: 1, queries executing: 1, fragment instances: 1. (state=HY000,code=0)
对于executor节点, 如果执行graceful shutdown:
- 当前已经执行的SQL不受影响,继续执行;
- 后续提交的SQL,不会再往这个节点上产生子计划;
代码实现
说完了graceful shutdown的具体使用,下面来简单看下Impala是如何实现graceful shutdown功能的。总的来说,就是启动一个专门的线程来控制graceful shutdown的进度,并更新相关的集群信息。相关的函数调用如下所示:
从上图可以看到,当我们在client提交:shutdown()的sql时,会被frontend解析为TStmtType::ADMIN_FN,然后传到backend,相关代码如下所示:
# client-request-state.cc
case TStmtType::ADMIN_FN:
DCHECK(exec_request_.admin_request.type == TAdminRequestType::SHUTDOWN);
RETURN_IF_ERROR(ExecShutdownRequest());
break;
当前TAdminRequestType只有SHUTDOWN一个成员。最终会调用StartShutdown函数,然后创建一个名为shutdown的线程。该线程就是执行ShutdownThread方法,启动一个循环,不断更新相关的状态信息,直到跳出循环,退出服务进程。当我们执行graceful shutdown之后,在服务退出之前,可以在/threadz页面搜索到相关的线程,如下所示:
从图中我们可以看到,除了shutdown线程之外,还有一个shutdown-signal-handler线程,下面就来解释下这个线程的含义。通过上面的函数调用图可以看到,在启动impalad进程的时候,会在main函数中通过调用StartImpalaShutdownSignalHandlerThread函数来创建一个名为“shutdown-signal-handler”的线程。该线程会一直使用sigwait()函数等待相应地信号,如果收到IMPALA_SHUTDOWN_SIGNAL(这里就是SIGRTMIN)信号,则调用StartShutdown函数,启动shutdown线程来启动graceful shutdown,相关的代码如下所示:
[[noreturn]] static void ImpalaShutdownSignalHandler() {
sigset_t signals;
CHECK_EQ(0, sigemptyset(&signals));
CHECK_EQ(0, sigaddset(&signals, IMPALA_SHUTDOWN_SIGNAL));
DCHECK(ExecEnv::GetInstance() != nullptr);
DCHECK(ExecEnv::GetInstance()->impala_server() != nullptr);
ImpalaServer* impala_server = ExecEnv::GetInstance()->impala_server();
while (true) {
int signal;
int err = sigwait(&signals, &signal);
CHECK(err == 0) << "sigwait(): " << GetStrErrMsg(err) << ": " << err;
CHECK_EQ(IMPALA_SHUTDOWN_SIGNAL, signal);
ShutdownStatusPB shutdown_status;
const int ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60;
Status status = impala_server->StartShutdown(ONE_YEAR_IN_SECONDS, &shutdown_status);
这里需要注意的是,在调用StartShutdown函数的时候,将deadline函数设置为了1年,因此当我们执行graceful_shutdown_backends.sh脚本的时候,相应的节点会输出如下的信息:
我们查看bin/graceful_shutdown_backends.sh脚本的内容可以发现,主要就是通过向impalad进行发送SIGRTMIN信号来触发graceful shutdown功能:
LOG "Initiating graceful shutdown."
for pid in $(pgrep impalad); do
LOG "Sending signal to daemon with pid $pid"
kill -SIGRTMIN $pid
done
到这里关于graceful shutdown的功能和实现我们就基本介绍完毕了,当然这其中还有关于集群信息如何更新,状态同步等内容就不再这篇文章中阐述,后续有机会的话,再跟大家一起学习。graceful shutdown涉及到的相关JIRA如下: