作者:温昂展
要说清楚后面的一些TAF实现需要完整地考虑整个框架体系,因此本节先对TAF服务端的整个启动过程进行解析,同时探讨一些相关问题。
不得不说TAF框架的代码质量还是挺高的,业界良心!
光看程序逻辑就比较清晰,如下代码,上来直接找到startUp包下,Main程序,就是这么的简单直白,与优雅,这就是taf服务启动的入口:
package com.qq.cloud.taf.server.startup;
public class Main {
/**
* The only way to start TaServer.
*
* @param args
* @throws IOException
*/
public static void main(String[] args) {
new Server().startUp(args);
}
}
好,点击跳转进入startUp方法,打开新世界的大门。
TAF服务的整个启动流程可概括如下,下面分点叙述。
这个过程主要通过ConfigurationManager类来完成,通过读取xml配置文件,通过Config工具类读取/usr/local/app/taf/${app}.${service}/conf/${app}.${service}.config.conf,该文件包括了TAF服务端配置、各个obj服务配置以及客户端连接器的配置。
配置管理类的封装也比较直观,代码如下:
public class ServerConfig {
private String application;
private String serverName;
private Endpoint local;
....
//若干配置项
private LinkedHashMap<String, ServantAdapterConfig> servantAdapterConfMap;
private CommunicatorConfig communicatorConfig;
public ServerConfig load(Config conf) {
...
}
//省略
}
类图如下
TAF使用的日志框架是内部的 j4log, 原理上和log4j 日志框架很相像,定义了5个日志级别,3种日志类型,分别如下:
/**
* 日志级别
*/
public static enum Level {
DEBUG(0), INFO(1), WARN(2), ERROR(3), FATAL(4);
private int value;
private Level(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* 日志类型
*/
public static enum LogType {
LOCAL(0), REMOTE(1), ALL(2);
private int value;
private LogType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
从名字也可以看出来是什么意思,默认配置也是通过读取property配置文件来定义的,配置文件名为 j4log.property,这是写死在代码里面的,不可修改。
有个地方需要注意的是,j4log还有一个重置日志级别,提供给TAF管理平台使用。该日志级别:NONE,作用是关闭日志不打印,它没有将其作为一个单独的日志级别,而是使用状态标记NONE。
之前的文章中也提到了,TAF有一个远程的日志服务中心,因此这里的j4log即支持将日志输出到本地,也支持调用LogPrx将日志打到远程,且两者可以同时使用。同时出于可靠策略,TAF在远程写入多次失败的时候会自动转为输出本地。在实现上,当然是在本地内存中保存在一个缓冲队列,定期的批量再写到磁盘或远程啦。
另外,由于是服务端程序,TAF会将标准输出重定向为stdout.log文件,错误输出重定向到stderr.log文件,另外默认初始化出几个常用的日志类,如:tafserver.log , nami_core.log, 代码是这样写的:
public static void init() {
System.setOut(new PrintStream(new LoggingOutputStream(Logger.getLogger(STDOUT_log_NAME)), true));
System.setErr(new PrintStream(new LoggingOutputStream(Logger.getLogger(STDERR_LOG_NAME)), true));
}
前面已经提到,TAF另一个大重要的模块就是提供了一整套完备的运营服务,服务端除了拉取路由服务没有使用到之外,其他服务都有应用,代码实现上可以看到封装了很多的Helper,其下则是使用了各个服务的远程代理对象,各个服务如下,
另外还有一个发布服务Patch:存放和获取业务的发布包,给node进行业务Server的发布,这是由主控发起的服务,这里不涉及。
还是直接看图比较清晰:
可能这里你就会产生一个疑问, 为什么要分成这么多的服务而不是集中起来呢?
我的理解是: 首先,功能上各个运营服务是不一样的; 其次,怎么让这些服务不和TAF处理客户端请求和业务处理的代码解构呢?当然是单独开启一个周期执行的线程统一管理啦!此时,可想而知每个服务需要执行的周期不尽相同,另一方面,各个服务需要的机器资源也是有差异的,当然是分开部署好些。
以上大概就是开发运营一体化理念的吧。
当然,详细探究以上几个涉及监控上报的服务设计实现还是有必要的,后面再具体提及。
经过前面的一系列初始化工作,当前环境已经具备了配置信息、日志记录、运营服务了,此时就应该将业务侧的代码和资源加载进来了,这个实现TAF采用了容器的概念,容器初始化所需的配置信息在service.xml 文件读取。
如图,AppContainer即为装载类资源的容器,loadApp的具体执行执行过程总结如下:
1.构建一个类加载器AppClassLoader,加载BasePath/ROOT目录下的资源文件
2.读取BasePath/service.xml文件,从中加载listener,加载om管理命令servant
这个就是上一节提到的Reactor多线程+ NIO模型啦,下节将对如何处理客户端请求做详细的说明,其中,Session提供了一个对连接上下文的封装和抽象,提供socket读写API,后面启动的SessionManager即是对session的回收管理。
具体可看下图:
另外值得注意的是,TAF怎么实现多协议的支持呢?
在代码实现上,可以看到它分别启动了四个NIO服务器:
/**
* Start NIO server
*
* @throws Exception
*/
protected void startNIOServer() throws IOException {
// 1. Start main port server, and it is required.
startMainServer(host, port);
// 2. Start admin port and it is required.
startMainServer(adminHost, adminPort);
// 3. Start extended UDP server, and it is optional.
startUDPServer();
// 4. Start extended TCP server, and it is optional.
startTCPServer();
}
这就厉害了我的哥,其中第一个MainServer就是监听处理走JCE协议的客户端请求,JCE下面的传输层其实是TCP可靠的传输,第二个绑定在adminHost上的MainServer用于处理和Node节点交互的管理命令;
另外两个则用于支持HTTP协议的请求,包括TCP和UDP两种协议,在实现上只需要绑定对应的编解码器Codec就可以了,后文重点关注JCE协议。
还有一个值得注意的地方,目前去理解当前这样的实现,发现多个Obj都会对同一个监听端口复用,也就是说在服务接收到请求的时候是没有办法直接区分开是那个Obj的请求的,只有在后面的业务线程处理时才会分发到各个服务Obj上处理。这里也就是为什么之后看到TAF对于服务连接数的管理,目前是按整个服务的总量来做的, 具体实现下节再详细展开
管理器实现类为SessionManagerImpl,它可提供Session的注册和回收,同时还可以绑定监听器来实现一些功能逻辑,目前主要了解其完成的两个工作:
终于讲完啦,最后当然不能忘记服务退出时候的资源释放,这部分功能可以通过注册JVM 的 Runtime shutdownHook加以实现,主要是关闭seletor管理器和app容器,代码可以这样写:
private void registerServerHook() {
Runtime.getRuntime().addShutdownHook(run() -> {
try {
// 1. Stop SelectorManager
if (mainSelectorManager != null) {
mainSelectorManager.stop();
}
if (udpSelectorManager != null) {
udpSelectorManager.stop();
}
System.out.println("[SERVER] server stopped successfully.");
// 2. Stop Container
if (container != null) {
container.stop();
}
} catch (Exception ex) {
System.err.println("The exception occured at stopping server...");
}
});
}
感谢阅读,有错误之处还请不吝赐教。
这一节的理解要特别感谢一下terry浩哥,少走了不少弯路
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有