TAF 必修课(三):Server 启动全过程

作者:温昂展

一、程序入口

要说清楚后面的一些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) {
	...
	}
	//省略
}

类图如下

四、初始化Logger

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,其下则是使用了各个服务的远程代理对象,各个服务如下,

  • LogPrx: 这个前面已经提到了,可以远程收集各个业务打印的远程日志,用于分析
  • ConfigHelper:远程拉取业务服务的配置文件
  • NotifyHelper:收集服务状态变更、异常信息以及业务自定义的异常信息等重要信息,然后对这些信息进行监控告警,比如上报连接数据库失败的信息
  • PropertyReportHelper:用于上报业务一些特定的属性数据,比如内存大小、队列大小,然后进行监控
  • NodeHelper: 收集业务的心跳上报,服务版本号
  • ServerStatHelper:对业务Server之间调用的流量、平均耗时、异常率、超时率进行统计监控

另外还有一个发布服务Patch:存放和获取业务的发布包,给node进行业务Server的发布,这是由主控发起的服务,这里不涉及。

还是直接看图比较清晰:

可能这里你就会产生一个疑问, 为什么要分成这么多的服务而不是集中起来呢?

我的理解是: 首先,功能上各个运营服务是不一样的; 其次,怎么让这些服务不和TAF处理客户端请求和业务处理的代码解构呢?当然是单独开启一个周期执行的线程统一管理啦!此时,可想而知每个服务需要执行的周期不尽相同,另一方面,各个服务需要的机器资源也是有差异的,当然是分开部署好些。

以上大概就是开发运营一体化理念的吧。

当然,详细探究以上几个涉及监控上报的服务设计实现还是有必要的,后面再具体提及。

六、 容器初始化

经过前面的一系列初始化工作,当前环境已经具备了配置信息、日志记录、运营服务了,此时就应该将业务侧的代码和资源加载进来了,这个实现TAF采用了容器的概念,容器初始化所需的配置信息在service.xml 文件读取。

如图,AppContainer即为装载类资源的容器,loadApp的具体执行执行过程总结如下:

1.构建一个类加载器AppClassLoader,加载BasePath/ROOT目录下的资源文件

2.读取BasePath/service.xml文件,从中加载listener,加载om管理命令servant

  1. 加载业务services,并设置最大负载max-load,线程池大小threads,队列大小queuecap,触发监听器的appServiceStarted
  2. 取得支持的Jce协议(之后可以考虑扩展到其他协议)的所有Services信息放入AnalystManager, 存储接口方法名对应方法参数和返回值
  3. 触发监听器执行appContextStarted方法

七、初始化网络IO线程模型

这个就是上一节提到的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对于服务连接数的管理,目前是按整个服务的总量来做的, 具体实现下节再详细展开

八、启动Session管理器

管理器实现类为SessionManagerImpl,它可提供Session的注册和回收,同时还可以绑定监听器来实现一些功能逻辑,目前主要了解其完成的两个工作:

  1. 管理Session,定期回收过期SessionTimeout连接
  2. 注册监听器记录连接数,当连接总数大于整个服务的总量限制maxConns关闭该创建的连接

九、Shutdown Hook

终于讲完啦,最后当然不能忘记服务退出时候的资源释放,这部分功能可以通过注册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浩哥,少走了不少弯路

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏恰童鞋骚年

Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用

  应用层服务器(这里一般指Web服务器)处理网站应用的业务逻辑,应用的一个最显著的特点是:应用的无状态性。

13330
来自专栏SDNLAB

Neutron的软件实现

上一节交代了Neutron基本的组网原理,本节我们来看一看Neutron在软件层面的实现。 在架构设计上, Neutron沿用了OpenStack完全分布式的思...

41570
来自专栏13blog.site

小测试

可以在 @RequestMapping 注解里面加上 method=RequestMethod.GET 或者使用 @GetMapping 注解

20310
来自专栏开发之途

Android IPC机制(1)-序列化机制

16550
来自专栏码洞

Java高阶必备之Netty基础原理

Netty是Java程序员通向高阶之路必须要过的门槛之一。干了几年的Java程序员发现业务开发似乎就是在SSH的世界里摸滚打爬的时候,会开始感到迷茫,难道程序员...

8820
来自专栏JAVA高级架构

Java技术大纲

42730
来自专栏用户2442861的专栏

使用ThinkPHP框架快速开发网站(多图)

http://blog.csdn.net/ruby97/article/details/7574851/

2.7K20
来自专栏青蛙要fly的专栏

Android技能树 — 网络小结(4)之socket/websocket/webservice

介于自己的网络方面知识烂的一塌糊涂,所以准备写相关网络的文章,但是考虑全部写在一篇太长了,所以分开写,希望大家能仔细看,最好可以指出我的错误,让我也能纠正。

11230
来自专栏jouypub

应用性能优化列表

应用开发完了,但是随着用户规模的上升,数据量的积累,系统会越来越慢,性能优化将会伴随着项目一直持续下去

17110
来自专栏逸鹏说道

2.并发编程~先导篇(下)

代码实例:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/进程...

14040

扫码关注云+社区

领取腾讯云代金券