幕后揭秘:OpenTelemetry 插件如何实时映射微服务架构
我们都经历过这种情况:你加入一个新项目,第一件事就是索要架构图。你拿到一张看起来不错的图,但调试一周后才发现,它已经过时六个月了。服务 A 从春天起就没再和服务 B 通信过,而且还有一个没人费心记录的新消息队列。
弄清楚一个复杂系统实际上是如何组合在一起的,是经典的工程难题。你可以尝试手动弄清楚(如果你对自己有信心,而且有足够的时间),也可以使用静态分析来探索代码库(但静态分析往往无法捕捉服务在运行时实际是如何连接在一起的)。
但还有第三种方式:动态分析。如果我们能直接观察系统运行,并根据实际发生的情况绘制地图,会怎么样?
由于 OpenTelemetry 插件已经在收集大量运行时数据——日志、指标和追踪——我们意识到,这是一个为你自动生成这张架构图的绝佳机会。下面就来深入了解一下 Service Map(服务地图)功能的内幕,它是 Rider 执行团队与软件工程研究合作构建的。
神奇的要素:追踪
如果你熟悉可观测性,就会知道“三大支柱”:日志、指标和追踪。
日志告诉你发生了什么,指标告诉你发生了多少,而追踪则向你展示请求在系统中经历的旅程。追踪由称为 Span 的独立工作单元组成。
由于 OpenTelemetry 标准化了这些 Span(例如,明确定义了 HTTP 客户端 和 HTTP 服务端 Span),它们就成了理解系统架构的终极作弊码。依赖 OpenTelemetry 标准意味着,只要你的应用和库按照 OTel 期望的方式发出 Span,插件就能完全独立于你的技术栈来可视化你的系统。
基于追踪构建地图有一个巨大优势:它是运行时真实情况的来源。我们不是根据源代码或过时的规格来猜测。我们看的是由实时系统生成的数据。
工作原理
那么,这在你的 JetBrains IDE 中实际上是如何工作的?
[LOADING...]
当你启用 OpenTelemetry 插件启动 IDE 时,插件会启动一个轻量级的本地 OpenTelemetry 后端,用于处理应用程序的遥测数据。
当你在 IDE 中点击 Run 时:
- 插件会向应用程序提供标准的 OTel 环境变量,让它知道数据应发送到本地后端。
- 你的应用(已经配置为发出 Span)开始向我们的本地后端发送遥测数据。
- 后端会异步处理这些传入的 Span,持续构建并更新你的架构内部模型。
- 当你点击 Service Map 标签页时,OpenTelemetry 插件会从后端获取最新的结构模型,并渲染可视化图表。
遥测数据的混乱现实
如果你看着一张架构图,它看起来静态而有序。但生成这张图的遥测数据流绝非如此。在编写算法把这些点连接起来之前,我们必须解决几个隐藏挑战:
传输过程中的混乱
Span 完全独立地到达,顺序永远无法保证。父 Span 可能已经完成,却在其子 Span 已被处理之后才到达。
没有终点线
一条追踪永远不会明确地说“我完成了”。在任何时刻,我们都不能 100% 确定不会再有迟到的 Span 出现。
无类型负载
OpenTelemetry 并未为每种 Span 类型提供严格类型的版本。相反,每个 Span 都携带一个键值映射,其中的属性描述了操作的语义。我们必须完全通过检查这些属性来推断它们代表哪种交互。
重建算法
为了处理这种异步、乱序的数据,我们将架构重建构建为一种流处理算法。我们不会等待一条完整的追踪——正如刚才所说,这无法保证——而是在每个 Span 到达的瞬间就进行处理。
首先,我们要弄清楚自己看到的是什么。我们从 Span 中提取基本元数据,然后检查其语义属性来进行分类:诸如 http.request.method 和 http.response.status_code 这样的属性表明它是一次 HTTP 调用,而其他属性则指向数据库查询、消息队列交互等。
接着我们判断是哪个服务发出了它。如果是我们没见过的新服务?就把它放到地图上。如果已经存在?我们就把新数据合并进去,并更新其统计信息。
然后是有趣的部分:跨越服务边界把点连接起来。一次完整插桩的 HTTP 调用有两个侧面:调用方服务发出 CLIENT Span,而接收方服务发出 SERVER Span。追踪上下文随请求一起传递,因此下游的 SERVER Span 会作为上游 CLIENT Span 的子 Span 创建。
因此,当一个出站的 HTTP Client Span 出现时,我们会去寻找它在服务端的子 Span。当一个入站的 HTTP Server Span 出现时,我们会寻找调用它的父 Span。如果配对的 Span 已经在系统中,我们就立即绘制(或更新)两个服务之间的连接。如果还不在,就把该 Span 暂存在内存中,等待它的另一半到达。
其他类型的依赖需要稍微不同的规则。数据库调用通常由单个 CLIENT Span 表示,因此我们直接从其语义属性推断数据库节点。消息传递则更加多样:生产者和消费者操作可能通过父子关系或 Span 链接相连,具体取决于消息系统和插桩方式。无论哪种情况,后端都会在 Span 到达时进行处理,并随着更多证据出现而逐步丰富地图。
最后这一步让插件能够构建准确、实时的地图,即使网络让所有数据迟到且乱序到达。
[LOADING...] 服务地图展示跨服务 HTTP 通信和数据库访问。
通过这种方式,我们可以处理并向你展示有关 HTTP 请求、数据库请求和消息队列的信息。
[LOADING...] 服务地图展示通过消息队列(rabbit)和数据库访问进行的跨服务通信。
由于地图是基于标准 OpenTelemetry Span 构建的,并且重建算法依赖语义约定而非特定框架 API,因此该功能与语言和供应商无关。只要插桩按预期发出 Span 并正确传播上下文,同一套逻辑就能适用于 JVM、.NET、Python、Go 以及其他经过 OpenTelemetry 插桩的应用程序。这也意味着你可以在最适合自己技术栈的 JetBrains IDE 中使用该功能,包括 IntelliJ IDEA、GoLand、PyCharm、WebStorm 和 Rider。
查看你自己的架构
想要实时看到你自己的架构被绘制出来吗?本以为只有一次 HTTP 调用或数据库查询,图中却显示了好几次?在开发期间发现这一点,你就能在发布前有时间修复它。
你现在就可以安装 OpenTelemetry 插件,不再猜测你的服务之间如何通信。
下载 OpenTelemetry 插件