网络IO主流程
TNonblockingServer使用的网络io模型为非阻塞同步io模型,accept由单个io线程处理,recv由多个io线程处理,相应的rpc事务处理由另外的线程池完成,处理完成后通知对应的io线程将处理结果进行send。
accept连接
所有网络连接由ioThreads_[0]线程监听端口,在listenHandler中处理,accept后生成一个clientConnection,如果存在多个io线程,ioThreads_[0]线程通过robin算法从所有ioThreads_中选择一个io线程处理事务,即ioThreads_[0]通过pipe管道将clientConnection信息通知被选择的io线程,被选择的io线程此时会收到通知并处理notifyHandler,被选择的io线程在notifyHandler中将clientConnection信息读取出来,再进行recv数据并解析处理。
recv接收
接收事务处理都在connection->transition()中进行,首先读取帧长度readWant_,然后根据帧长度调整调整readBuffer_堆区的大小,最后将数据读完后即readBufferPos_ == readWant_,开始进行真正的rpc事务处理,如果是采用线程池的方式,就要先建立Task任务,将其放入任务队列中,线程池里面的线程则从任务队列中去任务进行消费,如果没有使用线程池,则该被选择的io线程就直接进行rpc事务处理了。
send回复
在线程池中将rpc事务处理完成后,通过connection_->notifyIOThread通知该连接的io线程,该io线程收到通知同样在notifyHandler中将clientConnection信息读取出来,最后将需要发送的信息通过tSocket->write_partial发送出去,客户端就可以收到rpc的回复了。
线程池
线程池初始化时传入线程数量,依次启动一定数目的工作线程,工作线程循环从任务队列中取任务执行,如果没有任务时阻塞等待,任务队列中的任务由上面的recv接收操作放入,一旦有任务放入,且此时还有空闲线程,则会通知空闲线程从队列中取任务立即执行。
分析
可能存在的性能瓶颈
io线程间通过pipe进行连接信息的传送,会陷入过多系统调用,fbthrift里使用eventfd替代。
一个线程池对应一个任务队列,无法区分任务优先级,如果线程池中的线程都阻塞,会导致其余任务均无法执行。
fbthrift里反馈:之前的thrift ThreadManager 使用互斥量和条件变量来对任务进行排队和唤醒线程。 实际上,这将吞吐量限制在 300k qps 左右。 我们已经彻底修改了 ThreadManager 以使用无锁 MPMC 队列,并添加了 LIFO 工作线程语义。 这将吞吐量提高到略低于 1M qps。