微服务开源框架TARS 之 框架服务解析

banner

做者 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分组下全部服务的公共配置,在应用配置的基础上进行补充追加。
  • 服务配置是具体一个服务下全部节点的公共配置,能够引用应用配置。
  • 节点配置是一个应用节点的个性化配置,它和服务配置合并成为具体一个服务节点的配置。

下图是服务配置管理页面

服务配置

添加配置的实现

在服务业务代码中,能够经过调用 addAppConfigaddConfig 来分别添加应用级配置文件和服务配置文件。接下来咱们来对 addAppConfigaddConfig 的添加配置的过程进行分析。

addAppConfig

addAppConfig("DBConnection.conf"); //添加应用级别的配置文件
  • addAppConfig 中会调用 RemoteConfig::getInstance()->addConfig(filename, result, true) 函数;
  • 而后该函数会调用 getRemoteFile 函数从 Config 服务远程获取配置文件信息。 getRemoteFile 函数中,经过 _configPrxConfig 服务发起 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上能够查询到相应的日志

Stat日志查询

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/

或扫码获取:

QR