使用 NIO 或事件驱动框架(如 Netty)时如何跟踪请求过程

How to trace request process when using NIO or event-driven framework like Netty

我们是 运行 一个 Web 应用程序,它本身由多个微服务组成,对于每个请求,我们最终需要调用第 3 方服务,这很耗时,通常需要几秒钟。我们有一个需求,需要跟踪每个请求的所有服务中的所有处理日志,使用 request traceId.

在当前的实现中,我们使用的是基于线程的并发模型,在每个服务中分配一个线程从头到尾处理一个请求,并在等待远程服务响应时阻塞。很自然的把traceId放到ThreadLocal这样我们就可以取回来whenever/wherever我们需要它

但是基于线程的并发模型扩展性不好,我们倾向于改用NIO/Event-driven模型并尝试Netty,性能提升非常大。但是每个请求处理的不同阶段可能由 Netty 的不同线程处理,使得日志的跟踪非常棘手。

我们目前的考虑包括:

那么 sophisticated/elegant 解决 NIO/Event-driven 模型中此类跟踪问题的方法是什么?

我的 2 美分:如果你在 NIO/Event-driven 模型中,那么你可能必须将 "request id" 从调用者传递给被调用者,然后返回给调用者(async/even-driven 方法)。这与线程或频道 ID 无关(一个频道可以重复用于各种查询,这样你就不会一次又一次地支付 "connect")。

然后在调用方,您可以使用地图左右(甚至是通过任何持久化工具实现的实体化地图)来恢复上下文并执行您需要执行的操作。

这是所有那些试图适应异步世界的 Java EE 框架的致命弱点(以及为什么现有框架永远不会真正适应异步世界)- 在 ThreadLocals 中存储状态数十年。

基本上,您需要将要传递的状态与您正在处理的通道或请求相关联,以便它可用于接下来获取它的任何代码 - 并且您不能假设这会发生在同一个线程上.

两种解决方法:

  1. Channel.attr() - 如果状态可以绑定到连接,一次只用于一件事,然后创建一个静态 AttributeKey 并将其传递给 Channel.attr() - 你会得到一个最初为 null 的属性 - 在你的第一个处理程序中,将它分配给某个东西,之后的所有东西都可以将它从那里拉出来(如果连接是,请确保在你知道你完成时清除它无需关闭即可重用,如 HTTP keep-alive 连接)。
  2. 将它附加到您解码的某个对象 - 将解码器子类化为 HTTP 请求(如果您正在做的是 HTTP)并创建您自己的带有 ID 的子类。

对这些东西使用 ThreadLocals 似乎很自然,因为我们的行业花了一两年的时间才以与计算机实际执行的操作无关的方式制作程序模型 I/O - 尽管这卖了很多硬件(异步更像是我在 1983 年左右编写的中断处理程序):-)