- C++ Server
- C++ Clinet
- Asynchronous nesting
- dyeing
- Tars protocol packet size
- The return code defined by the tars
- Business configuration
- Log
- Service management
- Statistical report
- Abnormally Report
- Attribute statistics
- Tars call chain
Here is a complete example of how to use TARS to implement your own services.
The following code describes an example of sending a hello world string to the server by a client and returning the Hello word string by the server.
Write the tars file as follows, Hello.tars:
module TestApp
{
interface Hello
{
int test();
int testHello(string sReq, out string sRsp);
};
};
The C++ file is automatically generated by the tars2cpp tool: the /usr/local/tars/cpp/tools/tars2cpp hello.tars generates a hello.h file, which is included by the client and the server.
The interface defined in the tars file is implemented in the code of the service:
HelloImp.h
#ifndef _HelloImp_H_
#define _HelloImp_H_
#include "servant/Application.h"
#include "Hello.h"
/**
* HelloImp inherits the Hello objects defined in the hello.h
*
*/
class HelloImp : public TestApp::Hello
{
public:
/**
*
*/
virtual ~HelloImp() {}
/**
* Initialization, the virtual function in Hello is called when HelloImp initializes.
*/
virtual void initialize();
/**
* Deconstruction, The virtual function in Hello is called when the service destruct HelloImp exits.
*/
virtual void destroy();
/**
* Implement the test interface defined in the tars file
*/
virtual int test(tars::TarsCurrentPtr current) { return 0;};
/**
* Implement the test interface defined in the tars file
*/
virtual int testHello(const std::string &sReq, std::string &sRsp, tars::TarsCurrentPtr current);
};
/////////////////////////////////////////////////////
#endif
HelloImp.cpp
#include "HelloImp.h"
#include "servant/Application.h"
using namespace std;
//////////////////////////////////////////////////////
void HelloImp::initialize()
{
//initialize servant here:
//...
}
//////////////////////////////////////////////////////
void HelloImp::destroy()
{
//destroy servant here:
//...
}
int HelloImp::testHello(const std::string &sReq, std::string &sRsp, tars::TarsCurrentPtr current)
{
TLOGDEBUG("HelloImp::testHellosReq:"<<sReq<<endl);
sRsp = sReq;
return 0;
}
The framework code of the server
HelloServer.h:
#ifndef _HelloServer_H_
#define _HelloServer_H_
#include <iostream>
#include "servant/Application.h"
using namespace tars;
/**
* The Application class in the framework is inherited by HelloServer
**/
class HelloServer : public Application
{
public:
/**
*
**/
virtual ~HelloServer() {};
/**
* The initialization interface of the server
**/
virtual void initialize();
/**
* The interface used to clean up when the server exits
**/
virtual void destroyApp();
};
extern HelloServer g_app;
////////////////////////////////////////////
#endif
The contents of the HelloServer.cpp are as follows:
#include "HelloServer.h"
#include "HelloImp.h"
using namespace std;
HelloServer g_app;
/////////////////////////////////////////////////////////////////
void
HelloServer::initialize()
{
//initialize application here:
//Adding Servant interface to implement binding between class HelloImp and routing Obj
addServant<HelloImp>(ServerConfig::Application + "." + ServerConfig::ServerName + ".HelloObj");
}
/////////////////////////////////////////////////////////////////
void
HelloServer::destroyApp()
{
//destroy application here:
//...
}
/////////////////////////////////////////////////////////////////
int
main(int argc, char* argv[])
{
try
{
g_app.main(argc, argv);
g_app.waitForShutdown();
}
catch (std::exception& e)
{
cerr << "std::exception:" << e.what() << std::endl;
}
catch (...)
{
cerr << "unknown exception." << std::endl;
}
return -1;
}
////////////////////////////////////////////////////////////////
Illustration:
- Object HelloImp has a specific instance for each thread of the service, and the HelloImp interface is thread safe if it is only operated on the member variables of the HelloImp, and if the HelloImp interface needs to access the global object, it needs to be locked.
- ServerConfig::Application+“.”+ ServerConfig::ServerName + ".HelloObj"represents the name of the Hello object of the service, and the subsequent client can access the service through this name.
- A parameter shared by all functions is TarsCurrentPtr, and all original contents of the request packet can be obtained through this structure.
- HelloServer is a service class implemented by itself, inherited from the tars:: Application class in the server framework.
There is a global structure ServerConfig in the service framework, which records the basic information of the server.
The member variables of ServerConfig are static, and these parameters are automatically initialized from the service configuration file when the service framework is initialized.
The definition of ServerConfig is as follows:
struct ServerConfig
{
static std::string Application; //Application name
static std::string ServerName; //Server name. A server name contains one or more server identities.
static std::string BasePath; //The application path is used to save the local directory of remote system configuration.
static std::string DataPath; //The application data path is used to save common data files.
static std::string LocalIp; //Local IP
static std::string LogPath; //Log path
static int LogSize; //Log size (bytes)
static int LogNum; //Log number
static std::string LogLevel; //Log level
static std::string Local; //Local sockets
static std::string Node; //Local node address
static std::string Log; //Log center address
static std::string Config; //Configuration center address
static std::string Notify; //Information notification Center
static std::string ConfigFile; //Frame configuration file path
static int ReportFlow; //Whether to open the server to report all the interface flow, 0 is not reported, and 1 is reported (for non tars protocol service traffic statistics).
static int IsCheckSet; //Whether to check the legality of the call according to the set rules, 0 does not check, and 1 checks.
static bool OpenCoroutine; //Whether to open the coroutine
static size_t CoroutineMemSize; //The maximum value of the coroutine occupied memory space
static uint32_t CoroutineStackSize; //Stack size for each coroutine(default 128K)
Parameter Description:
- Application:Application name, if the configuration file is not set, the default is UNKNOWN;
- ServerName:The name of the server. The default is the name of the executable file.
- BasePath:Basic path, which usually represents the path of executable files, defaults to "/".
- DataPath:The data file path, which usually indicates the existence of its own data, such as the MMAP file, and so on.
- LocalIp:Local IP, which acquiescence is the first network card IP of this machine, not 127.0.0.1.
- LogPath:Log file path, please refer to follow up for log writing.
- LogSize:The size of the rolling log file;
- LogNum:The number of rolling log files;
- LogLevel:Rolling log level;
- Local:The server can have a management port, which can be sent to the server through a management port, which represents the address of the bound management port, such as TCP -h 127.0.0.1 -p 8899, without a management port if it is not set.
- Node:Local NODE address,If it is set, the heartbeat is sent to NODE regularly, otherwise the heartbeat is not transmitted, usually only when the service is posted to the frame.
- Log:Log center address, for example: tars.tarslog.LogObj@tcp - H... - P... If there is no configuration, the remote log is not recorded.
- Config:The address of the configuration center, for example: tars.tarsconfig.ConfigObj@tcp - H... -p... If there is no configuration, the addConfig function is invalid and the configuration can not be pulled from the remote configuration center.
- Notify:Information to the center address, for example: tars.tarsnotify.NotifyObj@tcp - H... -p... If there is no configuration, the information submitted will be discarded directly.
- ConfigFile:Frame configuration file path
- IsCheckSet:Whether to check the legality of the call according to the set rules, 0 does not check, and 1 checks.
- OpenCoroutine:Whether to open a coroutine mode, the default value is 0, which means that it is not enabled.
- CoroutineMemSize:The maximum value of the coroutine occupied memory space
- CoroutineStackSize:Stack size for each coroutine(default 128K)
The configuration of server is as below:
<tars>
<application>
<server>
#Ip:port of local node
node=tars.tarsnode.ServerObj@tcp -h 10.120.129.226 -p 19386 -t 60000
#Application name
app=TestApp
#Server name
server=HelloServer
#Local ip
localip=10.120.129.226
#Management port
local=tcp -h 127.0.0.1 -p 20001 -t 3000
#The server's executable files, configuration files, and so on
basepath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/bin/
#Data directory of the server
datapath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/data/
#Log path
logpath=/usr/local/app/tars/app_log/
#Rolling log size
logsize=10M
#The address of the configuration center
config=tars.tarsconfig.ConfigObj
#The address of the report [optional]
notify=tars.tarsnotify.NotifyObj
#The address of the remote log [optional]
log=tars.tarslog.LogObj
#Timeout time of server stop
deactivating-timeout=2000
#Log level
logLevel=DEBUG
</server>
</application>
</tars>
Adapter corresponds to the TC_EpollServer:: BindAdapter in the code, which indicates the binding port.
If a new binding port is added to the server, a new BindAdapter is set up, and the relevant parameters and processed objects can be very convenient to complete the processing on this port, usually with this function to complete the support of other protocols.
For TARS servers, adding adapter items to the server's configuration files means that TARS can be added to a Servant processing object.
The Adapter configuration is as follows:
<tars>
<application>
<server>
#Configuration of bound ports
<TestApp.HelloServer.HelloObjAdapter>
#Allowed IP address
allow
#The IP address of the listener
endpoint=tcp -h 10.120.129.226 -p 20001 -t 60000
#Processing group
handlegroup=TestApp.HelloServer.HelloObjAdapter
#Maximum connection
maxconns=200000
#Protocol
protocol=tars
#Queue size
queuecap=10000
#The timeout time of the queue (milliseconds)
queuetimeout=60000
#Processing object
servant=TestApp.HelloServer.HelloObj
#Current thread number
threads=5
</TestApp.HelloServer.HelloObjAdapter>
</server>
</application>
</tars>
Focus on the servant item. In HelloServer:: Initialize (), the matching between the configuration and the object in the code is completed.`
void HelloServer::initialize ()
{
//Add servant
addServant<HelloImp>(ServerConfig::Application+“.”+ ServerConfig::ServerName + ".HelloObj");
}
## 1.6. Server startup
The server's boot command is as follows:
HelloServer --config=config.conf
Note: config.conf is the configuration file, the configuration files of the server and the client configuration files must be merged into this file.
As for servers, they can be run separately and do not need to be published on the TARS system framework.
The complete configuration is as follows:
# 2. C++ Client
The client can complete the remote call without writing any code related to the protocol communication.The client code also needs to include the hello.h file.
## 2.1. Communicator
After the server is implemented, the client needs to send and receive data packets to the server. The client's operation of sending and receiving data packets to the server is implemented by Communicator.
** Note: A Tars service can only have one Communicator variable, which can be obtained with Application::getCommunicator() (if it is not the Tars service, create a communicator yourself).
The communicator is a carrier of client resources and contains a set of resources for sending and receiving packets, status statistics and other functions.
The communicator is initialized as follows:
TC_Config conf("config.conf"); CommunicatorPtr c = new Communicator(); //Initialize the communicator with a configuration file c-> setProperty(conf); //Or initialize directly with attributes c->setProperty("property", "tars.tarsproperty.PropertyObj"); c->setProperty("locator", "tars.tarsregistry.QueryObj@tcp -h ... -p ...");
Description:
> * The communicator's configuration file format will be described later.
> * Communicators can be configured without a configuration file, and all parameters have default values.
> * The communicator can also be initialized directly through the "Property Settings Interface".
> * If you need to get the RPC call proxy through the name server, you must set the locator parameter.
Communicator attribute description:
> * locator:The address of the registry service must be in the format "ip port". If you do not need the registry to locate the service, you do not need to configure this item.
> * sync-invoke-timeout:The maximum timeout (in milliseconds) for synchronous calls. The default value for this configuration is 3000.
> * async-invoke-timeout:The maximum timeout (in milliseconds) for asynchronous calls. The default value for this configuration is 5000.
> * refresh-endpoint-interval:The interval (in milliseconds) for periodically accessing the registry to obtain information. The default value for this configuration is one minute.
> * stat:The address of the service is called between modules. If this item is not configured, it means that the reported data will be directly discarded.
> * property:The address that the service reports its attribute. If it is not configured, this means that the reported data is directly discarded.
> * report-interval:The interval at which the information is reported to stat/property. The default is 60000 milliseconds.
> * asyncthread:The number of threads that process asynchronous responses when taking an asynchronous call. The default is 1.
> * modulename:The module name, the default value is the name of the executable program.
The format of the communicator's configuration file is as follows:
Instructions for use:
> * When using the Tars framework for server use, users do not need to create their own communicators, directly using the communicator in the service framework. E.g: Application::getCommunicator()->stringToProxy(...). For a pure client scenario, the user needs to define a communicator and generate a service proxy.
> * Application::getCommunicator() is a static function of the Application class, which can be obtained at any time;
> * For the service proxy created by the communicator, it is also not necessary to call stringToProxy() before each use. The service proxy will be established during initialization, and it can be used directly afterwards.
> * For the creation and use of agents, please see the following sections;
> * For the same Obj name, the service proxy obtained by calling stringToProxy() multiple times is actually the same variable, which is safe for multi-threaded calls and does not affect performance.
> * The ip list corresponding to obj can be obtained by Application::getCommunicator()->getEndpoint("obj").Another way to get an IP list is to get it directly from the proxy generated by the communicator, such as Application::getCommunicator()->stringToProxy(...) ->getEndpoint().
## 2.2. Timeout control
The timeout control is for the client proxy. There are records in the configuration file of the communicator described in the previous section:
sync-invoke-timeout = 3000
async-invoke-timeout = 5000
The above timeout is valid for all the proxies generated by the communicator.
If you need to set the timeout separately, as shown below:
Set the timeout period for the proxy:
ProxyPrx pproxy; //Set the timeout for the agent's synchronous call (in milliseconds) pproxy->tars_timeout(3000); //Sets the timeout for the agent's asynchronous call (in milliseconds) pproxy->tars_async_timeout(4000);
Set the timeout for the calling interface:
//Set the timeout (in milliseconds) for this interface call of this agent. This setting will only take effect once. pproxy->tars_set_timeout(2000)->a();
## 2.3. Call interface
This section details how the Tars client remotely invokes the server.
First, briefly describe the addressing mode of the Tars client. Secondly, it will introduce the calling method of the client, including but not limited to one-way calling, synchronous calling, asynchronous calling, hash calling, and so on.
### 2.3.1. Introduction to addressing mode
The addressing mode of the Tars service can usually be divided into two ways: the service name is registered in the master and the service name is not registered in the master. A master is a name server (routing server) dedicated to registering service node information.
The service name added in the name server is implemented through the operation management platform.
For services that are not registered with the master, it can be classified as direct addressing, that is, the ip address of the service provider needs to be specified before calling the service. The client needs to specify the specific address of the HelloObj object when calling the service:
that is: Test.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 9985
Test.HelloServer.HelloObj: Object name
tcp:Tcp protocol
-h:Specify the host address, here is 127.0.0.1
-p:Port, here is 9985
If HelloServer is running on two servers, HelloPrx is initialized as follows:
HelloPrx pPrx = c->stringToProxy
The address of HelloObj is set to the address of the two servers. At this point, the request will be distributed to two servers (distribution method can be specified, not introduced here). If one server is down, the request will be automatically assigned to another one, and the server will be restarted periodically.
For services registered in the master, the service is addressed based on the service name. When the client requests the service, it does not need to specify the specific address of the HelloServer, but it needs to specify the address of the `registry` when generating the communicator or initializing the communicator.
The following shows the address of the registry by setting the parameters of the communicator:
CommunicatorPtr c = new Communicator(); c->setProperty("locator", "tars.tarsregistry.QueryObj@tcp -h .. -p ..")
Since the client needs to rely on the registry's address, the registry must also be fault-tolerant. The registry's fault-tolerant method is the same as above, specifying the address of the two registry.
### 2.3.2. One-way call
A one-way call means that the client only sends data to the server without receiving the response from the server, and whether the server receives the request data.
TC_Config conf("config.conf");
CommunicatorPtr c = new Communicator();
//Initialize the communicator with a configuration file
c-> setProperty(conf);
//Generate a client's service proxy
HelloPrx pPrx = c->stringToProxy
### 2.3.3. Synchronous call
Take a look at the code example below:
TC_Config conf("config.conf");
CommunicatorPtr c = new Communicator();
//Initialize the communicator with a configuration file
c-> setProperty(conf);
//Generate a client's service proxy
HelloPrx pPrx = c->stringToProxy
The above example shows that the client initiates a remote synchronization call to the HelloObj object of the HelloServer.
### 2.3.4. Asynchronous call
Define an asynchronous callback object:
struct HelloCallback : public HelloPrxCallback { //Callback virtual void callback_testHello(int ret, const string &r) {
assert(r == "hello word");
}
virtual void callback_testHello_exception(tars::Int32 ret) {
assert(ret == 0);
cout << "callback exception:" << ret << endl;
} };
TC_Config conf("config.conf");
CommunicatorPtr c = new Communicator();
//Initialize the communicator with a configuration file
c-> setProperty(conf);
//Generate a client's service proxy
HelloPrx pPrx = c->stringToProxy
//Initiate a remote synchronization call string s = "hello word"; string r; pPrx->async_testHello(cb, s);
note:
> * When a response from the server is received, HelloPrxCallback::callback_testHello() will be called.
> * If the asynchronous call returns an exception or timeout, then HelloPrxCallback::callback_testHello_exception() will be called with the return value defined as follows:
//The return code given by the TARS server const int TARSSERVERSUCCESS = 0; //The server is successfully processed const int TARSSERVERDECODEERR = -1; //Server decoding exception const int TARSSERVERENCODEERR = -2; //Server encoding exception const int TARSSERVERNOFUNCERR = -3; //The server does not have this function const int TARSSERVERNOSERVANTERR = -4; //The server does not have the Servant object. const int TARSSERVERRESETGRID = -5; //Inconsistent gray state on the server side const int TARSSERVERQUEUETIMEOUT = -6; //Server queue exceeded limit const int TARSASYNCCALLTIMEOUT = -7; //Asynchronous call timeout const int TARSINVOKETIMEOUT = -7; //Call timeout const int TARSPROXYCONNECTERR = -8; //Proxy link exception const int TARSSERVEROVERLOAD = -9; //The server is overloaded and exceeds the queue length. const int TARSADAPTERNULL = -10; //The client routing is empty, the service does not exist or all services are offline. const int TARSINVOKEBYINVALIDESET = -11; //The client is called by an invalid set rule const int TARSCLIENTDECODEERR = -12; //Client decoding exception const int TARSSERVERUNKNOWNERR = -99; //Server location is abnormal
### 2.3.5. Set mode call
Currently, the framework already supports the deployment of services in set mode. After deployment by set, calls between services are transparent to business development. However, because some services have special requirements, after the deployment by set, the client can specify the set name to invoke the server. So the framework adds the ability for the client to specify the set name to call those services deployed by set.
The detailed usage rules are as follows:
Assume that the service server HelloServer is deployed on two sets, Test.s.1 and Test.n.1. Then the client specifies the set mode to be called as follows:
TC_Config conf("config.conf");
CommunicatorPtr c = new Communicator();
//Initialize the communicator with a configuration file
c-> setProperty(conf);
//Generate a client's service proxy
HelloPrx pPrx_Tests1 = c->stringToProxy
HelloPrx pPrx_Testn1 = c->stringToProxy
//Initiate a remote synchronization call string s = "hello word"; string r;
int ret = pPrx_Tests1->testHello(s, r);
int ret = pPrx_Testn1->testHello(s, r);
note:
> * The priority of the specified set call is higher than the priority of the client and the server itself to enable the set. For example, both the client and the server have "Test.s.1" enabled, and if the client specifies "Test.n.1" when creating the server proxy instance, the actual request is sent to "Test.n.1"("Test.n.1" has a deployment service).
> * Just create a proxy instance once
### 2.3.6. Hash call
Since multiple servers can be deployed, client requests are randomly distributed to the server, but in some cases, it is desirable that certain requests are always sent to a particular server. In this case, Tars provides a simple way to achieve:
If there is a request for querying data according to the QQ number, as follows:
QQInfo qi = pPrx->query(uin);
Normally, for the same call to uin, the server address of each response is not necessarily the same. However, With the following call, it can be guaranteed that each request for uin is the same server response.
QQInfo qi = pPrx->tars_hash(uin)->query(uin);
note:
> * This method is not strict. If a server goes down, these requests will be migrated to other servers. When it is normal, the request will be migrated back.
> * The argument to tars_hash() must be int. For string, the Tars base library (under the util directory) also provides the method: tars::hash<string>()("abc"). See util/tc_hash_fun.h for details.
# 3. Asynchronous nesting
Asynchronous nesting represents the following:
> * A calls B asynchronously, B calls C asynchronously after receiving the request, and B returns the result to A when C returns.
Normally, B needs to return a response to A after B receives the request and finishes processing in the interface.
Therefore, it is not can be implemented if B initiates an asynchronous request to C in the interface.
Therefore, it is necessary to implemente the asynchronous calls across services by using the following methods.
You can see the examples/QuickStartDemo/ProxyServer example for details.
The following still uses the helloworld program to explain. Firstly, the client initiates a request to the proxy, and the proxy initiates
testHello to the HelloServer asynchronously after receiving the request. Then the proxy returns the result by the HelloServer to the client
after the request returns.
The key logic in this process is on the ProxyServer. The following code is the logical processing in B:
//Asynchronous callback object in ProxyServer class HelloCallback : public HelloPrxCallback {
public:
HelloCallback(TarsCurrentPtr ¤t)
: _current(current)
{}
virtual void callback_testHello(tars::Int32 ret, const std::string& sOut)
{
Proxy::async_response_testProxy(_current, ret, sOut);
}
virtual void callback_testHello_exception(tars::Int32 ret)
{
TLOGERROR("HelloCallback callback_testHello_exception ret:" << ret << endl);
Proxy::async_response_testProxy(_current, ret, "");
}
TarsCurrentPtr _current;
};
//The interface defined in ProxyServer tars::Int32 ProxyImp::testProxy(const std::string& sIn, std::string &sOut, tars::TarsCurrentPtr current) {
try
{
current->setResponse(false);
TestApp::HelloPrxCallbackPtr cb = new HelloCallback(current);
_prx->tars_set_timeout(3000)->async_testHello(cb,sIn);
}
catch(std::exception &ex)
{
current->setResponse(true);
TLOGERROR("ProxyImp::testProxy ex:" << ex.what() << endl);
}
return 0;
}
Description:
> * The callback object HelloCallback saves the context current;
> * The callback object return the request to the client through Proxy::async_response_testProxy after receiving the request returned by HelloServer;
> * It needs to set the automatic reply current->setResponse(false) in the ProxyServer interface testProxy implementation;
> * It is actually meaningless to return a value or parameters of testProxy. No matter what is returned;
> * If other parameters are needed in the callback object, they can be passed in when constructing again;
> * The callback object must be new and placed in the smart pointer, for example: HelloPrxCallbackPtr cb = new HelloCallback(current); For its life cycle, the business does not need to managed, because the framework layer is automatically managed;
# 4. Dyeing
## 4.1. Funcational Overview
The main function of the dyeing function is to dye the message of a specific user number in an interface of a certain service, and conveniently view the log of all subsequent related call message flows caused by the user in real time.
After the dye log is opened, the dyed log can be viewd on the server where tarslog is located. For the specific path, please refer to:
The scrolling log which is written by the LOG macro in the program is all printed to the tarslog, its log file like: tars_dyeing.dyeing_roll_yyyymmdd.log. There is one file every day, such as:
/usr/local/app/tars/remote_app_log/tars_dyeing/dyeing/tars_dyeing.dyeing_roll_20161227.log
The daily log which is written by the DLOG, FDLOG and FFDLOG in the program is all printed to the tarslog, its log file like: tars_dyeing.dyeing_day_yyyymmdd.log. There is one file every day, such as:
/usr/local/app/tars/remote_app_log/tars_dyeing/dyeing/tars_dyeing.dyeing_day_20161227.log
## 4.2. Rule description
The dye log has two methods: active open and passive open.
The active open means that the dyed log switch in the framework is opened on the requesting client.
The specific steps are as follows:
> * An anchor point is buried in the appropriate place of the client program, and the anchor point is used to decide whether to open the dye log switch according to a certain condition.--tars.TarsDyeingSwitch. The range of staining starts from the opening of log util the switch is destructed.
> * The interface names enableDyeing is the method of openning dyed log. Subsequently, all logs should be requested(including this service, as well as logs for all services called later) will be printed an additional copy to the tarslog.
> * The called service opens the dyed log according to the request flag and prints the log to tarslog. The flag is passed to the next service automatically if the called also calls other service. The dyed log is closed automatically when the request is completed.
The passive open means that, under the pre-set dyeing condition of the requested server, the server opens the dyeing log switch of the service according to the transmitted key value.
The specific steps are as follows:
> * The dyed ingress interface needs to be specified as the routekey for the user keyword when the Tars interface is defined.
> * The method of dyeing: You can set the user number, remote object and interface to be dyed to the target service through the management command interface(optional interface parameters).
> * After the service receives the matching request(matching the user number, remote object and interface), the request packet is dyed.
> * For the already dyed request, the related system log and the daily log will be printed normally, and also print to the local and remote dye log directory centralizedly. The directory location is in the /tars_dyeing directory.
> * if other services are continuously called during the service processing, the request will passes the dyeing information through the status field. The called service will print the dyed log in the above manner and continue the dyeing status.
## 4.3. The use case of active open
The usage of active open:
using namespace std; using namespace TestApp; using namespace tars;
int main(int argc,char ** argv) { try {
CommunicatorPtr comm =new Communicator();
comm->setProperty("locator", "tars.tarsregistry.QueryObj@tcp -h 10.120.129.226 -p 17890 -t 10000");
TarsRollLogger::getInstance()->setLogInfo("TestApp", "HelloServer", "./log", 100000, 10, comm, "tars.tarslog.LogObj");
TarsRollLogger::getInstance()->sync(false);
TarsTimeLogger::getInstance()->setLogInfo(comm, "tars.tarslog.LogObj", "TestApp", "HelloServer", "./log");
{
//This log will only be printed to the local log before the dye log is opened.
TLOGDEBUG (__FILE__ << "|" << __LINE__ <<"Test Before Dyeing" <<endl);
DLOG <<__FILE__ << "|" << __LINE__ <<"D/Test Before Dyeing"<<endl;
FDLOG("T_D")<<__FILE__ << "|" << __LINE__ <<"F/Test Before Dyeing"<<endl;
}
try
{
{
//Declare a class TarsDyeingSwitch, and call enableDyeing to open the dye log.
TarsDyeingSwitch dye;
dye.enableDyeing();
//You can see the log in the local and dye logs after the dye log is opened.
{
TLOGDEBUG (__FILE__ << "|" << __LINE__ <<"Test Before Dyeing before call other function" <<endl);
DLOG <<__FILE__ << "|" << __LINE__ <<"D/Test Before Dyeing before call other function"<<endl;
FDLOG("T_D")<<__FILE__ << "|" << __LINE__ <<"F/Test Before Dyeing before call other function"<<endl;
}
string sReq("hello");
std::string sServant="TestApp.HelloServer.HelloObj";
TestApp::HelloPrx prx = comm->stringToProxy<TestApp::HelloPrx>(sServant);
tars::Int32 iRet = prx->test();
string sRsp;
prx->testHello(sReq,sRsp);
TLOGDEBUG (__FILE__ << "|" << __LINE__ <<"Test Before Dyeing after call other function" <<endl);
DLOG <<__FILE__ << "|" << __LINE__ <<"D/Test Before Dyeing after call other function"<<endl;
FDLOG("T_D")<<__FILE__ << "|" << __LINE__ <<"F/Test Before Dyeing after call other function"<<endl;
}
{
//The dye log object has beed destructed, the dye function is invalid, and you can't see the dye log in the future.
TLOGDEBUG (__FILE__ << "|" << __LINE__ <<"~Dyeing"<<endl);
DLOG <<__FILE__ << "|" << __LINE__ <<"D/~Dyeing"<<endl;
FDLOG("T_D")<<__FILE__ << "|" << __LINE__ <<"F/~Dyeing"<<endl;
}
}
catch(exception &ex)
{
cerr << "ex:" << ex.what() << endl;
}
catch(...)
{
cerr << "unknown exception." << endl;
}
}
catch(exception& e)
{
cerr << "exception:" << e.what() << endl;
}
catch (...)
{
cerr << "unknown exception." << endl;
}
sleep(10); //Waiting for thread written log asynchronously to synchronize log data to logserver
return 0;
}
in tars_dyeing.dyeing_roll_20161227.log
//This log is rolling log printed by the server using TLOGDEBUG. 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|7670|DEBUG|main.cpp|37Test Before Dyeing before call other function //This log is rolling log printed by the server using TLOGDEBUG. 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|7670|DEBUG|main.cpp|59Test Before Dyeing after call other function
in tars_dyeing.dyeing_day_20161227.log
//Rolling log using DLOG 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|main.cpp|38D/Test Before Dyeing before call other function 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|main.cpp|60D/Test Before Dyeing after call other function //Rolling log using FLOG 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|main.cpp|39F/Test Before Dyeing before call other function 10.120.129.226|TestApp.HelloServer|2016-12-27 11:30:47|main.cpp|61F/Test Before Dyeing after call other function
## 4.4. The Usage of passive open
Interface definition to be dyed
interface HelloRouteObj {
int testHello(routekey string sInput, out string sOutput);
};
Can be dyed by the frame command, the command format of the dye is:
tars.setdyeing dyeingKey dyeingServant [dyeingInterface]
This three parameters are the user number(the value corresponding to routekey), the remote object name, and the interface name(optional)
Assume that the remote object of the above interface is TestApp.HelloServer.HelloObj testHello
You can issue commands through the management platform: tars.setdyeing 123456 TestApp.HelloServer.HelloObj testHello
When a request with sInput is 123456 is sent to the service, it is not only having the normal log output, but also having the local system log printed:
/usr/local/app/tars/app_log/tars_dyeing/dyeing_20161227.log
TestApp.HelloServer|2016-12-27 15:38:49|11454|DEBUG|HelloImp::testHello sReq:123456
local log one by one:
/usr/local/app/tars/app_log/tars_dyeing/dyeing_20161227.log
TestApp.HelloServer|2016-12-27 15:38:49|11454|DEBUG|HelloImp::testHello sReq:123456
The remote log will be printed to the machine where tarslog is located:
remote system log:
/usr/local/app/tars/remote_app_log/tars_dyeing/dyeing/tars_dyeing.dyeing_roll_20161227.log
remote log by day:
/usr/local/app/tars/remote_app_log/tars_dyeing/dyeing/tars_dyeing.dyeing_day_20161227.log
The first field in the log is the service name of the dye request processing process, and the related logs of other subsequent services are also printed to the same file,
and the logs of different services are distinguished by the first field.
# 5. tars protocol packet size
Currently, the tars protocol limits the size of data packets.
The communicator (client) has no limit on the size of the delivered packet, and there is a limit on the received packet. The default is 10000000 bytes (close to 10M).
The server has no restrictions on the delivered packets , and has a size limit on the received packets. The default is 100000000 bytes (close to 100M).
## 5.1. Modify the client receiving packet size
Modify the size of the packet by modifying the tars_set_protocol of ServantProxy.
ProxyProtocol prot; prot.responseFunc = ProxyProtocol::tarsResponseLen<100000000>; prot.requestFunc = ProxyProtocol::tarsRequest; ccserverPrx -> tars_set_protocol(prot);
100000000 represents the size of the limit, in bytes.
ccserverPrx is globally unique, just set it once.
In order to write codes conveniently, it is recommended to set it once in the initialization of the business thread .
First call stringToProxy and then set it.
prot.requestFunc = ProxyProtocol::tarsRequest //Must exist, the default is not this function.
If it is called in tup mode. Set len
prot.responseFunc = ProxyProtocol:: tupResponseLen<100000000>;
## 5.2. Modify the server to receive the packet size
Modify the packet size by setting the form of ServantProtocol.
addServantProtocol(ServerConfig::Application + "." + ServerConfig::ServerName + ".BObj",AppProtocol::parseLenLen<100000000>);
It is recommended to set it in the initialize of the server and set it after addServant.
# 6. Tars defined return code
//Define the return code given by the TARS service const int TARSSERVERSUCCESS = 0; //Server-side processing succeeded const int TARSSERVERDECODEERR = -1; //Server-side decoding exception const int TARSSERVERENCODEERR = -2; //Server-side encoding exception const int TARSSERVERNOFUNCERR = -3; //There is no such function on the server side const int TARSSERVERNOSERVANTERR = -4; //The server does not have the Servant object const int TARSSERVERRESETGRID = -5; // server grayscale state is inconsistent const int TARSSERVERQUEUETIMEOUT = -6; //server queue exceeds limit const int TARSASYNCCALLTIMEOUT = -7; // Asynchronous call timeout const int TARSINVOKETIMEOUT = -7; //call timeout const int TARSPROXYCONNECTERR = -8; //proxy link exception const int TARSSERVEROVERLOAD = -9; //Server overload, exceeding queue length const int TARSADAPTERNULL = -10; //The client routing is empty, the service does not exist or all services are down. const int TARSINVOKEBYINVALIDESET = -11; //The client calls the set rule illegally const int TARSCLIENTDECODEERR = -12; //Client decoding exception const int TARSSERVERUNKNOWNERR = -99; //The server is in an abnormal position
# 7. Business Configuration
The Tars service framework provides the ability to pull the configuration of a service from tarsconfig to a local directory.
The method of use is very simple. In the initialize of the Server, call addConfig to pull the configuration file.
Take HelloServer as an example:
HelloServer::initialize() {
//Increase the object
addServant<HelloImp>(ServerConfig::Application+"."+ ServerConfig::ServerName + ".HelloObj");
//pull the configuration file
addConfig("HelloServer.conf");
}
Description:
> * HelloServer.conf configuration file can be configured on the web management platform;
> * After HelloServer.conf is pulled to the local, the absolute path of the configuration file can be indicated by ServerConfig::BasePath + "HelloServer.conf";
> * The configuration file management is on the web management platform, and the web management platform can actively push the configuration file to the server;
> * The configuration center supports ip level configuration, that is, a service is deployed on multiple services, only partially different (related to IP). In this case, the configuration center can support the merging of configuration files and support viewing on the web management platform as well as modification;
Note:
> * For services that are not released to the management platform, you need to specify the address of Config in the service configuration file, otherwise you cannot use remote configuration.
# 8. Log
Tars provides a number of macro for logging the system's rolling logs and daily logs. They are thread-safe and can be used at will.
## 8.1. TLOGXXX tutorial
TLOGXXX is used to record rolling logs, mainly used for debugging services. XXX includes four levels of INFO/DEBUG/WARN/ERROR, the meanings are as follows.
> * INFO: Information level, the internal log of the framework is printed at this level, unless it's an error.
> * DEBUG: Debug level, lowest level.
> * WARN: Warning level.
> * ERROR: Error level.
Instructtions for use:
TLOGINFO("test" << endl); TLOGDEBUG("test" << endl); TLOGWARN("test" << endl); TLOGERROR("test" << endl);
Directions:
> * The current level of the server log can be set in the web management system.
> * The logs of the Tars framework are printed by INFO. After setting it to INFO, you can see the frame log of Tars.
> * TLOGXXX scrolls by size, you can modify the scroll size and number in the template configuration file of the service, usually do not need to modify.
> * TLOGXXX indicates a circular log that is not sent to the remote tarslog service.
> * The file name of TLOGXXX is related to the service name, usually like app.server.log.
> * TLOGXXX has only one instance, so you can use it anywhere, but if it is used before the framework finishes LOG initialization, it will be output to cout.
> * In the place where TLOGXXX is used, you need to add a line of code: #include "servant/TarsLogger.h"
TLOGXXX is a macro, which is defined as follows:
The return type of TarsRollLogger::getInstance()->logger() is TC_RollLogger*,so you can set the LOG through it. For example:
Set LOG to info level:
TarsRollLogger::getInstance()->logger()->setLogLevel(TC_RollLogger::INFO_LOG);
The LOG log is asynchronous by default, but can also be set to sync:
TarsRollLogger::getInstance()->sync(true);
You can also use LOG in any place where you use ostream. For example:
ostream &print(ostream &os);
It can also be used like this:
print(LOG->debug());
## 8.2. DLOG/FDLOG
Daily log, mainly used to record important business information:
> * The default daily log of Tars is DLOG, and FDLOG can specify the file name of daily log.
> * DLOG/FDLOG logs are automatically uploaded to tarslog and can be set to not be uploaded to tarslog.
> * DLOG/FDLOG can modify the scrolling time, such as by minute, hour, etc.
> * DLOG/FDLOG is asynchronous by default. It can be set to sync if necessary, but it must be asynchronous for remote upload to tarslog and cannot be set to sync.
> * DLOG/FDLOG can be set to upload only to the remote and not recorded locally;
> * In the place where TLOGXXX is used, you need to add a line of code: #include "servant/TarsLogger.h"
## 8.3. Code example
CommunicatorPtr c = new Communicator();
string logObj = "tars.tarslog.LogObj@tcp -h 127.0.0.1 -p 20500";
//Initialize local scrolling logs TarsRollLogger::getInstance()->setLogInfo("Test", "TestServer", "./");
//Initialize time log TarsTimeLogger::getInstance()->setLogInfo(c, logObj , "Test", "TestServer", "./");
//If it is a Tars service, the above part of the code is not needed, the framework has been completed automatically.
//The default daily log does't need to be uploaded to the server TarsTimeLogger::getInstance()->enableRemote("", false);
//The default daily log is scrolled by minute TarsTimeLogger::getInstance()->initFormat("", "%Y%m%d%H%M");
//Set abc2 not to be uploaded to the server TarsTimeLogger::getInstance()->enableRemote("abc2", false);
//set abc2 scrolls by hour TarsTimeLogger::getInstance()->initFormat("abc2", "%Y%m%d%H");
//Set abc3 to not be recorded locally TarsTimeLogger::getInstance()->enableLocal("abc3", false);
int i = 100000; while(i--) {
//as same as last one
TLOGDEBUG(i << endl);
//error level
TLOGERROR(i << endl);
DLOG << i << endl;
FDLOG("abc1") << i << endl;
FDLOG("abc2") << i << endl;
FDLOG("abc3") << i << endl;
if(i % 1000 == 0)
{
cout << i << endl;
}
usleep(10);
}
# 9. Service management
The Tars server framework supports dynamic receiving commands to handle related business logic, such as dynamic update configuration.
Two macros are defined in the framework:
> * TARS_ADD_ADMIN_CMD_PREFIX: Add pre-command processing method, executed before all normal methods. The execution order between multiple pre-command methods is undefined.
> * TARS_ADD_ADMIN_CMD_NORMAL: Add the Normal command processing method, which is executed at the end of all pre-command methods. The execution order between multiple normal methods is undefined.
By using these two macros, you can register the command processing interface. When a command is sent to the service through the web management platform, the registered interface is called.
There are two types of processing interfaces that are usually registered: global processing interface and object-based processing interface.
The following uses HelloServer as an example to describe how to use commands.
## 9.1. Global processing interface
The so-called global processing interface means that the processing interface is service-dependent, not related to any objects such as HelloImp.
Assume that HelloServer needs to add a function to set a FDLOG configuration file to not be uploaded to the remote tarslog. This processing is independent of the HelloImp object, so it is a global change. The processing steps are as follows:
Add a handler to the HelloServer to do this. Note that the function must be declared in this way:
bool HelloServer::procDLOG(const string& command, const string& params, string& result) {
TarsTimeLogger::getInstance()->enableLocal(params, false);
return false;
}
Register this function in HelloServer::initialize():
void HelloServer::initialize() {
addServant(...);
addConfig(…);
//Registration handler:
TARS_ADD_ADMIN_CMD_NORMAL("DISABLEDLOG", HelloServer::procDLOG);
}
Instruction:
> * Can send commands directly to the service on the web management platform. Such as "DISABLEDLOG", indicating that the default daily log is set to not be recorded locally. "DISABLEDLOG test", indicating that the daily log of the test is not be recorded locally.
> * Command handler must be declared in the same way.
> * Return type: indicates whether the command is passed down. That is, if multiple interface functions are registered on one command, if false is returned, the subsequent interface is not called after the current interface is executed.
> * First parameter: Indicates the name of registered command, in this example is "DISABLEDOG".
> * Second parameter: Indicates the parameters of the command, which are decomposed by spaces, in this example is "test".
> * Using TARS_ADD_ADMIN_CMD_NORMAL or TARS_ADD_ADMIN_CMD_PREFIX when registering depends on where you want the command to be executed.
> * The first parameter of the registration macro represents the command name (cannot contain spaces), and the second parameter represents the function executed on the command.
# 9.2. Object-based processing interface
The so-called object-based processing interface means that the command interface is for an object in the service.
For example, for the HelloImp object, if a command is sent to the service, the command interface of HelloImp in each thread will be executed once, and the process of executing these interfaces and the interface of executing HelloImp are mutually exclusive (ie, thread-safe)
Assuming that HelloImp has the member variable string _hello, you need to send a command to notify each HelloImp object to change _hello to a custom value. The steps are as follows:
Add handlers to HelloImp:
bool HelloImp::procHello(const string& command, const string& params, string& result) {
_hello = params;
return false;
}
Register the function in the initialization of HelloImp:
void HelloImp::initialize() {
//Registration handler:
TARS_ADD_ADMIN_CMD_NORMAL("SETHELLO", HelloImp::procHello);
}
After completing the above operation, send the "SETHELLO test" command on the web page, and _hello is assigned the value "test".
# 9.3. Send management command
How to send management commands: Publish a TARS service to the platform through the web management platform, and then send commands through the management platform.
TARS currently has eight commands built in:
> * tars.help //View all administrative commands
> * tars.loadconfig //From the configuration center, pull the configuration file down. For example: tars.loadconfig filename
> * tars.setloglevel //Set the level of the rolling log. For example: tars.setloglevel [NONE, ERROR, WARN, DEBUG]
> * tars.viewstatus //View service status
> * tars.connection //View current link status
> * tars.loadproperty //Reload the properties in the configuration file
> * tars.setdyeing //Set staining information. For example: tars.setdyeing key servant [interface]
# 10. Statistical reporting
Reporting statistics information is the logic of reporting the time-consuming information and other information to tarsstat inside the Tars framework. No user development is required. After the relevant information is correctly set during program initialization, it can be automatically reported inside the framework (including the client and the server).
After the client call the reporting interface, it is temporarily stored in memory. When it reaches a certain time point, it is reported to the tarsstat service (the default is once reporting 1 minute). We call the time gap between the two reporting time points as a statistical interval, and perform the operations such as accumulating and comparing the same key in a statistical interval.
The sample code is as follows:
//Initialize the communicator CommunicatorPtr pcomm = new Communicator(); //Initialize the tarsregistry service address pcomm->setProperty("locator", "tars.tarsregistry.QueryObj@tcp -h xxx.xxx.xxx.xx -p xxxx" //Initialize the stat service pcomm->setProperty("stat", "tars.tarsstat.StatObj"); //Set the reporting interval pcomm->setProperty("report-interval", "1000"); //Set the report main call name pcomm->setProperty("modulename", "Test.TestServer_Client");
Description:
> * If the main service is deployed on the web management system, you do not need to define Communicator set the configurations of tarsregistry, tarsstat, etc., the service will be automatically reported.
> * If the main service or program is not deployed on the web management system, you need to define the Communicator, set the tarsregistry, tarsstat, etc., so that you can view the service monitoring of the called service on the web management system.
> * The reported data is reported regularly and can be set in the configuration of the communicator.
# 11. Anormaly reporting
For better monitoring, the TARS framework supports reporting abnormal situdation directly to tarsnotify in the program and can be viewed on the WEB management page.
The framework provides three macros to report different kinds of exceptions:
// Report ordinary information TARS_NOTIFY_NORMAL(info) // Report warning message TARS_NOTIFY_WARN(info) // Report error message TARS_NOTIFY_ERROR(info)
Info is a string, which can directly report the string to tarsnotify. The reported string can be seen on the page, subsequently, we can alarm according to the reported information.
# 12. Attribute Statistics
In order to facilitate business statistics, the TARS framework also supports the display of information on the web management platform.
The types of statistics currently supported include the following:
> * Sum(sum)
> * Average(avg)
> * Distribution(distr)
> * Maximum(max)
> * Minimum(min)
> * Count(count)
The sample code is as follows:
// Initialize the communicator Communicator _comm; // Initialize the property service address _comm.setProperty("property", "tars.tarsproperty.PropertyObj@ tcp -h xxx.xxx.xxx.xxx -p xxxx");
// Initialize the distribution data range
vector
// Create test1 attribute, this attribute uses all the statistics above, and pay attention to the initialization of distrv PropertyReportPtr srp = _comm.getStatReport()->createPropertyReport("test1", PropertyReport::sum(), PropertyReport::avg(), PropertyReport::count(), PropertyReport::max(), PropertyReport::min(), PropertyReport::distr(v));
// Report data, property only supports int type data reporting int iValue = 0; for ( int i = 0; i < 10000000; i++ ) {
sleep(1);
srp->report(rand() % 100 );
}
Description:
> * Data is reported regularly, and can be set in the configuration of the communicator, currently once per minute;
> * Create a PropertyReportPtr function: The parameter createPropertyReport can be any collection of statistical methods, the example uses six statistical methods, usually only need to use one or two;
> * Note that when you call createPropertyReport, you must create and save the created object after the service is enabled, and then just take the object to report, do not create it each time you use.
# 13. Tars call chain
Tars supports reporting rpc call path information to zipkin to help locate network call problems.
About zipkin instructions can be found at [https://zipkin.io/] (https://zipkin.io/).
**Compile and Running dependencies:**
The Tars call chain uses opentracking and zipkin-opentracking libraries. Since the zipkin-opentracking library depend on the libcurl library, you need to install libcurl. In addition, the compiler needs to support c++11.
Download link:
[opentracing-cpp](https://github.com/opentracing/opentracing-cpp)
[zipkin-cpp-opentracing](https://github.com/rnburn/zipkin-cpp-opentracing)
**Instructions for use:**
1) Compile and install
The feature of tars call chain is controlled by the compile option _USE_OPENTRACKING, which is off by default.
Open mode: Execute export _USE_OPENTRACKING=1 in the shell before compile tars.
After the framework is compiled, modify the \'servant/makefile/makefile.tars\' file and add a line to the front before install tars:
`_USE_OPENTRACKING=1`
to indicates that the framework has opened the call chain switch. In addition, opentraking, curl, zipkin_opentracing install path need to be manually modified to the correct path (The default path is /usr/local/lib).
2) Configuration
When using the tars call chain function, you need to specify the address of the zipkin in the program configuration file. Sample configuration is as follows:
The collector_host and collector_port are mandatory (if the configuration is not configured, the call chain function will not be available), sample_rate is optional (the default value is 1.0, the interval is 0.0~1.0, which is used to specify the rate of call chain information that reported to the zipkin collector)