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
節點上服務關閉