tars服务端(一):server的启动流程

本文主要介绍了tars服务端的启动流程,包括框架封装的功能如线程模型、Servant和Adapter,以及主线程的逻辑,如框架初始化、adapter创建、业务线程启动和epoll创建。详细阐述了主线程的各个阶段,如网络线程和业务线程的职责,以及Servant和Adapter的映射关系。文章旨在帮助读者理解tars服务端的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

tars服务端在框架层帮我们封装了rpc的实现细节,让开发人员只需专注于业务层的接口实现。这一篇文章主要分两个部分来初步了解tars服务端:

  1. 先从顶层了解服务端框架层提供了什么,不纠结代码细节
  2. 再结合代码具体的了解一个tars server的启动流程

一.框架封装的功能

  • 线程模型创建:主线程,网络线程,业务线程
  • 网络io模型创建
  • 业务模型创建:Adapter,Servant的管理
  • 通信器创建
  • 其他的:异常信息,统计信息上报,ssl,协程,日志,链路跟踪,消息染色等

这一节先重点了解一下线程模型,servant和adapter的细节。

 

1.线程模型

  1. 1个主线程:框架的初始化,业务层初始化,servant和adapter的管理,epoll模型的创建,业务线程创建,网络线程创建。
  2. N个网络线程:调度epoll处理网络事件,socket的accept,read,write,请求包push到接收队列让业务线程处理。
  3. M个业务线程:解析请求包,分发到业务层进行处理。
  4. 其他的线程,例如通信器的线程,不在这个线程模型的范畴内。

  • 蓝色部分,主线程启动了业务线程。业务线程的数量可以针对不同的servant设置不同的线程数。之后业务线程在一个循环逻辑中,每次从队列中取出请求进行处理。
  • 黄色部分,网络线程启动。框架限制了网络线程的数量最大为15,这个和链接管理的uid有关,uid的组成有4位是给网络线程索引的,所以最大的线程索引是15。网络线程同样把收到的请求push到队列中,供业务线程处理。
  • 红色部分,当进程被设置为terminate的时候,主线程结束,同时网络线程和业务线程也把terminate设置为true,线程结束。
  • 紫色的接收队列,网络线程收到请求后push到队列中,业务线程通过waitForRevcQueue取出请求进行处理。接收队列只有1个,属于adapter的。
  • 灰色的N个发送队列,每个网络线程都有1个发送队列,当业务线程向客户端回包的时候,会push到发送队列中,网络线程把发送包取出来发送到对应的客户端。
  • 另外还有一点需要注意的,不管是网络线程还是业务线程,都是基于一个循环去处理事件和消息,所以一般不能出现耗时比较久的操作(例如长时间的阻塞)。特别是网络线程,如果在处理一个fd的事件时,如果长时间阻塞,那么在阻塞期间这个网络线程的其他fd就都处理不了了;而业务线程,虽然会同时有多个业务线程去处理一个消息队列,但是如果出现了比较多的阻塞操作,也会影响到整个消息队列的消费速度(tars提供了协程解决这个问题)。

 

2.Servant和Adapter

       一开始接触tars会比较容易混淆Servant和Adapter。官网的文档告诉我们,tars节点的名字由三级组成:App.Server.Servant,在web上部署的时候也是一个Servant对应一个ip:port;但实际上Servant并没有和ip:port直接绑定,而是由Adapter来管理ip:port,Servant再和Adapter进行一一映射。从tars Server的配置文件就可以看出来,ip和port都是在Adapter的配置中的,同时每个Adapter还对应着一个servant的配置项:

<TestApp.HelloServer.HelloObjAdapter>                                                                                    
        allow                                                                                                                  
        endpoint=tcp -h 172.17.136.115 -p 23800 -t 60000                                                                       
        handlegroup=TestApp.HelloServer.HelloObjAdapter                                                                        
        maxconns=100000                                                                                                        
        protocol=tars                                                                                                          
        queuecap=50000                                                                                                         
        queuetimeout=20000                                                                                                     
        servant=TestApp.HelloServer.HelloObj                                                                                   
        threads=5                                                                                                             
</TestApp.HelloServer.HelloObjAdapter> 

        Servant和Adapter如何映射起来呢?每个Servant和Adapter都有自己的名字,在框架进行初始化时,会调用setAdapterServant()把Servant和Adapter的名字映射起来:

  • Adapter和Servant的映射关系保存在全局单列类ServantHelperManager中
  • 调用setAdapterServant把两者的名字存在map中
  • 用Adapter的名字调用getAdapterServant可以获取到对应的Servant名字
  • 用Servant的名字调用getServantAdapter可以获取到对应的Adapter名字         

每个Adapter管理一个端口,同时网络线程和业务线程都是直接与Adapter进行交互,可以说Adapter是端口在框架网络层的封装;而Servant是该端口提供的业务逻辑的集合:

 

  • 这里只画了用tars协议的servant,如果是非tars协议,那么需要在servant中覆盖父类的doRequest()接口
  • 每个adapter有属于自己的接收队列,在网络线程收到请求后,找到对应的Adapter,把包push到Adapter的接收队列中,然后业务线程再把包从adapter的接收队列中取出来分发到servant中进行处理:

 

3.框架的变与不变

以官网的HelloServer为例,实现自己的业务逻辑只需要修改下面这四个文件就可以了:

  • HelloImp.cpp
  • HelloImp.h
  • HelloServer.cpp
  • HelloServer.h

   只需两步:

addServant实际上是向框架中注册了HelloImp的生成方式,而并不是创建了HelloImp的实例。HelloImp这个Servant的实例创建其实是在后面的业务线程中做的:

  • addServant把HelloImp这个Servant的生成方法保存在全局单例类ServantHelperManager中
  • 框架的业务线程在真正需要用到HelloImp的时候会自动调用ServantHelperManager的create方法创建实例

框架借助ServantHelperManager这个全局单列类实现了servant的可插拔注册,又在运行时动态创建servant实例,通过servant管理所有可变的业务逻辑。总结起来,框架为我们封装了线程模型,epoll模型,网络通信细节和协议解析等不变的部分;而变化的部分通过继承把两个类暴露给业务层:

  1. 业务逻辑是变化的。一个server可以同时监听多个端口,框架把每个端口提供的服务抽象成servant,业务层通过继承servant,可以实现自己的业务逻辑,然后把实现好的servant注册进框架中。
  2. 业务层和框架层的交互,例如业务层需要把servant注册进框架,把协议注册进框架,注册命令到框架,获取框架的默认通信器,拉配置文件等,这些操作都是通过Application暴露给业务层的。每个业务server继承Application,还可以重载Application的一些接口,实现业务的自定义。

  HelloImp和HelloServer这两个类分别继承于Servant和Application,是变化的部分,它们与框架的交互如下:

  • 蓝色箭头方向,HelloServer通过几个接口调用了框架提供的功能:servant的注册,配置文件拉取,协议注册等;
  • 黄色箭头方向,框架在收到客户端的请求后,找到该端口对应的servant,如果是tars协议,则调用servant的onDispatch接口把请求分发到HelloImp对应的rpc接口;如果不是tars接口,则调用servant的doRequest接口,而HelloImp会重载这个接口去处理请求。

二.主线程逻辑

主线程对框架进行初始化,创建epoll,启动网络线程,handle(业务)线程等,跟着主线程的逻辑走一遍,可以熟悉框架的功能。流程图如下:

  • 红色部分,是主流程,其实就是上面说的主线程干了啥:初始化,adapter创建,epoller创建,线程创建等
  • 与主流程对应的还有很多细节放在子流程中,当然中间还是省略了很多细节,下面会结合代码进行详细讲解
  • 黄色部分,是epoller模型的创建,分布在两个阶段中。先是在adapter创建的时候,就已经把对应的ip和端口进行socket的创建,绑定,和监听;然后在epoller创建之后,把监听的fd加到epoller中。
  • 灰色部分,是adapter和servant的创建绑定过程。先是在创建adapter的时候,就执行adapter和servant的名字映射setAdapterServant();接着在业务初始化的时候addServant;最后,在业务线程启动后,生成每个业务线程自己的servant实例。
  • 蓝色部分,是三种线程最后的状态,分别等待在接收队列,epoll,或者条件变量上,直到terminate。(实际上,handle线程是等在handleGroup的monitor上,这里为了简化,才画成把handle线程等在接收队列上)

下面结合代码讲一下整个逻辑。直接从main函数看起:

int                                                                                                                            
main(int argc, char* argv[])                                                                                                   
{                                                                                                                              
    try                                                                                                                        
    {                                                                                                                          
        g_app.main(argc, argv);                                                                                                
        g_app.waitForShutdown();                                                                                               
    }                                                                                                                          
    catch (std::exception& e)                                                          
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值