gRpc原理_epool-poling引擎实现原理

文章观点来自于:https://github.com/grpc/grpc/blob/master/doc/core/epoll-polling-engine.md 

加入了点自己的理解,可能还有很多地方认识不到位,仅供和大家一起交流和讨论。

老 实现

 

当前gRpc 的pollset是基于epoll实现,主要有以下几个点:

  • pollset 和epoll 一一对应
  • 一个cq 对应一个pollset
  • 多个线程可以消费一个cq,需要业务自己创建消费线程,调用grpc_completion_queue_next() 或者grpc_completion_queue_pluck()
  • 一个fd可以被多个pollset引用到(惊群问题的原因),也就是可以存在于多个cq
  • pollset中当一个事件产生,由于多个线程可能都在消费这个pollset(cq),  多个线程会被唤醒。grpc不能保证那个对这个事件真正感兴趣的线程调用到和这个 event关联的callbacks。真正执行这个事件的回调工作的线程,最终会把一个"完成事件"入队到一合适的 完成队列(cq), 然后真正的唤醒对这个事件感兴趣的线程(当然也可能是自己,这样就没有线程间的跳转)
  • ev_fdx: 对应线程的文件描述符,epoll_fdx: epoll set 的文件描述符;

举个例子: 上图 fd1 变成可读, thread 1 到 thread P 都有可能被唤醒。设定 threadP 调用grpc_completion_queue_pluck() 且是对在fd1上的这个事件真正感兴趣的。但是呢,不幸的是thread1被唤醒了,所以thead1就执行了一下callbacks,最后通过发送信号event_fd_P真正的唤醒threadP。 紧接着,threadP从grpc_completion_queue_pluck()返回,然后做具体处理。

 

 

之前存在的问题

Thundering Herds(惊群)

如果说是,多个线程并发的调用 epoll_wait(),那么木有问题,只有一个线程会醒来。但是,当前grpc的实现是调用 poll(还是看上图),不是直接调用epoll_wait。

也就是说,由于一个Fd可以在多个pollset, pollset 可以用多个线程去消费,这就意味着当fd变成readable/writable,所有关联到这个pollset中的线程多会被唤醒

这就很明显会影响到服务端的性能,当然客户端也会。

文档中给了两个例子:

Example 1: Listening fds on server

  • A gRPC server can have multiple server completion queues (i.e completion queues which are used to listen for incoming channels).
  • A gRPC server can also listen on more than one TCP-port.
  • A listening socket is created for each port the gRPC server would be listening on.
  • Every listening socket's fd is added to all the server completion queues' pollsets. (Currently we do not do any sharding of the listening fds across these pollsets).

This means that for every incoming new channel, all the threads waiting on all the pollsets are woken up.

Example 2: New Incoming-channel fds on server

  • Currently, every new incoming channel's fd (i.e the socket fd that is returned by doing an accept() on the new incoming channel) is added to all the server completion queues' pollsets [^5]).
  • Clearly, this would also cause all thundering herd problem for every read onthat fd

There are other scenarios especially on the client side where an fd can end up being on multiple pollsets which would cause thundering herds on the clients.

 

下面这个服务:

创建了24个cq, 每个cq对应一个线程,服务端默认会为创建和cq个数一样的TCP-port进行监听,

如果说,会将每个istening socket's fd添加到服务端所有的cq 的pollset中,那么意味消费这些cq对应的线程会存在"惊群"问题。

对于之前惊群问题的一个改进

新epoll实现

核心思想是: 打包相关的fds到唯一一个 epoll-based set。这和前面的不同,之前实现可能一个fd会被添加到多个epoll_fd,这边其实是多加了一层(哈哈,没什么问题是不能加一层来解决的),做了一个隔离操作。这样至少可以保证只有一个线程会被唤醒,比如下图:thread P 对fd_1感兴趣(调用grpc_completion_queue_next), fd_1产生事件,那么现在只会theadP 被唤醒;之前其他线程都可能会被唤醒。

实现

引进polling_island (对应会有一个epoll set), 包含如下结构:

  • epoll_fd:epoll set 的文件描述符
  • fd_set:对应 polling_island 的 epoll set 中的所有 fd( 做合并操作时需要的数据结构)
  • event_fd:level triggered event fd, 用于唤醒所有要等 这个epoll set的线程(event_fd 也是放到对应的epoll set, 这个对于下面谈到的合并很有用)
  • merged_to:指向真正能用的polling_island (被合并),然后这个polling_island 里面的东东也就无用了。

 fdpollset 和 polling_island关系:

  • 一个fd可以属于多个pollset(也就是和之前的一样,一个cq还是对应着一个pollset,不过我觉得一个listening socket's fd  应该只会加到一个cq中,不然感觉问题还是存在),但是都属于同一个polling_island
  • 一个poolset也只能属于唯一一个 polling_island
  • 也就是说 与同一个fd有关联的pollset属于一个 polling_island

添加fd到pollset的算法

  • 如果fd 和 pollset 已经都属于同一个polling_island :啥都不用做
  • fd 和 pollset 的polling_island 不同的话
    1. fd数量少的polling_island 移动到 fd多的polling_island ,然后更新小的polling_island里面的merged_to指向这个大的
    2. 唤醒所有在等这个小的polling_island的epoll_fd (通过信号发event_fd 到这些线程),然后让他们现在等大的polling_island的epoll_fd 
    3. 更新fd 和pollset 指向新的polling_island

直接唤醒( Directed wakeups)

新的实现和老的实现一样不能保证真正对一个事件event感兴趣的线程被唤醒,同样可能需要经过线程间的传递。但是,我觉得至少不会说一下唤醒太多无关的线程。

之前的实现:每一个poll 线程呢都会监听着event_fd,唤醒他们的话就是简单的通过信号发event_fd 到这些线程。但是,要用event_fd 意味着每个线程都要调用poll(在 event_fd and epoll_fd) 而不是epoll_wait,正式这个原因导致了之前提到的惊群问题。

现在改进用信号去唤醒那个真正需要唤醒的线程。不过,POSIX systems  现在只剩下少数可以使用的:SIGUSR1SIGUSR2 and SIGRTx(SIGRTMIN to SIGRTMAX)

gRPC库提供一个接口,来实现这个功能:

void grpc_use_signal(int signal_num)

如果不提供这个signal_num,gRpc会回退到老的实现逻辑(调用poll监听wakeup_fd 和 epoll_fd)

 

 

总结

问题:

1.之前,fd1 发生事件, 1 到 P 线程都有可能被唤醒,现在呢?

 

2.现在的实现,是否还会把每一个 listening socket's fd  加到所有的cq中?文章后面没说到,但我感觉应该是不会了吧,不然最后相当于只有一个polling_island ?

我又看了一下

https://github.com/grpc/grpc/issues/5470

https://github.com/grpc/grpc/blob/master/doc/core/grpc-client-server-polling-engine-usage.md

,目前应该是规避了这个问题。参考的https://github.com/grpc/grpc/blob/master/doc/core/epoll-polling-engine.md这篇文章应该也是比较老的了。

 

 

参考:

https://github.com/grpc/grpc/blob/master/doc/core/epoll-polling-engine.md

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值