作者 herman
简介
本文源自herman的系列文章之一《鹅厂开源框架TARS之运营服务监控》。相关代码已按TARS开源社区最新版本更新。
TARS框架为用户提供了涉及到开发、运维、以及测试的一整套解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。 它集可扩展协议编解码、高性能RPC通信框架、名字路由与发现、发布监控、日志统计、配置管理等于一体。其中发布监控,日志统计等运维功能依靠着TARS框架中的运维服务和工具,本文将对各运维服务的功能和作用进行分析,并通过部分源码帮助读者进一步理解TARS的工作原理。
如上图,可以清楚看到TARS框架的运维服务和工具:包括主控 Registry
、发布平台 Patch
、配置文件中心Config
、远程 Log
、指标统计 Stat
、业务信息 Property
、异常信息 Notify
等主要服务,再结合 TarsWeb 平台对这些服务进行可视化操作和运用,对开发和运维人员算是非常方便和人性化了。
TarsWeb 服务管理平台用于服务的管理与运维,功能包括:
TarsWeb 可视化管理平台对服务的管理和运维功能都是基于TARS框架运维服务的接口来提供服务的。
Registry 服务提供对象名称寻址服务,返回 IP:Port
列表,为客户端提供可用服务列表信息。
同时,它提供TARS框架核心管理功能 :服务部署、服务起停、服务状态信息查询、发布、配置管理、命令通知。
Registry
提供的接口有
同时主控提供了 AdminRegImp
(关联控制接口类),提供如下接口:
下面来看看 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服务提供服务的发布功能,用于实现服务发布包的上传、管理与发布,配合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
服务用于提供整套框架的配置文件保存和读取等操作,后台使用mysql
存储。配置拉取服务化,服务只需调用配置服务的接口即可获取到配置文件。
为了能灵活管理配置文件,配置文件分为几个级别:应用配置、Set配置、服务配置和节点配置。应用配置为最高一级的配置文件,它是多个服务配置提炼出来的公共配置,服务配置通过引用它来使用其配置内容。 Set配置是具体一个Set分组下所有服务的公共配置,在应用配置的基础上进行补充追加。 服务配置是具体一个服务下所有节点的公共配置,可以引用应用配置。 节点配置是一个应用节点的个性化配置,它和服务配置合并成为具体一个服务节点的配置。
下图是服务配置管理页面
在服务业务代码中,可以通过调用 addAppConfig
和 addConfig
来分别添加应用级配置文件和服务配置文件。接下来我们来对 addAppConfig
和 addConfig
的添加配置的过程进行分析。
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(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) + "' "
...
至此,实现了从指定服务器拉取配置文件的功能 (这里还关系到引用配置等功能,多个同名文件还涉及到文件合并等,这里先不做详细说明)。
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 服务定义的接口:
/**
* 上报模块间调用信息
* @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服务提供接口 reportPropMsg
进行服务特性上报,接口声明如下
/**
* 上报属性信息
* @param statmsg, 上报信息
* @return int, 返回0表示成功
*/
virtual int reportPropMsg(const map<StatPropMsgHead,StatPropMsgBody>& propMsg, tars::TarsCurrentPtr current );
官方文档定义为异常信息,用于获取服务的业务异常上报的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
服务提供的主要接口:
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/
或扫码获取:
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。