brpc源码解析(二)—— brpc收到请求的处理过程


作为rpc服务器,在启动过后,最主要的一个过程就是收到请求后的处理,而这就牵涉到一个网络编程相关最基本的部分:如何有效地处理socket传过来地数据,这篇文章就来详细聊一聊brpc是怎么处理收到的请求的。

一、基本设计思路

作为服务器,业界最典型的实现就是区分I/O线程和工作线程,一个或多个I/O线程负责从socket读取数据放入一个队列,然后一堆worker线程来从队列里取数据并处理,或者I/O线程读完数据直接交给worker,此类严格区分I/O线程和worker线程的机制会有几种典型的问题:

1.一个I/O线程同时只能读取一个fd,I/O线程通常只有一个或者几个,如果某个fd上数据量很大,读取很耗时会容易导致其他fd数据的阻塞。
2.如果要操作队列会产生竞争,高并发的时候严重影响性能。
3.当前主流的多核cpu机制下,各个核之间进行cache同步是微妙级的,并不是很快,容易出现cache bouncing,I/O线程将任务交给worker涉及到跨核,效率不是很高,之间的同样cpu缓存到内存之间交换数据也比较慢。

和很多rpc框架不同,brpc有一套独特的机制来解决上述问题,总的来说就是,不区分I/O线程和worker线程,通过全局的EventDispatcher来负责监听fd上的事件,如果fd上已经有bthread在处理了直接返回,否则启动一个bthread处理,被EventDispatcher启动的bthread负责读取fd上的消息,每读到一条就启动bthread去处理,最后一条原地执行,直到读完为止,因为EventDispatcher本身既不负责读消息也不负责处理消息,自身吞吐量可以做得很大,fd间和fd内的消息都能获得并发,所有线程都是wait-free的,这使得brpc在高负载时仍能及时处理不同来源的消息。下面就结合源码详细阐述相关机制。

二、实现细节

在上篇文章里已经介绍过了,服务器启动后会由EventDispatcher监听epoll事件,作为服务器,收到一个新连接属于epoll_in事件,会调用Socket::StartInputEvent进行处理。StartInputEvent函数如下:
在这里插入图片描述
StartInputEvent函数里,首先是调用Address方法根据SocketId获取socket s,因为一个fd上会不断地发生事件,socket类里面设置了一个butil::atomic _nevent变量,用来保证对于一个fd,同时只会有一个bthread在处理,当收到事件时,EventDispatcher给nevent加1,只有当加1前的值是0时启动一个bthread处理对应fd上的数据,前值不为0说明已经有bthread在处理该fd上的数据了,可以直接返回,因为正在处理的bthread会一直读下去。总的来说StartInputEvent仅仅是负责判断发生事件的fd上有没有bthread在处理了,没有就原地启动一个,有就直接返回,注意调用的是bthread_start_urgent,这个函数启动bthread会让出当前bthread,也就是官方文档所说的,“EventDispatcher把所在的pthread让给了新建的bthread,使其有更好的cache locality,可以尽快地读取fd上的数据”,不需要pthread切换对性能有帮助”。如果因为某些原因bthread_start_urgent启动失败,则原地执行ProcessEvent兜底。

调用的ProcessEvent如下:
在这里插入图片描述
也就是执行socket 里的_on_edge_triggered_events,对于目前收到新连接请求的情况,事件发生在监听端口上,对应的_on_edge_triggered_events是上篇文章提到的StartAccept里赋值的OnNewConnection,也就是来了新连接要调用的回调函数

OnNewConnections内部调用OnNewConnectionsUntilEAGAIN函数,OnNewConnectionsUntilEAGAIN核心如下:
在这里插入图片描述
首先accept监听fd来得到clinet fd
在这里插入图片描述
然后调用Create新建一个socket来具体处理本次新连接的后续消息,在里面添加了一个新的epoll_in 事件,注册的处理函数是InputMessenger::OnNewMessages,注意和前面的OnNewConnections对比,前面是处理新连接,这个是新连接处理后来处理新连接上的新消息。在InputMessenger::OnNewMessages函数里面,核心是读取消息然后交由相应协议的处理函数,进而调用用户服务里的实际业务函数。

对于clinet fd上收到的epoll_in事件,入口仍然是StartInputEvent,只不过随后的ProcessEvent调用的处理函数是InputMessenger::OnNewMessages,核心部分代码如下:
在这里插入图片描述
一直读取直到读完。
在这里插入图片描述
做一次读取
在这里插入图片描述
messenger是InputMessenger对象,负责从fd上切割和处理消息。上篇文章介绍了brpc的自定义协议,每个协议其中最重要的就是ParseXXXmessage和ProcessXXX,分别对应处理消息的两个基本步骤,Parse和Process,Parse一般是把消息从二进制流上切割下来,运行时间较固定;Process则是进一步解析消息(比如反序列化为protobuf)后调用用户回调,时间不确定。因为brpc是支持单端口多协议的,因此InputMessenger会逐一尝试用户指定的种协议的回调,当某一个Parse成功切割下一个消息后,调用对应的ProcessXXX。由于一个连接上往往只有一种消息格式,InputMessenger会记录下上次的选择,而避免每次都重复尝试。
在这里插入图片描述
在这里插入图片描述
QueueMessage是启动一个bthread执行ProcessInputMessage来处理一个msg。若连续从某个fd读取出n个消息(n > 1),InputMessenger会启动n-1个bthread分别处理前n-1个消息,最后一个消息则会在原地被Process。
根据handler里的解析器处理消息得到解析后的结果
ProcessInputMessage如下,调用的是msg->_process,也就是对应协议的process_request来处理cut下来的message:
在这里插入图片描述
这里实现的关键就是last_msg,定义如下,是一个自定义deleter的unique_ptr:
在这里插入图片描述
每次循环都会去执行对last_msg的处理,如果last_msg非null,则新启一个bthread去处理,在第一次循环,也就是处理第一个msg的时候,last_msg为null,随后的每一次循环都会先调用QueueMessage处理last_msg,

随后last_msg置成这次循环的msg,待下次循环处理,分别会对msg->_process (消息处理函数)和msg->_arg 赋值供后续上述ProcessInputMessage调用
在这里插入图片描述在这里插入图片描述
所以最后一次循环完了后还剩一个last_msg没有处理,怎么办呢,last_msg的deleter是RunlastMessage,如下:
在这里插入图片描述
RunlastMessage跟queueMessage一样都是调用的ProcessInputMessage,析构的时候会自动调用deleter,从而实现最后一个msg原地调用。
Msg->_process是对应协议的process_request请求处理函数,其中最终会调用用户函数,以http协议为例,Msg->_proces指向的是协议注册时我们看到的ProcessHttpRequest,
在这里插入图片描述
可以看到msg作为参数进来后,通过msg->arg()拿到了指向server的指针,msg->arg()返回的就是msg->_arg,前面图里可以看到msg->_arg 所赋予的是handler 的arg,回忆一下,handler 的arg其实就是所启动的server的指针,是在BuildAcceptor()里面添加各种handler的时候赋值的:
在这里插入图片描述
ProcessHttpRequest核心处理部分如下:
先拿到MethodProperty sp
在这里插入图片描述
得到service和method
在这里插入图片描述
调用业务代码,比如echo示例:
在这里插入图片描述
在这里插入图片描述
具体的打包发送则是通过调用done->Run(),在推荐用法里,由RAII机制保证,也就是上图的done_guard。
在这里插入图片描述

三、总结

brpc在处理接收到的请求的时候,不区分io线程核worker线程,二是利用EventDispatcher基于边缘触发的epoll做event的分发,这样并发可以做得很大,具体的消息切割、协议相关的请求处理、以及后续的用户逻辑均交由独立的bthread来处理,实现了wait-free,不用担心某个请求io耗时过大造成批量IO阻塞,也没有生产者消费者队列所带来的瓶颈。并且是让出当前pthread的方式进行调度,保证了cache locality。以上消息处理上的优势也是brpc高性能的基石之一。

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值