事件驱动框架

事件驱动框架#

设计#

大家都认为 Envoy 是一个 Proxy 。主要实现定制逻辑的请求转发。这点没错。但与拥有高负载低延迟要求的其它中间件一样。设计上必须考虑负载的调度和流控。良好的调度设计必须平衡吞吐、响应时间、资源消耗(footprint) 。

图: 事件驱动框架设计

图: 事件驱动框架设计#

用 Draw.io 打开

  1. Dispatcher 线程事件循环。Dispatcher Thread 等待事件(epoll wait) 并在等待超时或事件发生后处理事件。

  2. 有以下事件唤醒 epool wait:

    • 收到线程间 post callback 消息。主要用于 Thread Local Storage(TLS) 的数据更新。如 Cluster/Stats 信息更新

      • Dispatcher 为线程间事件

    • timer timeout 事件

    • file/socket/inotify 事件

    • internal active event。内部其它线程,或者本 dispatcher 线程,程序显式调用函数,触发事件

  3. 处理事件

一次事件处理的 loop 过程,包含上面三步。三步完成合称为一个 event loop , 有时,也叫 event loop iteration

实现#

上面主要在 kernel syscall 层面上介绍事件处理的底层过程。下面介绍在 Envoy 代码层面,如何抽象和封装事件。

Envoy 使用了 libevent 这个 C 编写的事件 library。还在其上作了 C++ OOP 方面的封装。

图 - Envoy 事件的抽象封装模型

图: Envoy 事件的抽象封装模型#

用 Draw.io 打开

如何快速在一个重度(甚至过度)使用 OOP 封装和 OOP Design Pattern 的项目中读懂核心流程逻辑,而不是在源码海洋中无方向地漂流? 答案是:找到主线。 对于 Envoy 的事件处理,主线当然是 libevent 的对象:

  • libevent::event_base

  • libevent::event

如果你对 libevent 还不了解,可以看看本书的 libevent 核心思想 一节。

  • libevent::event 封装到 ImplBase 对象中。

  • libevent::event_base 包含在 LibeventScheduler <- DispatcherImpl <- WorkerImpl <- ThreadImplPosix

然后,不同类型的 libevent::event ,又封装到不同的 ImplBase 子类中:

  • TimerImpl - 基于定时的功能都会使用它。如连接超时,闲置超时等等

  • SchedulableCallbackImpl - 设计上,在高负载时,Envoy 需要平衡事件处理的响应时间和吞吐量。为平衡每次 event loop 的工作量及避免一次 event loop处理太久而影响其它未处理事件的响应时效。有的内部发起的、或定时发起的处理过程,可以选择在当前event loop 的最后一个完成,也可以 “延后” 到下一个 event loopSchedulableCallbackImpl 封装这种可调度的任务。应用场景有:thead callback post / 请求重试等等

  • FileEventImpl - file / socket 事件

其它信息上图已经比较详细,不再多言了。

扩展阅读#

如果有兴趣研究实现细节,建议看看我 Blog 的文章:

与 Envoy 作者 Matt Klein 的: Envoy threading model