Service Mesh 要是做到全链路到终端设备,就可统一流量从终端设备到最底层服务的控制面。或可为服务治理、流量可视化、边缘计算的流量本地化、响应时间的优化提供帮助。从而提升服务的用户体验。Envoy Mobile 有机会成为终端设备的 Istio Sidecar。
还记得 8 年前,我在一家电商公司的服务接入部门工作。当时接手过一个在 iOS 和 Android 上跑的 ,基于 libcurl 的 lib 。这个 lib 的主要作用就是让 APP 的流量指向用户终端最近的机房,以提高 APP 的响应速度和 API 调用成功率。可见,终端流量控制,实现边缘计算,是个普遍的需求。
from Next Generation Client APIs in Envoy Mobile
什么是 Envoy Mobile
Envoy Mobile is an iOS and Android client networking library built using Envoy as its core.
Envoy’s project goals are simply stated as:
The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem.
2019 年,Envoy 之父 Matt Klein 写了一篇 《Announcing Envoy Mobile》,算是宣告 Envoy Mobile 的可用了。
上面的视频和文章已经谈到 Envoy Mobile 的主要目标了。其实几乎和 istio sidecar 是一样的,除了多了一些移动端网络不稳定需要的需求,如离线缓存和服务降级。
现在是 2024 年了,时间已经过去 5 年,在我这个移动终端应用开发外行者看来,Envoy Mobile 好像未取得大到影响业界的成功。不过可以肯定的是,它一直在活跃开发中。
为什么我想研究这玩意?或者是源于之前的一个想法: Imagine: jEnvoy = pid(Java + Envoy) - 脑洞还是脑残 。其中想到 Java 程序和 C++ Envoy 程序的交互,需要用于 JNI。于是最近我想到了 Envoy Mobile 和它的 JNI 实现。Android 应用本质上是个 JVM 程序,与 C++ Envoy 程序库交互也需要 JNI ,于是,一个现成的成熟的 Outbound JNI
实现参考来了。
一些实现设计
Envoy Mobile 的 Thread Context
Thread Context from Next Generation Client APIs in Envoy Mobile
The first thing that happens in the application threads and in the platform layer is that we create the Envoy engine object. This is the object that allows the application to run Envoy and provide initial configuration. From the application Envoy engine we start the Envoy main thread. Unlike Envoy running the server, Envoy Mobile does all of its work in the main thread.
These are the threads that interact with the engine and actually issue network requests.
In the middle we have the Envoy thread, which actually runs the Envoy engine.
Then at the bottom we have the callback threads. These are threads that the engine can use to dispatch control back to the application when responses come in the network.
借鉴 Server Envoy(非 Envoy Mobile) 的 Event Dispatcher 设计
This was enabled by one of Envoy’s key design principles, which is that most code in Envoy is actually single threaded code. When Envoy is running in the server, Envoy’s main thread is mostly responsible for life cycle updates, and then when requests come in they are actually attached to worker threads. If worker threads need to communicate to Envoy’s main thread, Envoy has an event dispatcher. This is what allows us to cross threading barriers both from the worker threads to the main thread and also from the main thread over to work threads.
The second important concept here is that when Envoy is making HTTP requests it uses an HTTP manager. This HTTP manager is the basis of Envoy’s HTTP stack. We’ll see how this becomes really important in a few slides.
复用 Envoy 的组件
What we did in Envoy Mobile is actually we hoisted these two constructs (the
event dispatcher
and theHTTP manager
) and we bolted them together into what we call theEnvoy Mobile engine
because theHTTP dispatcher
allows us to cross this threading context that I’ve been describing, and then theHTTP manager
allows us to actually make HTTP calls out into the network.我们在 Envoy Mobile 中所做的实际上是提升了这两个组件(
事件调度器
和HTTP 管理器
),并将它们组合到所谓的Envoy Mobile 引擎
中,因为HTTP 调度器
允许我们跨越我一直在描述的这个线程上下文,然后HTTP 管理器
允许我们实际上在网络中发出 HTTP 调用。What I want to highlight here is that we have lifted these constructs from Envoy itself. While Envoy Mobile is a newer library, the real underlying implementations of all of our engine is the well-trod paths of Envoy constructs that have a lot of industry production experience.
我想在这里强调的是,我们已经从 Envoy 本身中提取了这些组件。虽然 Envoy Mobile 是一个较新的库,但我们所有引擎的真正底层实现都是具有大量行业生产经验的 Envoy 组件。
After we have the engine started, then the application threads can create HTTP streams. The HTTP streams then can issue calls across the application thread into the Envoy engine with event dispatcher and then into the HTTP manager. Then from the HTTP manager we go out into the internet.
Http Filters
I want to zoom here into the HTTP manager because, as I said, the HTTP manager is the foundational basis of the HTTP stack in Envoy. It’s also a big extension point for Envoy because it has this concept of HTTP filters that attach onto the HTTP manager. What these filters do is they enact behavior onto every single HTTP request that goes out of the engine and then every HTTP response that comes back in. Really, I want to put a pin here because this is exactly what we were looking for before: a place where we can enact those behaviors that we had declared onto our APIs.
Callback
After we have the request go out into the internet and our services do whatever they need to do, the response comes back into the engine. When the response comes back into the engine we have some bridging code that calls callbacks back into the platform. This is done via platform-specific callback mechanisms. It allows us to then, again, dispatch from the main thread into the callback threads and give control back to the application layer.
在我们将请求发送到互联网并且我们的服务执行它们需要执行的操作后,响应将返回到引擎。当响应返回到引擎时,我们有一些桥接代码,用于将回调调用回平台。这是通过特定于平台的回调机制完成的。它允许我们再次从主线程分派到回调线程并将控制权交还给应用层。
An important design decision that I’d like to discuss here is that we unsynchronized both the request path and the response path. There is at no point a moment of synchronization. We did this deliberately because we didn’t want any platform code to actually hang onto operations while we were issuing network requests.
我想在这里讨论的一个重要设计决策是,我们取消了请求路径和响应路径的同步。在任何时候都没有同步的时刻。我们故意这样做,因为我们不希望任何平台代码在我们发出网络请求时实际挂起操作。