Dispatcher机制
Envoy和Nginx一样都是基于事件驱动的架构,这种架构的核心就是事件循环(EventLoop)。业界目前典型的几种事件循环实现主要有Libevent、Libev、Libuv、Boost.Asio等,也可以完全基于Linux系统调用epoll来实现。Envoy选择在Libevent的基础上进行了封装,实现了自己的事件循环机制,在Envoy中被称为Dispatcher
,一个Dispatcher
对象就是一个事件分发器,就如同它的名字一样。Dispatcher
是Envoy的核心,可以说Envoy中绝大部分的能力都是构建在Dispatcher
的基础上。所以理解Dispatcher
机制是掌握Envoy的一个很重要的前提。
在Envoy中Dispatcher
不仅仅提供了网络事件分发、定时器、信号处理等基本的事件循环能力,还在事件循环的基础上实现任务执行队列、DeferredDelet
等,这两个功能为Envoy中很多组件提供了必不可少的基础能力。比如借助DeferredDelet
实现了安全的对象析构,通过任务执行队列实现Thread Local机制等等。
Libevent事件封装
Envoy在Libevent的基础上进行了封装最为重要的一个原因就是因为Libevent本身是C开发的,很多Libevent暴露出来的结构需要自己来管理内存的分配和释放,这对于现代化的C++来说显然是无法接受的,因此Envoy借助了C++的RAII机制将这些结构封装起来,自动管理内存资源的释放。接下来我们看下Envoy是如何进行封装的。
template <class T, void (*deleter)(T*)>
class CSmartPtr : public std::unique_ptr<T, void (*)(T*)> {
public:
CSmartPtr() : std::unique_ptr<T, void (*)(T*)>(nullptr, deleter) {
}
CSmartPtr(T* object) : std::unique_ptr<T, void (*)(T*)>(object, deleter) {
}
};
Envoy通过继承unique_ptr
自定义了一个CSmartPtr
,通过继承拥有了unqiue_ptr
自动管理内存释放的能力,离开作用域后自动释放内存。借助CSmartPtr
,Envoy将Libevent中的event_base
包装成BasePtr
,将evconnlistener
包装成ListenerPtr
。其中event_base
就是事件循环,一个event_base
就是一个事件循环,可以拥有多个事件循环,Envoy内部就是每一个worker线程都会有一个事件循环,也就是最常见的one loop per thread模型。
using BasePtr = CSmartPtr<event_base, event_base_free>;
using ListenerPtr = CSmartPtr<evconnlistener, evconnlistener_free>;
在Libevent中无论是定时器到期、收到信号、还是文件可读写等都是事件,统一使用event
类型来表示,Envoy中则将event
作为ImplBase
的成员,然后让所有的事件类型的对象都继承ImplBase
,从而实现了事件的抽象。同时也借助了RAII机制自动实现了事件资源的释放。
class ImplBase {
protected:
~ImplBase();
event raw_event_;
};
ImplBase::~ImplBase() {
// Derived classes are assumed to have already assigned the raw event in the constructor.
event_del(&raw_event_);
}
通过继承ImplBase
基类可以拥有event
事件成员,但是每一种事件表现出的具体行为是不一样的,比如说信号事件,需要有信号注册的能力,定时器事件则需要可以开启或者关闭定时的能力,文件事件则需要能够开启某些事件状态的监听。为此Envoy为每一种事件类型都抽象了对应的接口,例如文件事件接口。
class FileEvent {
public:
virtual ~FileEvent() = default;
// 激活指定事件,会自动触发对应事件的callback
virtual void activate(uint32_t events) PURE;
// 开启指定事件状态的监听
virtual void setEnabled(uint32_t events) PURE;
};
有了事件基类和对应的接口类后,让我们来看下Envoy如何来实现一个文件事件对象。
// 通过继承ImplBase拥有了event成员
class FileEventImpl : public FileEvent, ImplBase {
public:
FileEventImpl(DispatcherImpl& dispatcher, int fd, FileReadyCb cb,
FileTriggerType trigger,
uint32_t events);
// Event::FileEvent
// 实现了文件事件的接口,通过这个接口可