TARS 服务优雅关闭
TARS是腾讯开源的微服务调用框架,框架基础设施(RegistryServer,NodeServer,ConfigServer等)是由C++编写,框架上的微服务支持C++、Java、Go等。在众多介绍TARS的使用文章中都是介绍服务的开发及部署,没有文章介绍服务如何进行优雅关闭,优雅关闭对于生产环境尤为重要,不支持优雅关闭将在服务升级或服务实例调整时对业务产生影响。本文将讲通过分析TARS框架源码介绍一种实现优雅关闭的方式。
TARS服务关闭逻辑
AdminRegistryServer
WEB调用AdminRegistryServer的stopServer方法停止服务,stopServer代码比较简单概括说此方法做了两件事:
- 更新数据库服务状态(RegistryServer查询服务数据库)
- 通知tarsnode关闭服务,stopServer方法
NodeServer
NodeServer是结束进程的实施者,AdminRegistryServer通过调用NodeServer接口方法stopServer关闭服务,在stopServer中由CommandStop类来处理具体的服务进程停止操作,我们看一下CommandStop的核心方法executor的源代码:
int CommandStop::execute(string& sResult) { bool needWait = false; ........... string sStopScript = _serverObjectPtr->getStopScript(); //配置了脚本或者非tars服务 if( TC_Common::lower(TC_Common::trim(_desc.serverType)) == "tars_php" ){ 生成停止shell脚本 ............... }else if (!sStopScript.empty() || _serverObjectPtr->isTarsServer() == false) { 如果设置了停止脚本,则运行启动脚本 map<string, string> mResult; _serverObjectPtr->getActivator()->doScript(sStopScript, sResult, mResult); 设置需要等待 needWait = true; } else { 如果没有设置关闭脚本,直接调用Admin服务关闭(每个TARS服务都会自动产生一个Admin服务) if (_useAdmin) { AdminFPrx pAdminPrx; //服务管理代理 string sAdminPrx = "AdminObj@" + _serverObjectPtr->getLocalEndpoint().toString(); 异步调用关闭方法,并设置等待 pAdminPrx->async_shutdown(NULL); needWait = true; } } } catch (exception& e) { ..... } catch (...) { ..... } //等待STOP_WAIT_INTERVAL秒 time_t tNow = TNOW; int iStopWaitInterval = STOP_WAIT_INTERVAL; try { 服务停止,超时时间自己定义,单位MS TC_Config conf; conf.parseFile(_serverObjectPtr->getConfigFile()); iStopWaitInterval = TC_Common::strto<int>(conf["/tars/application/server<deactivating-timeout>"]) / 1000; 最短不短于,服务停止检测间隔 2S if (iStopWaitInterval < STOP_WAIT_INTERVAL) { iStopWaitInterval = STOP_WAIT_INTERVAL; } 时间最长不超过60S if (iStopWaitInterval > 60) { iStopWaitInterval = 60; } } catch (...) { } 等待服务进程关闭 if (needWait) { while (TNOW - iStopWaitInterval < tNow) { if (_serverObjectPtr->checkPid() != 0) //如果pid已经不存在 { 等待过程中服务关闭,则完成服务关闭 return 0; } } ........ } 仍然失败。用kill -9,再等待STOP_WAIT_INTERVAL秒。 .............. return -1; } |
服务Admin接口(AdminFServantImpl)
public void shutdown() {
try {
System.out.println(ConfigurationManager.getInstance().getServerConfig().getApplication() + "." +
ConfigurationManager.getInstance().getServerConfig().getServerName() + " is stopped.");
NotifyHelper.getInstance().syncReport("[alarm] server is stopped.");
} catch (Exception e) {
OmLogger.record("shutdown error", e);
}
System.exit(0);
}
分析
以上是对代码的分析(关键部分已经红字标注),我们可以画出整体逻辑图。
TARS服务发现及关闭逻辑图:

其中红色线条为服务管理数据流,黑色为微服务运行时数据流。
先来看微服务运行时:
- 客户端通过RegistryServer查询调用服务的服务器列表
- RegistryServer查询数据库,找出active的服务
- 客户端根据此列表负载均衡的访问各个服务。
- 客户端通过RegistryServer定期更新服务列表,通常20-30s
服务管理:
- 管理员通过WEB调用AdminRegistryServer关闭服务
- AdminRegistryServer更新数据库服务状态位inactive
- 通过调用相应服务器节点的NodeServer关闭服务
如果我们没有设置过停止脚本,那么Admin结束后直接关闭了服务。如果这时客户端还没有刷新服务器列表则一定时间内将出现服务调用失败,如下图所示:
通过分析服务关闭逻辑我们可以利用关闭脚本延迟服务的关闭,在客户端刷新周期内使服务继续服务:
- 服务延时一段时间关闭,延时时间为框架默认通过TarsQuery模块刷新服务地址的时间(<30s)
- 配置服务deactivating-timeout时间,避免服务被NodeServer强行关闭
1.配置服务关闭脚本
2.配置服务模板,修改deactivating-timeout
3.添加关闭脚本
脚本内容:
#!/bin/sh PID=`ps -eopid,cmd | grep "服务名特征" | grep -v "grep" |awk '{print $1}'` echo $PID sleep 40 延迟40s关闭 if [ "$PID" != "" ]; then echo "kill -2 $PID" kill -2 $PID 使用2,是应用有机会处理退出逻辑 fi |
完成以上配制后再关闭服务,node将延时时间T关闭服务,只要客户端的服务列表刷新时间小于T,将不会出现错误实现优雅关闭,如下图:
1.关闭服务节点
节点状态在数据库中置为inactive
2.客户端刷新列表
排除inactive节点
3.到达时间T
节点上服务关闭