做者 hermannode
簡介mysql
本文源自herman的系列文章之一《鵝廠開源框架TARS之運營服務監控》。相關代碼已按TARS開源社區最新版本更新。git
TARS框架爲用戶提供了涉及到開發、運維、以及測試的一整套解決方案,幫助一個產品或者服務快速開發、部署、測試、上線。 它集可擴展協議編解碼、高性能RPC通訊框架、名字路由與發現、發佈監控、日誌統計、配置管理等於一體。其中發佈監控,日誌統計等運維功能依靠着TARS框架中的運維服務和工具,本文將對各運維服務的功能和做用進行分析,並經過部分源碼幫助讀者進一步理解TARS的工做原理。github
目錄
運維工具概覽
如上圖,能夠清楚看到TARS框架的運維服務和工具:包括主控 Registry
、發佈平臺 Patch
、配置文件中心Config
、遠程 Log
、指標統計 Stat
、業務信息 Property
、異常信息 Notify
等主要服務,再結合 TarsWeb 平臺對這些服務進行可視化操做和運用,對開發和運維人員算是很是方便和人性化了。sql
TarsWeb —— 管理平臺
TarsWeb 服務管理平臺用於服務的管理與運維,功能包括:數據庫
- 提供服務狀態信息查詢和起停服務、設置服務日誌級別、發送自定義命令等操做頁面
- 提供部署服務、自動編譯發佈、配置管理等運維操做頁面
- 提供自動測試操做界面
- 展現服務性能指標數據
- 展現業務特性指標數據
TarsWeb 可視化管理平臺對服務的管理和運維功能都是基於TARS框架運維服務的接口來提供服務的。服務器
Registry服務 —— 主控服務
Registry 服務提供對象名稱尋址服務,返回 IP:Port
列表,爲客戶端提供可用服務列表信息。app
同時,它提供TARS框架核心管理功能 :服務部署、服務起停、服務狀態信息查詢、發佈、配置管理、命令通知。框架
Registry
提供的接口有運維
- registerNode(node節點註冊)
- keepAlive(node上報心跳負載)
- getServers(獲取在該node部署的server列表)
- updateServer(更新server狀態)
同時主控提供了 AdminRegImp
(關聯控制接口類),提供以下接口:
- getAllApplicationNames(獲取application列表)
- getAllNodeNames(獲取node列表)
- shutdownNode(中止 node)
- getServerState(獲取特定server狀態)
- startServer(啓動特定server)
- restartServer( 重啓特定server)
- notifyServer(通知服務)
- patchServer(發佈特定server)
下面來看看 startServer
接口如何實現。
startServer
接口的實現中,先調用 updateServerState
更新服務的狀態
//更新數據庫server的設置狀態 DBPROXY->updateServerState(application, serverName, nodeName, "setting_state", tars::Active);
而後判斷 server 是否爲 DNS:DNS直接經過db修改狀態,非DNS則經過Node節點啓動服務(下節介紹Node節點)
if(server.size() != 0 && server[0].serverType == "tars_dns") { TLOGINFO(" '" + application + "." + serverName + "_" + nodeName + "' is tars_dns server" << endl); iRet = DBPROXY->updateServerState(application, serverName, nodeName, "present_state", tars::Active); } else { // 獲取Node節點proxy NodePrx nodePrx = DBPROXY->getNodePrx(nodeName); TLOGINFO("call node into " << __FUNCTION__ << "|" << application << "." << serverName << "_" << nodeName << "|" << current->getHostName() << ":" << current->getPort() <<endl); current->setResponse(false); NodePrxCallbackPtr callback = new StartServerCallbackImp(application, serverName, nodeName, current); // 異步啓動服務 nodePrx->async_startServer(callback, application, serverName); }
Patch服務 —— 統一發布
Patch服務提供服務的發佈功能,用於實現服務發佈包的上傳、管理與發佈,配合TarsWeb平臺,可以管理全部須要發佈的服務和文件的目錄,以下
Patch 服務中定義瞭如下四個接口
/** * 獲取路徑下全部文件列表信息 * @param path, 目錄路徑, 相對_directory的路徑, 不能有.. * @param vector<FileInfo>, 文件列表信息 * @return int */ int listFileInfo(const string &path, vector<FileInfo> &vf, TarsCurrentPtr current); /** * 下載文件 * @param file, 文件徹底路徑 * @param pos, 從什麼位置開始下載 * @return vector<byte>, 文件內容 */ int download(const string &file, int pos, vector<char> &vb, TarsCurrentPtr current); /** * 準備好須要patch的文件,將發佈的文件從上傳目錄複製到發佈目錄 * @param app, 應用名 * @param serverName, 服務名 * @param patchFile, 須要發佈的文件名 * @return int, 0: 成功, <0: 失敗 */ int preparePatchFile(const string &app, const string &serverName, const string &patchFile, TarsCurrentPtr current); /** * 刪除patch文件 * @param app * @param serverName * @param patchFile * @param current * @return */ int deletePatchFile(const string & app, const string & serverName, const string & patchFile, TarsCurrentPtr current);
Config 服務 —— 配置中心
Config
服務用於提供整套框架的配置文件保存和讀取等操做,後臺使用mysql
存儲。配置拉取服務化,服務只需調用配置服務的接口便可獲取到配置文件。
爲了能靈活管理配置文件,配置文件分爲幾個級別:應用配置、Set配置、服務配置和節點配置。
- 應用配置爲最高一級的配置文件,它是多個服務配置提煉出來的公共配置,服務配置經過引用它來使用其配置內容。
- Set配置是具體一個Set分組下全部服務的公共配置,在應用配置的基礎上進行補充追加。
- 服務配置是具體一個服務下全部節點的公共配置,能夠引用應用配置。
- 節點配置是一個應用節點的個性化配置,它和服務配置合併成爲具體一個服務節點的配置。
下圖是服務配置管理頁面
添加配置的實現
在服務業務代碼中,能夠經過調用 addAppConfig
和 addConfig
來分別添加應用級配置文件和服務配置文件。接下來咱們來對 addAppConfig
和 addConfig
的添加配置的過程進行分析。
addAppConfig
addAppConfig("DBConnection.conf"); //添加應用級別的配置文件
addAppConfig
中會調用RemoteConfig::getInstance()->addConfig(filename, result, true)
函數;- 而後該函數會調用
getRemoteFile
函數從Config
服務遠程獲取配置文件信息。getRemoteFile
函數中,經過_configPrx
向Config
服務發起rpc
調用實現,以下
_configPrx->loadConfig(_sApp, (bAppConfigOnly ? "" : _sServerName), sFileName, stream);
ConfigImp::loadConfig
函數是 Config
的接口函數 ,若是是應用級別的配置文件,則執行 select id,config from t_config_files...
而且把結果使用string
的方式回傳給調用者。
調用者獲取到文件內容,根據文件的名字生成在本地以供後續程序使用文件:
std::ofstream out(newFile.c_str());
addConfig
addConfig(ServerConfig::ServerName + ".conf");
和 addAppConfig
類似, addConfig
也會調用 RemoteConfig::getInstance()->addConfig(filename, result, false)
。最後一個參數 bAppConfigOnly
設置爲 false
,表示獲取服務配置,而不是應用配置。
接着進行 rpc
調用
int ret = _configPrx->loadConfig(_sApp, (bAppConfigOnly ? "" : _sServerName), sFileName, stream);
和加載應用級別的配置文件有所不一樣,這裏調用 _configPrx
服務器的參數,增長了 _sServerName
,即 rpc
調用 loadConfig
接口的時候 server
參數不爲空字符串,會改調用 loadConfigByHost
函數
... if(!server.empty()) { return loadConfigByHost(app + "." + server, fileName, current->getHostName() , config, current); } else { return loadAppConfig(app, fileName, config, current); }
loadConfigByHost
函數一樣是經過mysql查詢 t_config_files
表格,但修改了 where
查詢條件,appName
替換爲 appServerName
,即查詢服務配置,以下
... "where server_name = '" + _mysqlConfig.escapeString(appServerName) + "' " ...
至此,實現了從指定服務器拉取配置文件的功能 (這裏還關係到引用配置等功能,多個同名文件還涉及到文件合併等,這裏先不作詳細說明)。
Log服務 —— 日誌中心
TARS框架的日誌服務,用於接收遠程日誌。
提供兩個接口,以下
/** * 輸出日誌信息到指定文件 * @param app 業務名稱 * @param server 服務名稱 * @param file 日誌文件名稱 * @param format 日誌輸出格式 * @param buffer 日誌內容 */ void logger(const string &app, const string &server, const string &file, const string &format, const vector<string> &buffer, tars::TarsCurrentPtr current); /** * 獲取數據 * @param info * @param buffer */ void loggerbyInfo(const LogInfo & info,const vector<std::string> & buffer,tars::TarsCurrentPtr current);
業務服務以框架層的 API 異步發送日誌到日誌服務器,例如 Stat 服務的 ReapSSDThread::run
中經過 FDLOG
發送日誌
FDLOG("CountStat") << "stat ip:" << ServerConfig::LocalIp << "|Buffer Index:" << iBufferIndex << "|ReapSSDThread::run insert record num:" << iTotalNum << "|tast patch finished." << endl;
在TarsWeb上能夠查詢到相應的日誌
FDLOG
的定義在庫文件的 RemoteLogger.h
,定義以下:
#define FDLOG(x) (RemoteTimeLogger::getInstance()->logger(x)->any())
RemoteTimeLogger::getInstance()->logger(x)
函數生成並返回:TimeLogger*
,TimeLogger
的定義以下:
typedef TC_Logger<TimeWriteT, TC_RollByTime> TimeLogger;
其中 TC_Logger
爲日誌基類模板:模板第一個參數 TimeWriteT
負責寫Logger。在 applicantion 服務啓動的時候會調用設置遠程日誌服務器對象的服務,例如 log=tars.tarslog.LogObj
,而後調用 setLogInfo
設置本地信息
RemoteTimeLogger::getInstance()->setLogInfo(_communicator, ServerConfig::Log, ServerConfig::Application, ServerConfig::ServerName, ServerConfig::LogPath,setDivision());
這裏底層實現函數以下,會獲取遠程日誌服務器的地址
void RemoteTimeLogger::setLogInfo(const CommunicatorPtr &comm, const string &obj, const string &sApp, const string &sServer, const string &sLogpath, const string& setdivision, const bool &bLogStatReport) { _app = sApp; _server = sServer; _logpath = sLogpath; _comm = comm; _setDivision = setdivision; _logStatReport = bLogStatReport; if(!obj.empty()) { _logPrx = _comm->stringToProxy<LogPrx>(obj); //單獨設置超時時間 _logPrx->tars_timeout(3000); if(_defaultLogger) { _defaultLogger->getWriteT().setLogPrx(_logPrx); } } //建立本地目錄 TC_File::makeDirRecursive(_logpath + "/" + _app + "/" + _server); }
具體寫日誌的時候,TimeWriteT
類重載了 operator()
,會調用遠程日誌服務的logger
接口寫遠程日誌,從而實現了日誌從本地到遠程日誌服務器的功能
void TimeWriteT::operator()(ostream &of, const deque<pair<size_t, string> > &buffer) { ... _logPrx->logger(DYEING_DIR, DYEING_FILE, "day", "%Y%m%d", vDyeingLog, ServerConfig::Context); ... }
Stat服務 —— 性能指標統計
Stat 服務用於監控服務進程的運行質量,提供服務模塊間調用信息統計上報的功能。Stat採集的數據包含
- 服務的性能數據,包括:調用時間、成功次數、超時次數、異常次數、耗時分佈等信息。
- 服務間的調用關係鏈採樣,包括:主調、被調、接口名等信息,以便定位問題服務。
如下爲 Stat 服務定義的接口:
/** * 上報模塊間調用信息 * @param statmsg, 上報信息 * @return int, 返回0表示成功 */ virtual int reportMicMsg( const map<tars::StatMicMsgHead, tars::StatMicMsgBody>& statmsg, bool bFromClient, tars::TarsCurrentPtr current ); /** * 上報模塊間調用採樣信息 * @param sample, 上報信息 * @return int, 返回0表示成功 */ virtual int reportSampleMsg(const vector<StatSampleMsg> &msg,tars::TarsCurrentPtr current );
Property服務 —— 業務指標統計
Property服務提供用戶自定義屬性上報功能,用於監控業務的運行質量狀況和相關指標的統計。採集的數據包含
- 服務業務特性數據,包括內存大小、隊列大小、cache命中率等;支持平均、計數、求和、分佈等統計方式。
Property服務提供接口 reportPropMsg
進行服務特性上報,接口聲明以下
/** * 上報屬性信息 * @param statmsg, 上報信息 * @return int, 返回0表示成功 */ virtual int reportPropMsg(const map<StatPropMsgHead,StatPropMsgBody>& propMsg, tars::TarsCurrentPtr current );
Notify服務 —— 異常信息
官方文檔定義爲異常信息,用於獲取服務的業務異常上報的report,輸出的信息能夠在TarsWeb平臺裏面看到。
具體服務業務代碼中,經過獲取 RemoteNotify
實例調用 report
上報異常信息,例如:
RemoteNotify::getInstance()->report("This is a report test");
服務管理界面回顯示服務上報的異常信息,以下
源碼實現主要部分以下:
if(_notifyPrx) { ReportInfo info; info.eType = REPORT; info.sApp = _app; info.sServer = _serverName; info.sSet = _setName; info.sThreadId = TC_Common::tostr(std::this_thread::get_id()); info.sMessage = sResult; info.sNodeName = _nodeName; if(!bSync) { _notifyPrx->async_reportNotifyInfo(NULL, info); } else { _notifyPrx->reportNotifyInfo(info); } }
Notify 服務的實現,主要就是把數據插入 t_server_notifys_
數據庫。這裏默認都是使用異步的接口,不然效率可能會有問題,畢竟是直接操做db,相關操做源碼以下
... string sql; TC_Mysql::RECORD_DATA rd; rd["application"] = make_pair(TC_Mysql::DB_STR, info.sApp); rd["server_name"] = make_pair(TC_Mysql::DB_STR, info.sServer); rd["container_name"] = make_pair(TC_Mysql::DB_STR, info.sContainer); rd["server_id"] = make_pair(TC_Mysql::DB_STR, info.sApp + "." + info.sServer + "_" + nodeId); rd["node_name"] = make_pair(TC_Mysql::DB_STR, nodeId); rd["thread_id"] = make_pair(TC_Mysql::DB_STR, info.sThreadId); if (!info.sSet.empty()) { vector<string> v = TC_Common::sepstr<string>(info.sSet, "."); if (v.size() != 3 || (v.size() == 3 && (v[0] == "*" || v[1] == "*"))) { TLOGERROR("NotifyImp::reportNotifyInfo bad set name:" << info.sSet << endl); } else { rd["set_name"] = make_pair(TC_Mysql::DB_STR, v[0]); rd["set_area"] = make_pair(TC_Mysql::DB_STR, v[1]); rd["set_group"] = make_pair(TC_Mysql::DB_STR, v[2]); } } else { rd["set_name"] = make_pair(TC_Mysql::DB_STR, ""); rd["set_area"] = make_pair(TC_Mysql::DB_STR, ""); rd["set_group"] = make_pair(TC_Mysql::DB_STR, ""); } rd["result"] = make_pair(TC_Mysql::DB_STR, info.sMessage); rd["notifytime"] = make_pair(TC_Mysql::DB_INT, "now()"); string sTable = "t_server_notifys"; try { _mysqlConfig.insertRecord(sTable, rd); } ...
Node服務 —— 節點管理
服務節點能夠認爲是服務所實際運行的一個具體的操做系統實例,能夠是物理主機或者虛擬主機、雲主機。每臺服務節點上均有一個Node服務和多個業務服務,Node服務會對業務服務進行統一管理,包括:
- 同一節點(容器、服務器、虛擬機等)上的服務起停、服務狀態信息採集、發佈、配置管理、自定義消息通知。
- 同一節點上的服務監控,異常退出、僵死等監控重啓。
Node
服務提供的主要接口:
- patch(patch指定服務)
- destroyServer (銷燬指定服務)
- shutdown (關閉Node)
- stopAllServers (中止全部服務)
- loadServer (加載指定服務)
- startServer (啓動指定服務)
- stopServer (中止指定服務)
- notifyServer (通知服務)
Registry 服務的 startServer
會調用本節中 Node 服務的 startServer
接口,startServer
再經過調用 CommandStart
類的 startByScript
函數實現服務的啓動,該派生自 ServerCommand
,同理也有 CommandStop
, CommandPatch
等,具體詳見源碼。
startByScript
經過拉取服務的啓動腳本啓動服務,啓動腳本通常隨服務conf存儲在DB中,而後調用如下函數執行腳本啓動服務
_serverObjectPtr->getActivator()->activate(sStartScript, sMonitorScript, sResult);
最終經過C語言exec
系列函數執行啓動腳本啓動服務。
除了以上主要的管理服務的接口外,還有用於獲取Node服務信息的接口,如 getState
用於獲取指定服務狀態,實現以下:
ServerState NodeImp::getState( const string& application, const string& serverName, string &result, TarsCurrentPtr current ) { string serverId = application + "." + serverName; result = string(__FUNCTION__)+" ["+serverId+"] "; ServerObjectPtr pServerObjectPtr = ServerFactory::getInstance()->getServer( application, serverName ); if ( pServerObjectPtr ) { result += "succ"; return pServerObjectPtr->getState(); } result += "server not exist"; NODE_LOG(serverId)->error() << "NodeImp::getState " << result << endl; return tars::Inactive; }
總結
本文介紹分析了TARS框架中如何經過不一樣運維服務工具實現對服務的運營和監控,爲開發和運維人員提供方便、人性化的服務管理和維護功能。
TARS能夠在考慮到易用性和高性能的同時快速構建系統並自動生成代碼,幫助開發人員和企業以微服務的方式快速構建本身穩定可靠的分佈式應用,從而令開發人員只關注業務邏輯,提升運營效率。多語言、敏捷研發、高可用和高效運營的特性使 TARS 成爲企業級產品。
TARS微服務助您數字化轉型,歡迎訪問:
TARS官網:https://TarsCloud.org
TARS源碼:https://github.com/TarsCloud
獲取《TARS官方培訓電子書》:https://wj.qq.com/s2/6570357/3adb/
或掃碼獲取: