gRPC completion queue架构
- grpc使用completion_queue来缓存事件,典型的如rpc请求。
- 使用grpc_cq_end_op来向cq中加入事件,使用grpc_completion_queue_next或pluck从队列中取出事件。
- 其中next/pluck的区别是:next从队列中按顺序依次取出事件,pluck可以通过指定tag条件来获取特定的事件.
接下来,我们通过同步RPC请求的处理流程,来了解下completion_queue的具体使用。
- 对于grpc_server,启动时会根据配置使用指定个数个cq来处理RPC请求,默认为1.
- 对于每个cq会启动一个ThreadManager(线程池)来处理,其中使用1~CPU核心个线程来处理epoll请求,再使用其它动态线程来处理具体的RPC方法。
- 对于grpc_server中注册的每个RPC方法,都会用register_method来管理,每个register_method都有一个request_matcher用来跟踪接收到的RPC请求
request_matcher跟踪RPC请求的方式如下:
- 内部维护固定个数个令牌(令牌个数和cq个数相同),如果请求到达时还有令牌,则指定放入cq队列中。如果令牌当前已经消耗完,则放入队列中待处理(请求处于pending状态).
- grpc_server每次接收到RPC请求时都会申请令牌,实际处理RPC方法时,再归还令牌,同时判断是否有pending状态的请求待处理。
- 通过这种机制,对RPC请求进行了一定的流控。
上面提到的各种对象关系如下图所示:
下面再来看一下,RPC请求到达时如何使用队列。
涉及到对象的整体关系如下:
一个cq通常由3部分组成
- grpc_completion_queue结构头,用于存储队列的管理信息.
- completion_queue_data,队列数据存储。内部通过grpc_cq_event_queue来存储事件。这个队列是一个MPSC(多生产者单一消费者队列),只有一个消费者时是无锁的,性能较高。有多个消费者时会使用锁来进行同步
- grpc_pollset用于管理关心的fd, 驱动事件循环
当有RPC请求到来时,通过grpc_cq_end_op将RPC请求封装成grpc_cq_completion放入队列,如果是队列中第一个事件,则会调用grpc_pollset的kick来唤醒一个工作者来完成任务。