Tars源码分析----Application

前言

Application是相对于EpollServer来说更靠近上层的类。它是所有服务的基类。Tars的核心功能是可以方便地构建各种类型的服务,因此Application类作用是非常核心的。每一个以Tars为基础构建的服务都需要从Application继承而来。从更底层的角度来看的话,Application是对EpollServer的一次封装,一定程度上可以说它连接了上层的服务实现者和下层的EpollServer组件。它为服务实现者提供了很多操作EpollServer的接口,使得服务实现者不需要过多关注EpollServer中的细节。

服务进程

这里写图片描述

从用户的角度来说,Tars是一个服务集合。用户可见的所有tars系统的关键组件,包括Patch,Log,Registry,Config,Notify等等(可参考Tars架构图)都是一个个的服务进程,这些是系统自身提供的元服务,为系统提供对用户服务的管理功能。当然Tars用户需要做的也是在Tars添加自己的服务,供客户端使用。

这里关注的是为用户提供的RPC服务。如上图所示,可以看到一个RPC调用过程需要涉及到三个寻址:

  1. 服务寻址,这个是进程层面,一般根据ip地址和端口完成;
  2. servant寻址,由具体的服务来完成;
  3. RPC method,由实现该method的servant来完成。

1比较简单,在配置服务时进行系统设置即可,与服务代码关系不大。2和3则需要服务的实现者编码实现。当然,由于这些算法都存在一定的共性,因此可以针对性的提供一些标准的实现。比如对于包含servant的服务,Tars为服务提供了ServantHandle来进行servant寻址处理,这得益与EpollServer为adapter提供的可插入Handle接口。同时Tars利用自动代码生成技术来为servant提供具体RPC method的寻址,这得益于servant类提供的onDispatch函数。

Tars服务的这种分层寻址,使得用户可以自己定义寻址算法,不必拘泥于tars提供的基于servant。当然,这是没必要的。

Application

Application的是所有服务的基类,他除了负责启动Epollserver之外,还需要根据配置文件对服务进行配置。主要是配置Adapter。当然,它还提供了一些虚函数供服务实现者对服务的初始化,析构进行控制。除此之外,Application还为服务实现者提供了Adapter的handle的设置接口:

virtual void setHandle(TC_EpollServer::BindAdapterPtr& adapter);

默认情况是为Adapter配置ServantHandle:

void Application::setHandle(TC_EpollServer::BindAdapterPtr& adapter)
{
    adapter->setHandle<ServantHandle>();
}

这种灵活性和我们上一节分析是一样的。

这里比较有意思的地方是,Tars中servant和adapter好像需要是一一对应的:

//void Application::bindAdapter(vector<TC_EpollServer::BindAdapterPtr>& adapters)
    if (_conf.getDomainVector("/tars/application/server", adapterName))//获取所有的adapter名字
    {
        for (size_t i = 0; i < adapterName.size(); i++)
        {
            string servant = _conf.get("/tars/application/server/" + adapterName[i] + "<servant>");

            checkServantNameValid(servant, sPrefix);

            ServantHelperManager::getInstance()->setAdapterServant(adapterName[i], servant);

            TC_EpollServer::BindAdapterPtr bindAdapter = new TC_EpollServer::BindAdapter(_epollServer.get());

这个比较反直觉,因为如果可以在每个adapter上面配置多个servant的话,配置文件要简短得多。不过tars之所以这么设计,主要是为了给servant提供和adapter一样的灵活性。比如在同一个服务上的多个servant可以通过配置不同的adapter分别支持不同的传输协议。这一点可以从Application为servant提供一个协议绑定函数上看到:

void addServantProtocol(const string& servant, const TC_EpollServer::protocol_functor& protocol);

这个函数为application中对应的servant设置一个网络传输协议,本质上是设置该servant对应的adapter。

除了在配置文件中需要设置servant外,还需要调用Application的

template<typename T> void addServant(const string &id)

函数,将servant加入到服务中。默认情况下,ServantHandle负责服务的servant寻址。因此addServant函数应该将servant加入到负责该servant的ServantHandler中。ServantHandle也确实维护了一个集合:

map<string, ServantPtr> _servants;

不过Tars在处理时并没有在addServant直接将servant构建起来交到ServantHandle集合中,而是异步地在ServantHandle初始化时才将其加入进去:

void ServantHandle::initialize()

这个过程通过ServantHelperCreation来完成。

可以看出的是,每个handle线程都会分别维护一份它负责寻址的servant副本(一个handle可能会负责处理多个Adapter的消息)。该handle也只使用它自己创建的servant副本,这似乎会降低内存使用率,这里应该是考虑到了不同线程处理的servant需要在servant中维护一些状态。

总结

简单介绍了一下Application,主要从以下几个方面:

  1. 首先以Tars中为用户提供的ServantHandle为基础,介绍了Tars的服务寻址过程。其实也就是单个Tars服务的基本架构(这里限于RPC服务)。
  2. 详细介绍了Application为用户提供的接口。包括为服务添加Servant的addservant函数,为服务的adapter设置协议处理的函数等等。adapter一般和servant对应,因此这也为同一个服务支持不同的协议的servant提供了支持,灵活性很大。还提供了修改Adapter的handle的接口,SetHandle。默认是ServantHandle,它帮助服务完成servant寻址,是一个虚函数,用户可自己实现,不必一定遵循servant的架构。
  3. 具体到单个servant里面rpc method的寻址。这对不同的servant具有统一性,而且和具体servant实现的method有关,因此交给了代码自动生成。接口是servant类的onDispatch函数。

Application作为所有服务都需要继承的类,它通过封装底层的EpollServer、为EpollServer的Adapter(关系到通信协议等)和Adapter的Handle(关系到servant寻址等)提供设置接口来为上层用户编写服务提供了方便。当然,还有tars协议的代码生成,也使得用户只需要关注servant里面具体的业务逻辑实现,不需要关心method的寻址。

猜你喜欢

转载自blog.csdn.net/swartz2015/article/details/80710872