Skip to the content.

使用指南

框架使用指南

服务端开发

下面通过一个完整的 hello world 例子介绍如何实现自己的服务。

依赖配置

在构建项目pom.xml中添加依赖 jar 包

<dependency>
    <groupId>com.tencent.tars</groupId>
    <artifactId>tars-server</artifactId>
    <version>1.7.2</version>
    <type>jar</type>
</dependency>
<plugin>
    <groupId>com.tencent.tars</groupId>
    <artifactId>tars-maven-plugin</artifactId>
    <version>1.6.1</version>
    <configuration>
	<tars2JavaConfig>
	    <tarsFiles>
		<tarsFile>${basedir}/src/main/resources/hello.tars</tarsFile>
	    </tarsFiles>
	    <tarsFileCharset>UTF-8</tarsFileCharset>
	    <servant>true</servant>
	    <srcPath>${basedir}/src/main/java</srcPath>
	    <charset>UTF-8</charset>
	    <packagePrefixName>com.qq.tars.quickstart.server.</packagePrefixName>
	</tars2JavaConfig>
    </configuration>
</plugin>

接口文件定义

接口文件定义是通过 Tars 接口描述语言来定义,在 src/main/resources 目录下建立 hello.tars 文件,内容如下

module TestApp
{
	interface Hello
	{
	    string hello(int no, string name);
	};
};

接口文件编译

提供插件编译生成 java 代码,在 tars-maven-plugin 添加生成 java 文件配置

<plugin>
	<groupId>com.tencent.tars</groupId>
	<artifactId>tars-maven-plugin</artifactId>
	<version>1.7.2</version>
	<configuration>
		<tars2JavaConfig>
			<!-- tars文件位置 -->
			<tarsFiles>
				<tarsFile>${basedir}/src/main/resources/hello.tars</tarsFile>
			</tarsFiles>
			<!-- 源文件编码 -->
			<tarsFileCharset>UTF-8</tarsFileCharset>
			<!-- 生成服务端代码 -->
			<servant>true</servant>
			<!-- 生成源代码编码 -->
			<charset>UTF-8</charset>
			<!-- 生成的源代码目录 -->
			<srcPath>${basedir}/src/main/java</srcPath>
			<!-- 生成源代码包前缀 -->
			<packagePrefixName>com.qq.tars.quickstart.server.</packagePrefixName>
		</tars2JavaConfig>
	</configuration>
</plugin>

在工程根目录下执行 mvn tars:tars2java

@Servant
public interface HelloServant {
    public String hello(int no, String name);
}

服务接口实现

新创建一个 HelloServantImpl.java 文件,实现 HelloServant.java 接口

public class HelloServantImpl implements HelloServant {
@Override
public String hello(int no, String name) {
    return String.format("hello no=%s, name=%s, time=%s", no, name, System.currentTimeMillis());
}

服务暴露配置

在 WEB-INF 下创建一个 servants.xml 的配置文件,服务编写后需要进程启动时加载配置暴露服务,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<servants>
	<servant name="HelloObj">
		<home-api>com.qq.tars.quickstart.server.testapp.HelloServant</home-api>
		<home-class>com.qq.tars.quickstart.server.testapp.impl.HelloServantImpl</home-class>
	</servant>
</servants>

说明:除了此方法之外,还可以采用 spring 模式来配置服务,详情见 tars_java_spring.md。

服务配置 ServerConfig

服务框架中有全局的结构 ServerConfig,它记录了服务的基本信息,在服务框架初始化时会自动从服务配置文件中初始化这些参数。参数说明:

服务的配置文件格式如下:

<tars>
  <application>
    <server>
       #本地node的ip:port
       node=tars.tarsnode.ServerObj@tcp -h x.x.x.x -p 19386 -t 60000
       #应用名称
       app=TestApp
       #服务名称
       server=HelloServer
       #本机ip
       localip=x.x.x.x
       #管理端口
       local=tcp -h 127.0.0.1 -p 20001 -t 3000
       #服务可执行文件,配置文件等
       basepath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/bin/
       #服务数据目录
       datapath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/data/
       #日志路径
       logpath=/usr/local/app/tars/app_log/
       #配置中心的地址
       config=tars.tarsconfig.ConfigObj
       #通知上报的地址[可选]
       notify=tars.tarsnotify.NotifyObj
       #远程日志的地址[可选]
       log=tars.tarslog.LogObj
       #服务停止的超时时间
       deactivating-timeout=2000
       #日志等级
       logLevel=DEBUG
       #防空闲连接超时设置
	   sessionTimeOut=120000
	   #防空闲连接超时检查周期
       sessionCheckInterval=60000
    </server>
  </application>
</tars>

Adapter

Adapter 表示了绑定端口。服务新增一个绑定端口,则新建立一个 Adapter,并设置相关的参数和处理对象则可以非常方便的完成对这个端口上的处理,通常用这个功能来完成在其他协议的支撑。

对于 TARS 服务而言,在服务配置文件中增加 adapter 项,即可以完成增加一个 Servant 处理对象。

Adapter 配置如下:

<tars>
  <application>
    <server>
       #配置绑定端口
       <TestApp.HelloServer.HelloObjAdapter>
            #允许的IP地址
            allow
            #监听IP地址
            endpoint=tcp -h x.x.x.x -p 20001 -t 60000
            #处理组
            handlegroup=TestApp.HelloServer.HelloObjAdapter
            #最大连接数
            maxconns=200000
            #协议
            protocol=tars
            #队列大小
            queuecap=10000
            #队列超时时间毫秒
            queuetimeout=60000
            #处理对象
            servant=TestApp.HelloServer.HelloObj
            #当前线程个数
            threads=5
       </TestApp.HelloServer.HelloObjAdapter>
    </server>
  </application>
</tars>

添加 servant 项需要在 servants.xml 中完成配置

服务启动

服务启动时需要在启动命令中添加配置文件**-Dconfig=config.conf**, 注意:config.conf 为配置文件,服务端的配置文件和客户端的配置文件必须合并到这一个文件中。完整的如下:

<tars>
  <application>
    enableset=n
    setdivision=NULL
    <server>
       #本地node的ip:port
       node=tars.tarsnode.ServerObj@tcp -h x.x.x.x -p 19386 -t 60000
       #应用名称
       app=TestApp
       #服务名称
       server=HelloServer
       #本机ip
       localip=x.x.x.x
       #管理端口
       local=tcp -h 127.0.0.1 -p 20001 -t 3000
       #服务可执行文件,配置文件等
       basepath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/bin/
       #服务数据目录
       datapath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/data/
       #日志路径
       logpath=/usr/local/app/tars/app_log/
       #配置中心的地址
       config=tars.tarsconfig.ConfigObj
       #通知上报的地址[可选]
       notify=tars.tarsnotify.NotifyObj
       #远程日志的地址[可选]
       log=tars.tarslog.LogObj
       #服务停止的超时时间
       deactivating-timeout=2000
       #日志等级
       logLevel=DEBUG
        #配置绑定端口
       <TestApp.HelloServer.HelloObjAdapter>
            #允许的IP地址
            allow
            #监听IP地址
            endpoint=tcp -h x.x.x.x -p 20001 -t 60000
            #处理组
            handlegroup=TestApp.HelloServer.HelloObjAdapter
            #最大连接数
            maxconns=200000
            #协议
            protocol=tars
            #队列大小
            queuecap=10000
            #队列超时时间毫秒
            queuetimeout=60000
            #处理对象
            servant=TestApp.HelloServer.HelloObj
            #当前线程个数
            threads=5
       </TestApp.HelloServer.HelloObjAdapter>
    </server>
    <client>
       #主控的地址
       locator=tars.tarsregistry.QueryObj@tcp -h x.x.x.x -p 17890
       #同步超时时间
       sync-invoke-timeout=3000
       #异步超时时间
       async-invoke-timeout=5000
       #刷新ip列表的时间间隔
       refresh-endpoint-interval=60000
       #上报数据的时间间隔
       report-interval=60000
       #采样率
       sample-rate=100000
       #最大采样数
       max-sample-count=50
       #模版名称
       modulename=TestApp.HelloServer
    </client>
  </application>
</tars>

异步嵌套

异步嵌套代表如下情况:

A 异步调用 B,B 接收到请求后再异步调用 C,等 C 返回后,B 再将结果返回 A。

通常情况下面,B 接收到请求后,在接口处理完毕以后就需要返回应答给 A,因此如果 B 在接口中又发起异步请求到 C,则无法实现。

因此需要在实现接口方法中,声明启动异步来实现跨服务的异步调用,​//声明启动异步上下文 AsyncContext context = AsyncContext.startAsync(); //接口实现 …

//在异步处理后回包
context.writeResult(...);

客户端开发

客户端可以不用写任何协议通信代码即可完成远程调用。

通信器

完成服务端以后,客户端对服务端完成收发包的操作是通过通信器Communicator来实现的。

通信器的初始化如下:

CommunicatorConfig cfg = CommunicatorConfig.load("config.conf");
//构建通信器
Communicator communicator = CommunicatorFactory.getInstance().getCommunicator(cfg);

说明:

通信器属性说明:

通信器配置文件格式如下:

<tars>
  <application>
	#set调用
	enableset                      = N
	setdivision                    = NULL
    #proxy需要的配置
    <client>
        #地址
        locator                     = tars.tarsregistry.QueryObj@tcp -h 127.0.0.1 -p 17890
        #同步最大超时时间(毫秒)
        connect-timeout             = 3000
        #网络连接数
        connections                 = 4
        #网络连接超时时间(毫秒)
        sync-invoke-timeout         = 3000
        #异步最大超时时间(毫秒)
        async-invoke-timeout        = 5000
        #刷新端口时间间隔(毫秒)
        refresh-endpoint-interval   = 60000
        #模块间调用
        stat                        = tars.tarsstat.StatObj
        #属性上报地址
        property                    = tars.tarsproperty.PropertyObj
        #report time interval
        report-interval             = 60000
        #模块名称
        modulename                  = TestApp.HelloServer
    </client>
  </application>
</tars>

使用说明:

超时控制

超时控制是对客户端 proxy(代理)而言。上节中通信器的配置文件中有记录:

#同步最大超时时间(毫秒)
sync-invoke-timeout          = 3000
#异步最大超时时间(毫秒)
async-invoke-timeout         = 5000

上面的超时时间对通信器生成的所有 proxy 都有效,如果需要单独设置超时时间,设置如下:

针对 proxy 设置(ServantProxyConfig 与 CommunicatorConfig 类似)​

//设置该代理单独初始化配置
public <T> T stringToProxy(Class<T> clazz, ServantProxyConfig servantProxyConfig)

调用

本节会详细阐述远程调用的方式。

首先简述 tars 客户端的寻址方式,其次会介绍客户端的调用方式,包括但不限于单向调用、同步调用、异步调用、hash 调用等。

寻址方式简介

Tars 服务的寻址方式通常可以分为如下两种方式,即服务名在主控注册和不在主控注册,主控是指专用于注册服务节点信息的名字服务(路由服务)。 名字服务中的服务名添加则是通过操作管理平台实现的。

对于没有在主控注册的服务,可以归为直接寻址方式,即在服务的 obj 后面指定要访问的 ip 地址。客户端在调用的时候需要指定 HelloObj 对象的具体地址:

即:TestApp.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 9985

TestApp.HelloServer.HelloObj:对象名称

tcp:tcp 协议

-h:指定主机地址,这里是 127.0.0.1

-p:端口地址,这里是 9985

如果 HelloServer 在两台服务器上运行,则 HelloPrx 初始化方式如下:​

HelloPrx prx = c.stringToProxy("TestApp.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 9985:tcp -h 192.168.1.1 -p 9983");

即,HelloObj 的地址设置为两台服务器的地址。此时请求会分发到两台服务器上(分发方式可以指定,这里不做介绍),如果一台服务器 down,则自动将请求分到另外一台,并定时重试开始 down 的那一台服务器。

对于在主控中注册的服务,服务的寻址方式是基于服务名进行的,客户端在请求服务端的时候则不需要指定 HelloServer 的具体地址,但是需要在生成通信器或初始化通信器的时候指定 registry(主控中心)的地址。

HelloPrx prx = c.stringToProxy<HelloPrx>("TestApp.HelloServer.HelloObj");

单向调用

所谓单向调用,表示客户端只管发送数据,而不接收服务端的响应,也不管服务端是否接收到请求。​

HelloPrx prx = c.stringToProxy("TestApp.HelloServer.HelloObj");
//发起远程调用
prx.async_hello(null, 1000, "hello word");

同步调用

请看如下调用示例:​

HelloPrx prx = c.stringToProxy("TestApp.HelloServer.HelloObj");
//发起远程调用
prx.hello(1000, "hello word");

异步调用

请看如下调用示例:

HelloPrx prx = c.stringToProxy("TestApp.HelloServer.HelloObj");
//发起远程调用
prx.async_hello(new HelloPrxCallback() {

        @Override
        public void callback_expired() {
        }

        @Override
        public void callback_exception(Throwable ex) {
        }

        @Override
        public void callback_hello(String ret) {
            System.out.println(ret);
        }
    }, 1000, "hello word");

注意:

set 方式调用

目前框架已经支持业务按 set 方式进行部署,按 set 部署之后,各个业务之间的调用对开业务发来说是透明的。但是由于有些业务有特殊需求,需要在按 set 部署之后,客户端可以指定 set 名称来调用服务端,因此框架则按 set 部署的基础上增加了客户端可以指定 set 名称去调用业务服务的功能。

详细使用规则如下,

假设业务服务端 HelloServer 部署在两个 set 上,分别为 Test.s.1 和 Test.n.1。那么客户端指定 set 方式调用配置 ​enableset = Y setdivision = Test.s.1

业务配置

Tars 服务框架提供了从 tarsconfig 拉取服务的配置到本地目录的功能。

使用方法很简单,服务在启动通过在注册监听器里面加载到服务 conf 目录。

以 HelloServer 为例:

public class AppStartListener implements AppContextListener {
    @Override
    public void appContextStarted(AppContextEvent event) {
        ConfigHelper.getInstance().loadConfig("helloServer.conf");
    }

    @Override
    public void appServantStarted(AppServantEvent event) {
    }
}

在 servant.xml 中注册配置 ​

<listener>
	<listener-class>com.qq.tars.quickstart.server.AppStartListener</listener-class>
</listener>

说明:

注意:

服务日志

框架支持本地和远程日志,获取日志 Logger 对象如下 ​

private final static Logger FLOW_LOGGER = Logger.getLogger("flow", LogType.LOCAL);

说明:打远程日志前需要预先申请远程日志服务

Logback 日志系统

TarsJava 1.7 版本之后使用了 Logback 作为 Tars 日志系统,Logback 提供了十分灵活的配置项,可以为用户提供更加强大的日志功能。

日志系统结构

Logback 日志系统由三部分组成,分别是 Logger, Appender, Layout:

配置文件

Logback 会首先会在类路径下寻找logback-test.xmllogback.xml配置文件,若没有找到,则会使用默认的BasicConfigurator。配置文件的基本结构为<configuration>元素中包含多个<appender>和<logger>元素和最多一个<root>元素,官方文档中给出了配置文件的结构图如下:

Logback-config

一个典型的 logback.xml 配置文件格式如下:

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="Logname" value="demo" />
    <contextName>${Logname}</contextName>


    <appender>
        ···
    </appender>

    <logger>
        ···
    </logger>

    <root>
       ···
    </root>
</configuration>
  1. <configuration>

<configuration>标签提供了三个配置选项:

<configuration scan="true" scanPeriod="30 seconds" debug="false">
  ...
</configuration>
  1. <contextName>

用于设置 LoggerContext 的名称,默认的上下文名称为”default”,一旦设置之后,上下文名字不能再被更改。

<contextName>myAppName</contextName>
  1. <property>

使用该变量可以在配置文件中定义变量,也可以从外部属性文件或者外部资源中批量加载。之后通过${变量名}的形式可以调用该变量。

<property name="USER_HOME" value="/home/log" />
  1. <appender>

负责写日志任务的组件,常用的有以下几类:

  1. <logger>

可以设置某一个包或者具体的某一个类的日志打印级别,并添加 appender。

<logger name="chapters.configuration" level="DEBUG">
    <appender-ref ref="STDOUT" />
</logger>
  1. <root>

也是<logger>元素,是根 logger,仅有 level 一个属性用于设置打印级别,同时也可以使用<appender-ref>来添加 appender。

<root level="debug">
    <appender-ref ref="STDOUT" />
</root>

一个完整的 logback.xml 配置如下:

<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myApp.log</file>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="chapters.configuration">
    <appender-ref ref="FILE" />
  </logger>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
日志使用

通过调用 LoggerFactory.getLogger 方法即可获取相应的日志记录器,之后使用记录器来完成来完成日志的记录。

Logger logger = LoggerFactory.getLogger("name");
logger.info("Hello World!");

更多关于 Logback 的使用,请参考Logback 官方文档

服务管理

服务框架可以支持动态接收命令,来处理相关的业务逻辑,例如:动态更新配置等。

服务的管理命令的发送方式:通过管理平台,将服务发布到平台上,通过管理平台发送命令;​TARS 服务框架目前内置命令:

服务的自定义命令发送方式,通过管理平台自定义命令发送; 只需注册相关命令以及命令处理类,如下

CustemCommandHelper.getInstance().registerCustemHandler("cmdName",new CommandHandler() {
	@Override
	public void handle(String cmdName, String params) {

	}
});

异常上报

为了更好监控,框架支持在程序中将异常直接上报到 tarsnotify,并可以在管理平台页面上查看到。

框架提供异常上报工具,使用如下 ​

NotifyHelper.getInstance().notifyNormal(info);
NotifyHelper.getInstance().notifyWarn(info);
NotifyHelper.getInstance().notifyError(info);

说明:

属性统计

为了方便业务做统计,框架中也支持能够在管理平台看上报的信息展示。

目前支持的统计类型包括以下几种:

示例代码如下:​PropertyReportHelper.getInstance().createPropertyReporter(“queue_size”); PropertyReportHelper.getInstance().reportPropertyValue(“queue_size”, 100);

说明:

染色日志

为了方便在 debug 时实时查看某个用户在调用某服务某接口后引起的后续相关调用消息流的日志,框架中支持将该用户触发的所有日志单独打印一份到一个指定日志文件中。

染色日志有主动打开和被动打开两种方法:

示例代码如下:

DyeingSwitch.enableActiveDyeing("helloServer");   //主动打开开关接口,参数表示染色日志名称
...业务处理
loggerInnerImpl.info("hello world");   //此时出于染色开启状态,该条日志会额外打印一份到染色日志中
...业务处理
DyeingSwitch.closeActiveDyeing();    //主动关闭染色开关接口

说明:

在新版的 TarsJava 中使用了 Logback 作为日志系统,因此可以使用 MDC 来实现对日志的染色。MDC 是 Logback 提供的在多线程中记录日志的一种技术,它的内部持有一个 ThreadLocal 对象,其中存储了一个 Map,因此用户可以根据需要向其中添加键值对。通过实现 Filter,配合 MDC 可以实现对日志的染色:

public class MyFilter implements Filter {
    private static final String TRACE_ID = "traceId";
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ...
        boolean success = putMDC(...);
        ...
        try {
            chain.doFilter(request, response);
        } finally {
            if(success) {
                MDC.remove(TRACE_ID);
            }
        }
    }

    private boolean putMDC(...) {
        if (...){
            String traceId = ...
            MDC.put(TRACE_ID, traceId);
        	return true;
        }
        return false;
    }

使用方法如下:

首先在需要染色的 Tars 接口上定义染色 routeKey,这个值就是判断是否开启染色的变量,示例如下:

@Servant
public interface HelloServant {
     public String sayHello(@TarsRouteKey int no, String name);  //使用注释routeKey来表示开启染色的参数变量
}

在定义染色 routeKey 之后,可以通过管理命令”tars.setdyeing”来设置需要染色用户标识(就是 routeKey 注释对应的值),远程对象名称和接口函数名(可选)。

管理命令格式如下:

tars.setdyeing dyeingKey dyeingServant [dyeingInterface]  //三个参数分别对应上文所提值,接口名可选填

假设远程对象名称是 TestApp.HelloServer.HelloObj,请求接口名为 sayHello,需要染色的用户号码为 12345,对应的管理命令如下:

tars.setdyeing 12345 TestApp.HelloServer.HelloObj sayHello