最近在花时间看istio的架构情况,2017年5月,Google、IBM和Lyft发布了开源服务网格框架Istio,提供微服务的连接、管理、监控和安全保护。Istio提供了一个服务间通信的基础设施层,解耦了应用逻辑和服务访问中版本管理、安全防护、故障转移、监控遥测等切面的问题。其中最关键的网络组件envoy,有着举足轻重的作用,是一个高性能的开源L7代理和通信总线。

translated from https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

在envoy代码库上底层的技术文档还很稀少。为了改变这个情况,我计划写一系列的关于各个子系统的文章。因为这是第一篇,请告诉我你想了解的和你希望看到的其他主题。

我遇到的最常见的envoy技术问题之一是询问其使用的low level的线程模型。本文将会讲到envoy如何map连接到线程上,也会描述TLS系统,TLS是内部用于使代码高性能和高并发的作用。

线程预览

图1 线程预览

线程预览

如图1所示,Envoy使用了三种类型的线程。

连接处理

如上面简短的讨论,所有的工作线程在没有分片的监听器进行监听。内核聪明地分发接收到的连接给工作线程。现代内核一般非常擅长干这事,他们并不使用单个自旋锁来处理每个连接,而是使用类似IO优先级提升的策略,策略能尝试在开始使用监听在相同socket上的其他线程前填满一个线程的工作。

一旦一个连接被一个工作线程所接受,它将永不离开worker。所有未来处理这个连接的事情都将整个使用这个工作线程来处理,包括每个转发行为。这里有一些重要的意义:

什么是非阻塞

目前为止,当讨论主线程和工作线程操作时,非阻塞被提到很多次。编写所有的代码都假设了没有阻塞。尽管如此,这并不完全正确(有什么是完全正确的吗?)。envoy的确也有一些线程到处是锁:

线程本地存储

因为envoy分了主线程和工作线程职责,所以就要求可以在主线程上复杂处理,同时要为每个工作线程搞一个高并发的可用方式。本节站在high level描述envoy的TLS系统。下节描述如何用于处理集群管理。

图2 TLS

TLS

如之前所述,主线程处理了envoy线程中几乎所有的管理和控制平面功能。(控制平面有一点超载,不过考虑到在envoy线程自身和对比工作线程转发情况,似乎也是合适的。)这是一种常见的模式,主线程处理一些事情,然后用结果更新每个工作线程,并且没有工作线程在过程中需要取锁。

envoy的TLS系统按下面所述运行:

尽管非常简单,这是一个难以置信的强大范式,和RCU锁原则非常相似。(本质上来讲,工作线程在干活时没有见到任何在TLS插槽上的数据修改。修改只发生在工作事件间的静止期里)。envoy在两个不同的方式上使用这一点:

集群更新线程

本节会介绍TLS如何做到集群管理。集群管理包括了xDS api处理和/或DNS以及健康检测。

图3 集群管理线程

图3 集群管理线程

图3显示了整个流程,包括以下组件和步骤:

1.集群管理是envoy的内部组件,管理了所有已知upstream的集群,CDS API,SDS EDS API,DNS,活跃健康检测。它负责给每个upstream集群创建一个最终一致的视图,包括已发现的主机以及他们的健康状况。

2.健康检测器执行活跃健康检测,上报健康状况给集群管理器。

3.CDS SDS EDS DNS 被执行来决定集群里的成员关系。状态变化也被回报给集群管理器。

4.每个工作线程都在持续地跑事件驱动。

5.当集群管理器决定一个群集的状态要改变时,它会创建一个新的只读的群集状态快照,并推给每个工作线程。

6.在下一个静默期,工作线程会更新快照到已经开辟的TLS插槽。

7.IO事件需要靠负载均衡决定一个主机,在此时,负载均衡器会查询TLS插槽找主机信息。这过程无需锁。(注意TLS也可以触发更新事件,这样负载均衡器和其他组件可以验算缓存、数据结构等。这超出了本文的范围,但在代码中多处被用)

靠前面的过程描述,envoy能够无锁地处理每个请求(或者不靠前面的内容)。除了TLS代码本身的复杂度,大多数代码并不需要理解线程是如何工作的,写的时候就像单线程一样。这使得大多数代码写起来更简单,另外还产生了卓越的性能好处。

其他使用了TLS的子系统

TLS和RCU被广泛在envoy里使用。其他一些例子包括:

还有很多其他的例子,但前面的例子已经可以提供不少关于TLS能干啥的好结论了。

已知的性能陷阱

尽管整体的envoy性能非常好,但在高并发和高吞吐情况下,有一些已知的情况仍然需要注意:

结论

Envoy的线程模型旨在支持简单编程,以及大规模的并行性,主要的代价为,如果没有正确的调试,可能会浪费内存和连接。这个模型允许它在非常高的工作线程数和吞吐量下有着良好的性能。

如我在twitter上简要提及,此设计也适合运行在像DPDK这样的完整用户模式网络之上,这可导致商品server在7层上能处理每秒百万的请求。在未来的几年里这些实现将会喜闻乐见。

最后一个问题:我被问了许多次为什么选c++来搞envoy。原因是它仍是唯一被广泛部署于的生产级语言,其最可能达成前文所述的架构。c++肯定不是适合所有人,甚至不是大数多人,大数多项目,但在特定场景下仍是完成工作的唯一可选工具。

相关代码

本文所讨论到的一个接口或实现的头文件: