核心是FileID的设计、和文件发现的逻辑,因为Inode复用导致单纯考Inode + Dev不再可靠,需要有更可靠的FileID的设计,Linux没有create time的概念,新的内核有birth time,引入了xattr来保存额外的属性,再引入文件前128字节的md5作为唯一标识。文件发现的设计,因为inotify不保证一定可用,其次对于递归创建的场景(创建一个目录,然后立刻在里面创建文件,奔溃恢复后,也没办法发现这期间创建的文件)会存在丢事件的问题。轮询 + inotify结合的方案。通过点位文件来保证不漏采(不保证不重复采集),点位文件本身需要高可用,类似wal log的思路,定期同步到磁盘,fdatasync + rename来保证原子性。
在Envoy上主要做了几件事、性能优化(meatdata池化, ObjectPool),Dubbo协议、Dubbo over http2协议等支持,dubbo连接池,自定义cluster扩展,对接内部服务发现。hessian2序列化协议,控制面热升级(Epoch + Fd管理+plugin机制)
Envoy非常全面的扩展机制(listener filter、network filter、http filter),稳定的CodeBase,社区氛围、文档,强大的生态合作者,是Envoy成功的因素之一。单进程多线程,主线程负责控制协调,多个worker线程负责listening、filtering、数据转发。只要一个连接被listener接受,那么这个连接上的所有工作都是在同一个线程中完成的。
XDS机制: LDS、 RDS、CDS、EDS、SDS
核心的Dispatcher机制提供的Deferred Deletable机制(延迟析构)
ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher,
ConnectionSocketPtr&& socket,
TransportSocketPtr&& transport_socket,
bool connected) {
......
}
// 传递裸指针到回调中
file_event_ = dispatcher_.createFileEvent(
// 这里将this裸指针传递给了内部的callback
// callback内部通过this指针访问onFileEvent方法,如何保证
// callback执行的时候,this指针是有效的呢?
fd(), [this](uint32_t events) -> void { onFileEvent(events); },
Event::FileTriggerType::Edge,
Event::FileReadyType::Read | Event::FileReadyType::Write);
......
}
传递给Dispatcher
的callback
都是通过裸指针的方式进行回调,如果进行回调的时候对象已经析构了,就会出现野指针的问题,我相信学过C++的同学都会看出这个问题,除非能在逻辑上保证Dispatcher
的生命周期比所有对象都短,这样就能保证在回调的时候对象肯定不会析构,但是这不可能成立的,因为Dispatcher
是EventLoop
的核心。一个线程运行一个EventLoop
直到线程结束,Dispatcher
对象才会析构,这意味着Dispatcher
对象的生命周期是最长的。所以从逻辑上没办法保证进行回调的时候对象没有析构。可能有人会有疑问,对象在析构的时候把注册的事件(file_event_
)取消不就可以避免野指针的问题吗? 那如果事件已经触发了,callback
正在等待运行? 又或者callback
运行了一半呢?前者libevent是可以保证的,在调用event_del
删除事件的时候可以把处于等待运行的事件callback取消掉,但是后者就无能为力了,这个时候如果对象析构了,那行为就是未定义了。沿着这个思路想一想,是不是只要保证对象析构的时候没有callback
正在运行就可以解决问题了呢?是的,只要保证所有在执行中的callback
执行完了,再做对象析构就可以了。可以利用Dispatcher
是顺序执行所有callback
的特点,向Dispatcher
中插入一个任务就是用来对象析构的,那么当这个任务执行的时候是可以保证没有其他任何callback
在运行。通过这个方法就完美解决了这里遇到的野指针问题了。或许有人又会想,这里是不是可以用shared_ptr和shared_from_this来解这个呢? 是的,这是解决多线程环境下对象析构的秘密武器,通过延长对象的生命周期,把对象的生命周期延长到和callback
一样,等callback
执行完再进行析构,同样可以达到效果,但是这带来了两个问题,第一就是对象生命周期被无限拉长,虽然延迟析构也拉长了生命周期,但是时间是可预期的,一旦EventLoop
执行了clearDeferredDeleteList
任务就会立刻被回收,而通过shared_ptr
的方式其生命周期取决于callback
何时运行,而callback
何时运行这个是没办法保证的,比如一个等待socket
的可读事件进行回调,如果对端一直不发送数据,那么callback
就一直不会被运行,对象就一直无法被析构,长时间累积会导致内存使用率上涨。第二就是在使用方式上侵入性较强,需要强制使用shared_ptr
的方式创建对象。
使用C++重写了Hessian 2 Java,提供C++的序列化反序列化的能力,单测覆盖率90%,被网易使用
Nydus透明转换,加速了镜像的e2e时间,主要是原生的镜像是按层串行去做解压,并行下载。通过Nydus + ZRAN,可以做到并行解压,不需要更新Containerd。Nydus + Dax支持,兼容性问题处理