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 条评论
登录 后参与评论

相关文章

来自专栏史上最简单的Spring Cloud教程

史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的...

2309
来自专栏逸鹏说道

微软分布式云计算框架Orleans(1):Hello World

自从写了RabbitHub框架系列后的一段时间内一直在思索更加轻量简便,分布式高并发的框架(RabbitHub学习成本较高),无意间在网上级联看到了很多...

3959
来自专栏Maroon1105

Linode Cloud中的大数据:使用Apache Storm进行流数据处理

Apache Storm是一项大数据技术,使软件,数据和基础架构工程师能够实时处理高速,大容量数据并提取有用信息。任何涉及实时处理高速数据流的项目都可以从中受益...

1162
来自专栏SeanCheney的专栏

爬虫框架整理汇总

2366
来自专栏Kirito的技术分享

使用Spring Cloud Sleuth实现链路监控

在服务比较少的年代,一个系统的接口响应缓慢通常能够迅速被发现,但如今的微服务模块,大多具有规模大,依赖关系复杂等特性,错综复杂的网状结构使得我们不容易定位到某一...

3598
来自专栏运维小白

linux基础(day 31)

10.1 使用w查看系统负载 监控系统状态 w / uptime 命令,查看系统负载 cat /proc/cpuinfo 命令,查看cpu核数——>里面的pro...

1855
来自专栏程序猿DD

Jenkins Pipeline插件十大最佳实践!

Jenkins Pipeline 插件对于 Jenkins 用户来说可以让用户能够改变游戏规则。基于 Groovy 中的领域特定语言(DSL),Pipeline...

39110
来自专栏圣杰的专栏

eShopOnContainers 知多少[3]:Identity microservice

通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问。那进行 API 级别信任决策的第一步就是身份认证——确定用户身份是否可靠。

662
来自专栏互联网高可用架构

Java服务化系统线上应急和技术攻关,你必须拥有的那些应用层脚本和Java虚拟机命令

1953
来自专栏散尽浮华

Linux下快速迁移海量文件的操作记录

有这么一种迁移海量文件的运维场景:由于现有网站服务器配置不够,需要做网站迁移(就是迁移到另一台高配置服务器上跑着),站点目录下有海量的小文件,大概100G左右,...

2017

扫码关注云+社区