线程种类
Envoy使用了三种不同类型的线程。
主线程:该线程负责服务器启动和关闭、所有xDS API处理(包括DNS、健康检查和一般集群管理)、运行时配置、统计刷新、admin和一般进程管理(信号、热重启等)。该线程上发生的所有事情都是异步且“非阻塞”的。一般来说,主线程协调所有不需要大量 CPU 来完成的关键进程功能,这使得大部分管理代码可以像单线程一样编写。
worker:默认情况下,Envoy为系统中的每个硬件级线程生成一个工作线程(这可以通过--concurrency选项控制)。每个工作线程运行一个非阻塞事件循环,负责监听每个监听器(目前没有监听器分片)接受新连接、为连接实例化一个过滤器堆栈、并在该监听器的生命周期内处理所有 IO。这使得大多数连接处理代码像单线程一样编写。
文件刷新器:Envoy写入的每个文件(主要是访问日志)当前都有一个独立的阻塞刷新线程。这是因为即使在使用时写入文件系统缓存的文件O_NONBLOCK有时也会阻塞。当工作线程需要写入文件时,数据实际上被移动到内存缓冲区中,最终通过文件刷新线程刷新。所有工作线程在试图填充内存缓冲区时,会竞争同一把锁。
连接处理
如上所述,所有工作线程都会侦听所有侦听器,而无需任何分片。因此,内核将接受的套接字分派给工作线程。一般来说,现代内核在这方面非常擅长;它们采用IO优先级提升等功能来尝试在开始使用也在同一套接字上侦听的其他线程之前填充线程的工作,并且不使用单个自旋锁来处理。
一旦连接被某个worker接受,它就永远不会离开该worker。连接的所有进一步处理完全在工作线程内处理,包括任何转发行为。
Envoy中的所有连接池都是每个工作线程。因此,尽管HTTP/2连接池一次仅与每个上游主机建立一个连接,但如果有四个工作线程,则每个上游主机在稳定状态下将有四个HTTP/2连接。
Envoy通过将所有内容保留在单个工作线程中,几乎所有代码都可以在没有锁的情况下编写,就像单线程一样。
从内存和连接池效率的角度来看,调整--concurrency选项非常重要。拥有比需要的更多的工作线程会浪费内存,创建更多的空闲连接,并导致连接池命中率降低。
thread local storage
Envoy将主线程职责与工作线程职责分开,因此可以在主线程上完成复杂的处理,然后以高度并发的方式提供给每个工作线程(worker)。
主线程基本上处理Envoy进程内的所有管理/控制平面功能。这是一种常见的模式,主线程进程执行一些工作,然后需要使用该工作的结果更新每个工作线程,并且工作线程不需要在每次访问时获取锁。
主线程与worker的数据同步方式的原理本质上与RCU机制类似。