接手的服务治理底层是依赖tars的,不过只是对tars做了二次开发,所以需要对tars的源码做一定的了解
tars的版本现在已经到v1.5版本
此分析是用的v1.03版本
具体版本是
https://github.com/Tencent/Tars/tree/508de1d5b664106e9b435eda0c478a9427128edb
阅读这类源码首先得理解他的配置功能,我们首先来看下Server端的配置
<servants>
<servant name="HelloObj" >
<home-api>com.qq.tars.quickstart.server.testapp.HelloServanthome-api>
<home-class>com.qq.tars.quickstart.server.testapp.impl.HelloServantImplhome-class>
servant>
servants>
其实例子里给的属性不全,还有,属性
这个配置主要配置暴露出去的服务
<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>
其中大的配置项:
- Application:应用名称,如果配置文件没有配置,默认为UNKNOWN;
- ServerName:服务名称;
- BasePath:基本路径,通常表示可执行文件的路径;
- DataPath:数据文件路径,通常表示存在服务自己的数据;
- LocalIp:本地ip,默认是本机非127.0.0.1的第一块网卡IP;
- LogPath:日志文件路径,日志的写法请参考后续;
- LogLevel:滚动log日志级别;
- Local:服务可以有管理端口,可以通过管理端口发送命令给服务,该参数表示绑定的管理端口的地址,例如tcp -h 127.0.0.1 -p 8899,如果没有设置则没有管理端口;
- Node:本地NODE地址,如果设置,则定时给NODE发送心跳,否则不发送心跳,通常只有发布到框架上面的服务才有该参数;
- Log:日志中心地址,例如:tars.tarslog.LogObj@tcp –h .. –p …,如果没有配置,则不记录远程日志;
- Config:配置中心地址,例如:tars.tarsconfig.ConfigObj@tcp –h … -p …,如果没有配置,则addConfig函数无效,无法从远程配置中心拉取配置;
- Notify:信息上报中心地址,例如:tars.tarsnotify.NotifyObj@tcp –h … -p …,如果没有配置,则上报的信息直接丢弃;
- SessionTimeOut:防空闲连接超时设置;
- SessionCheckInterval:防空闲连接超时检查周期;
目录名称 | 功能 |
---|---|
net | 框架rpc网络库 |
core | 框架rpc的实现 |
tools | 框架工具的实现,maven插件等 |
examples | 框架的示例代码 |
distributedContext | 框架的分布式上下文 |
protobuf | 框架的pb协议支持 |
spring | 框架的spring支持 |
package com.qq.tars.server.startup;
import com.qq.tars.server.core.Server;
public class Main {
public static void main(String[] args) {
new Server().startUp(args);
}
}
public void startUp(String args[]) {
try {
initCommunicator();
configLogger();
startManagerService();
startAppContainer(); <-----只关心这里
startSessionManager();
registerServerHook();
System.out.println("[SERVER] server is ready...");
} catch (Throwable ex) {
System.out.println("[SERVER] failed to start server...");
ex.printStackTrace();
System.out.close();
System.err.close();
System.exit(-1);
}
}
整个代码的核心在于startAppContainer 我们重点关注它
protected void startAppContainer() throws Exception {
this.container = new AppContainer();
ContainerManager.registerContainer(this.container);
this.container.start();
}
我们看下AppContainer和他的接口Container
Container接口定义启动和关闭函数
public interface Container {
public void start() throws Exception;----启动
public void stop() throws Exception;-----关闭
}
ContainManger提供了全局的管理Container的功能,从代码可以看出来全局只会有一个Container
public final class ContainerManager {
private static Container _container = null;
public static void registerContainer(final Container container) {
_container = container;
}
@SuppressWarnings("unchecked")
public static T getContainer(Class cointainerClazz) {
return (T) _container;
}
}
全局只有一个AppContainer,但是AppContainer里含有多个AppContext
public class AppContainer implements Container {
AppContext defaultApp = null;
private final ConcurrentHashMap contexts = new ConcurrentHashMap();
接下来看下APPContainer的start方法
public void start() throws Exception {
loadApp();
defaultApp = contexts.get("");
System.out.println("[SERVER] The container started successfully.");
}
loadApp就是构建AppContext的过程,注意到前面分析的配置,我们配置的servants.xml就是为了生成AppContext,我们关注XmlAppcontext分支
private void loadApp() throws Exception {
String root = ConfigurationManager.getInstance().getServerConfig().getBasePath();
File path = new File(root);
AppContext context = null;
URL servantXML = getClass().getClassLoader().getResource("servants.xml");
if (servantXML != null) {
context = new XmlAppContext();
} else if (getClass().getClassLoader().getResource("servants-spring.xml") != null){
System.out.println("[SERVER] find servants-spring.xml, use Spring mode.");
Class clazz = Class.forName("com.qq.tars.server.apps.SpringAppContext");
context = (AppContext) clazz.newInstance();
} else {
System.out.println("[SERVER] servants profile does not exist, start failed.");
throw new TarsException("servants profile does not exist");
}
contexts.put("", context);
}
看下设计逻辑
- interface AppContext
- abstract class BaseAppContext
- class XmlAppContext
- class SpringAppContext
public interface AppContext {
public String getInitParameter(String name);
public abstract String name();
public void stop();
public ServantHomeSkeleton getCapHomeSkeleton(String homeName);
}
BaseAppContext的成员变量
public abstract class BaseAppContext implements AppContext {
boolean ready = true;
ConcurrentHashMap skeletonMap = new ConcurrentHashMap();
ConcurrentHashMap servantAdapterMap = new ConcurrentHashMap();
HashMap contextParams = new HashMap();
Set listeners = new HashSet(4);
public XmlAppContext() {
try {
initFromConfigFile();
injectAdminServant();
initServants();
appContextStarted();
System.out.println("[SERVER] The application started successfully. {appname=}");
} catch (Exception ex) {
ready = false;
System.out.println("[SERVER] failed to start the applicaton. {appname=}");
}
}
private void initFromConfigFile() throws Exception {
XMLConfigFile cfg = new XMLConfigFile();
cfg.parse(getClass().getClassLoader().getResource("servants.xml").openStream());
XMLConfigElement root = cfg.getRootElement();
ArrayList elements = root.getChildList();
loadInitParams(root.getChildListByName("context-param"));<-----把xml里的的参数对写入contextParams
loadAppContextListeners(elements);<----把xml里配置的监听器写入listeners
loadAppServants(elements);<----处理xml配置的
}
这里servant从xml的配置转成内部的结构是重中之重
private void loadAppServants(ArrayList elements) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
for (XMLConfigElement element : elements) {
if ("servant".equals(element.getName())) {
try {
ServantHomeSkeleton skeleton = loadServant(element);
skeletonMap.put(skeleton.name(), skeleton);
appServantStarted(skeleton);
} catch (Exception e) {
System.err.println("init a service failed:context=[]");
}
}
}
}
private ServantHomeSkeleton loadServant(XMLConfigElement element) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
String homeName = null, homeApiName = null, homeClassName = null, processorClazzName = null,
codecClazzName = null;
Class> homeApiClazz = null;
Class extends Codec> codecClazz = null;
Class extends Processor> processorClazz = null;
Object homeClassImpl = null;
ServantHomeSkeleton skeleton = null;
int maxLoadLimit = -1;
ServerConfig serverCfg = ConfigurationManager.getInstance().getServerConfig();
//从xml里获取信息
homeName = element.getStringAttribute("name");
if (StringUtils.isEmpty(homeName)) {
throw new RuntimeException("servant name is null.");
}
homeName = String.format("%s.%s.%s", serverCfg.getApplication(), serverCfg.getServerName(), homeName);
homeApiName = getChildNodeValue(element, "home-api");
homeClassName = getChildNodeValue(element, "home-class");
processorClazzName = getChildNodeValue(element, "home-processor-class");
codecClazzName = getChildNodeValue(element, "home-codec-class");
//初始化接口和类,其中processClazz和codecClazz基本都是null
homeApiClazz = Class.forName(homeApiName);
homeClassImpl = Class.forName(homeClassName).newInstance();
codecClazz = (Class extends Codec>) (StringUtils.isEmpty(codecClazzName) ? null : Class.forName(codecClazzName));
processorClazz = (Class extends Processor>) (StringUtils.isEmpty(processorClazzName) ? null : Class.forName(processorClazzName));
//判断是否注解如果有@Servent,从Servnet去name值
if (TarsHelper.isServant(homeApiClazz)) {
String servantName = homeApiClazz.getAnnotation(Servant.class).name();
if (!StringUtils.isEmpty(servantName) && servantName.matches("^[\\w]+\\.[\\w]+\\.[\\w]+$")) {
homeName = servantName;
}
}
//获取ServantAdapter的配置
ServantAdapterConfig servantAdapterConfig = serverCfg.getServantAdapterConfMap().get(homeName);
//根据配置初始化ServerAdapter类
ServantAdapter ServerAdapter = new ServantAdapter(servantAdapterConfig);
//初始化skeleton类
skeleton = new ServantHomeSkeleton(homeName, homeClassImpl, homeApiClazz, codecClazz, processorClazz, maxLoadLimit);
skeleton.setAppContext(this);
//启动server服务
ServerAdapter.bind(skeleton);
servantAdapterMap.put(homeName, ServerAdapter);
return skeleton;
}
这里出现了很多类,需要梳理下
- abstract class AppService
- ServantHomeSkeleton(可以理解为对外暴露接口的代理类)
AppService持有一个appContext
public abstract class AppService {
public abstract AppContext getAppContext();
public abstract String name();
}
public class ServantHomeSkeleton extends AppService {
private String name = null;
private Object servantImpl = null;
private Class> apiClass = null;
private Class extends Codec> codecClazz = null;
private Class extends Processor> processorClazz = null;
}
ServantAdapter 此类提供网络服务,此为网路服务的上层入口
Tars的网络服务底层是通过nio原生的实现,没有用mina和netty,整个底层的实现在net包下,我们可以暂缓了解。
这个类我们可以理解为开了一个服务器的端口,客户端连接上来,请求方法名和参数,这个类去调用相应的对外暴露的类实现,返回结果
public class ServantAdapter implements Adapter {
private SelectorManager selectorManager = null;
private final ServantAdapterConfig servantAdapterConfig;
private ServantHomeSkeleton skeleton;
}
思考疑问???
这里可以看出tars的设计一个不妥的地方
比如这里一个Adapter只持有一个skeleton,而一个skeleton对应一个接口和接口实现类
通俗易懂的说法:
tars只支持一个端口 对应一个接口,这是其设计决定的
一个接口,一个实现类,一个通信协议的解码器,一个序列化反序列化协议解析器和唯一一个端口绑定。底层是这么设计的。
为什么这么设计?如果想设计成一个端口对应多个接口怎么设计?
一个端口对应 一个通信协议的解码器,一个序列化反序列化协议,对应多个接口(配置文件得改成这样的格式)
public ServantAdapter(ServantAdapterConfig servantAdapterConfig) {
this.servantAdapterConfig = servantAdapterConfig;
}
public ServantHomeSkeleton(String name, Object servantImpl, Class> apiClass, Class extends Codec> codecClazz,
Class extends Processor> processorClazz, int loadLimit) {
this.name = name;
this.servantImpl = servantImpl;
this.apiClass = apiClass;
this.codecClazz = codecClazz;
this.processorClazz = processorClazz;
}
public void bind(AppService appService) throws IOException {
this.skeleton = (ServantHomeSkeleton) appService;
ServerConfig serverCfg = ConfigurationManager.getInstance().getServerConfig();
boolean keepAlive = true;
//获取Codec
Codec codec = createCodec(serverCfg);
//获取process
Processor processor = createProcessor(serverCfg);
//获取ThreadPool
Executor threadPool = ServantThreadPoolManager.get(servantAdapterConfig);
Endpoint endpoint = this.servantAdapterConfig.getEndpoint();
//如果是tcp,开启tcp端口
if (endpoint.type().equals("tcp")) {
this.selectorManager = new SelectorManager(Utils.getSelectorPoolSize(), new ServantProtocolFactory(codec), threadPool, processor, keepAlive, "server-tcp-reactor", false);
this.selectorManager.setTcpNoDelay(serverCfg.isTcpNoDelay());
this.selectorManager.start();
System.out.println("[SERVER] server starting at " + endpoint + "...");
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(endpoint.host(), endpoint.port()), 1024);
serverChannel.configureBlocking(false);
selectorManager.getReactor(0).registerChannel(serverChannel, SelectionKey.OP_ACCEPT);
System.out.println("[SERVER] server started at " + endpoint + "...");
} else if (endpoint.type().equals("udp")) {
this.selectorManager = new SelectorManager(1, new ServantProtocolFactory(codec), threadPool, processor, false, "server-udp-reactor", true);
this.selectorManager.start();
System.out.println("[SERVER] server starting at " + endpoint + "...");
DatagramChannel serverChannel = DatagramChannel.open();
DatagramSocket socket = serverChannel.socket();
socket.bind(new InetSocketAddress(endpoint.host(), endpoint.port()));
serverChannel.configureBlocking(false);
this.selectorManager.getReactor(0).registerChannel(serverChannel, SelectionKey.OP_READ);
System.out.println("[SERVER] servant started at " + endpoint + "...");
}
}
private Processor createProcessor(ServerConfig serverCfg) throws TarsException {
Processor processor = null;
Class extends Processor> processorClass = skeleton.getProcessorClass();
if (processorClass == null) {
return new TarsServantProcessor();
}
if (processorClass != null) {
Constructor extends Processor> constructor;
try {
constructor = processorClass.getConstructor(new Class[] { ServantAdapter.class });
processor = constructor.newInstance(this);
} catch (Exception e) {
throw new TarsException("error occurred on create codec, codec=" + processorClass.getName());
}
}
return processor;
}
private Codec createCodec(ServerConfig serverCfg) throws TarsException {
Codec codec = null;
Class extends Codec> codecClass = skeleton.getCodecClass();
if (codecClass == null) {
ServantCodec servantCodec = skeleton.getApiClass().getAnnotation(ServantCodec.class);
if (servantCodec != null) {
codecClass = servantCodec.codec();
}
}
if (codecClass == null) {
codecClass = TarsCodec.class;
}
if (codecClass != null) {
Constructor extends Codec> constructor;
try {
constructor = codecClass.getConstructor(new Class[] { String.class });
codec = constructor.newInstance(serverCfg.getCharsetName());
} catch (Exception e) {
throw new TarsException("error occurred on create codec, codec=" + codecClass.getName());
}
}
return codec;
}
createCodec,createProcessor逻辑都是相似的,先从skeleton里取,如果配置没配置,再从annotation里取,如果没有就给默认值
这样服务器就启动起来了,回到上层函数 XmlAppContext的构造函数还有以下几个函数
void appContextStarted() {
for (AppContextListener listener : listeners) {
listener.appContextStarted(new DefaultAppContextEvent(this));
}
}
再回到上层函数 startAppContainer(); 这个就执行完毕
- startSessionManager();<–暂时不管它
这样服务端的核心部分的入口就解析完毕
最后梳理下:
全局只有一个appContain,理论上可以有多个appContext(实际上只有一个,这里是以XmlAppContext为例子),一个appContext下包含多个appService
Server启动类
持有Container
ContainerManager
持有全局的Container
AppContainer implements Container
持有 ConcurrentHashMap