使用 Async-hooks 集成 Envoy 和我们的 RPC 库(一)

在这篇文章中,我们将介绍新的 Node.js async_hooks API 在 Tubi 的一些工程实践。首先,我们介绍了驱动我们开始尝试它的缘由,以及我们特定的需求场景。然后我们将讲述我们遇到的一些问题,并解释和演示部分问题的代码示例。最后,我们将展示目前在 Tubi 生产环境中的解决方案。与所有工程一样,我们可接受的权衡不一定适用于你,仅供参考。

动机

除了 Elixir 和 Scala 之外,我们在 Tubi 的一些服务还在使用 Node。Node 是一个很好的平台,可以快速启动和运行,在开发 Backends For Frontends(服务于前端的后端)时也很有意义。然而,它并非没有问题,其中一些是 Javascript 固有的,另一些则是由于 Node 本身。为了解决这些问题,我们在 node-restify 之上编写了我们自己的微框架,更多的是最佳实践的集合。这个框架的代号是 UAPI,Unified API 的简称,它为 Tubi 工程师提供了构建 API 的标准方法。典型的 API 将遵循类似于下面的模式:

如你所见,我们通过函数参数传播所需的必要信息。在上面的示例中,调用堆栈将如下所示:

通过精心的规划、设计和审慎的代码审查,上述模式为我们工作了很长时间。然而,在过去的一年半时间里,我们迷上了来自 Lyft 的边车代理(Sidecar Proxy)Envoy。我们一直在围绕 Envoy 和 gRPC 标准化我们的基础设施。为了尽可能地遵循 DRY 来开发,我们在 Node,Scala 和 Elixir 中实现了 RPC 库。

我们的 RPC 库使用 Envoy 轻松进行网络调用。它们增加了以下功能:重试、短路开断、回退、缓存、跟踪等。然而,我们采用 Envoy for Node 的最大痛点之一就是在调用堆栈中传播跟踪 Headers(我们使用 Jaeger)。只有这样,无论网络调用发生在何处,都会设置正确的 Headers 并正确跟踪请求。其他语言具有解决这个确切问题的机制,即线程本地存储。JVM 的 gRPC 实现甚至还带有一个内置的 Context 对象以简化类似需求。Node 在 v8 之前有一个 Continuation-Local Storage 的库,它本来是非常有用的,但它不能与 async/await 很好地工作,而这是我们从 Node v4 就一直渴望使用的功能!

async_hooks API

幸运的是,Node v8 发布了一个名为 async_hooks 的新实验 API。这一 API 非常有用且通用,因为它提供了相当多的信息。 简而言之,它包含如下钩子(回调函数):

鉴于我们现在通过 asyncId 和 triggerAsyncId 可以获取每次执行之间的依赖关系,我们可以构建一个表示应用程序执行方式的树,并且我们可以将执行的所有函数与触发它们的请求关联起来。例如,用于处理典型 API 调用的调用堆栈可能如下图所示:

关键要求是能够唯一地分离由每个路由(req,res)创建的堆栈。如果你熟悉你所使用的框架,那么一种简单的方法就是猴子补丁(Monkey Patching)。下面是修补简单 HTTP 服务器的示例:

在我们的应用程序代码中,我们的实现如下(为简洁起见做了适当简化):

这种方法易于实现,并且具有不必担心清理上下文的额外好处。 在完成请求/响应周期的执行时,它将被销毁。在缺点方面,此方法与你使用的任何框架的实现紧密相关。 例如,在我们的例子中,我们无法找到一种方法来修补 node-grpc 库以使用 gRPC 调用。

在后续文章中,我们将以更好的方式查看更详细的解决此问题的方法,而不需要猴子补丁,这可以推广到从 Express 到 node-grpc 的任何服务器实现。我们还将讨论如何解决异步钩子生命周期中存在Keep-Alive连接时不能正常运行的问题,以及我们如何在这些情况下管理内存和垃圾收集。

备注:如果新的 HTTP 请求到达 Node HTTPServer,它将创建一个新的异步执行资源,因此我们不必实现处理共享相同异步 id 的不同请求的逻辑。

关于作者

程颖宇,Tubi 中国团队高级后端工程师。毕业后曾在微软工作三年,负责必应 Ad Insights API 和 Middle Tier 平台的研发工作。16 年加入刚刚组建的 Tubi 中国团队,先后负责 Adbreak Finder、Clip Pipeline 等多个重点项目,现在主要从事微服务基础设施以及 Ad Server 的研发工作。

Scala Meetup 第八期火热报名中,欢迎参与:抢位|Scala Meetup 线上回归! 预约报名-Tubi TV活动-活动行

猜你喜欢

转载自blog.csdn.net/weixin_49193714/article/details/128410395