大型网站重构指南 第1.2部分:Nodejs 系统可观测性 OpenTelemetry+SigNoz

677 阅读6分钟

在上一篇文章中我们介绍了 SonarQube,它是一种代码静态质量检查的工具。

但有时候一些问题在静态检查期间并不能完全暴露出来,这些问题会带到线上,在用户使用软件的时候,出现问题。

这时候就需要针对程序的运行时进行检测,这项能力也就是可观测行。也就是本篇文章要做的事。

OpenTelemetry 介绍

OpenTelemetry 是系统可观测行框架这个领域的佼佼者。

它是一个完全开源的库,它提供了一组 API/SDK 来帮助我们对程序进行监控、日志和追踪。而这三者就是目前可观测性的三大核心。

在可观测性这个领域,它几乎是唯一的事实标准。

OpenTelemetry 最初只是针对复杂的后端应用进行设计的,但现在同时也支持前端。不过 OpenTelemetry 在前端方面并不是最优的选择。

所以 ichati.cn 只在后端使用了 OpenTelemetry。前端可以选择一些更合适的工具,比如 Sentry。

OpenTelemetry 支持 C++、Java、JS、Go、Python、Rust 等 11 种主流语言。同时还可以轻松地和很多主流框架进行集成。比如 Java 的 Spring、Nodejs 的 Express、Nestjs、ASP.NET Core 等。

它的原理我就不讲了,稍微会复杂一些,通过进程间通信和上下文传播来完成的。我们主要关注如何利用 OpenTelemetry 来达到观测 ichati.cn 的目的。

OpenTelemetry 与 Nestjs 集成

ichati.cn 的后端使用 Nestjs 开发的,所以我们先将 OpenTelemetry 和项目进行集成。

这里你必须有一个 Nodejs 的项目,可以不是 Nestjs 框架的项目,比如 Express 也可以。

然后安装 OpenTelemetry 的依赖。

npm install @opentelemetry/sdk-node \
  @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/sdk-metrics

接下来创建一个 instrumentation.ts 文件。

import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { PeriodicExportingMetricReader, ConsoleMetricExporter } from '@opentelemetry/sdk-metrics';

const sdk = new NodeSDK({
  traceExporter: new ConsoleSpanExporter(),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new ConsoleMetricExporter()
  }),
  instrumentations: [getNodeAutoInstrumentations()]
});

sdk
  .start()

然后在项目的 HTTP 服务启动之前导入这个文件。通常会是 main.ts。

重新启动项目。随着你的接口被调用,你会发现控制台会有很多类似的输出:

{
  traceId: 'd4b107c7971580478eef859a2fd979aa',
  parentId: undefined,
  traceState: undefined,
  name: 'GET /user',
  id: '573d0fb86048fc11',
  kind: 1,
  timestamp: 1685629667330000,
  duration: 345450,
  attributes: {
    'http.url': 'http://127.0.0.1:4401/user',
    'http.host': '127.0.0.1:4401',
    'net.host.name': '127.0.0.1',
    'http.method': 'GET',
    'http.scheme': 'http',
    'http.target': '/user',
    'http.user_agent': 'insomnia/2023.1.0',
    'http.request_content_length_uncompressed': 0,
    'http.flavor': '1.1',
    'net.transport': 'ip_tcp',
    'net.host.ip': '::ffff:127.0.0.1',
    'net.host.port': 4401,
    'net.peer.ip': '::ffff:127.0.0.1',
    'net.peer.port': 53063,
    'http.status_code': 200,
    'http.status_text': 'OK',
    'http.route': '/user'
  },
  status: { code: 0 },
  events: [],
  links: []
}

不仅仅是 HTTP 的入站/出站会被检测,几乎其他所有的行为都会被检测,包括TLS 的连接/断开、文件系统的调用、中间件的调用、定时任务等等。

我们仅仅是把这些数据输出到控制台中显然是不够的,因为纯文本的内容不利于数据的分析和可视化。

我们还需要有一个平台来存储、查询、分析这些日志。

SigNoz 介绍

OpenTelemetry 中有一个 Exporters 的概念,其实就是一个提供存储日志数据和搜索的系统。

这类系统的开源项目有很多,比如 Jaeger、SkyWalking、Zipkin、Haystack、SigNoz 和 Grafana 等。

它们中的大多数最初都支持系统检测功能,但是后面都演变变成了纯粹的存储和可视化系统。把检测功能让给了 OpenTelemetry,让专业的人做专业事。

这里 ichati.cn 选择了 SigNoz。

SigNoz 不是里面最好的,也不是最成熟的。选择它的理由是,它可视化功能非常强大。我认为这类系统最重要的功能就是数据可视化和查询。

安装并启动 SigNoz

安装 SigNoz 很简单,但是过程可能比较慢。

第一步,首先电脑上安装 Docker。SigNoz 只支持 Docker 一种方式部署。

第二步,运行以下三行命令。

git clone -b main https://github.com/SigNoz/signoz.git
cd signoz/deploy/
./install.sh

不出意外的话,就可以看到启动成功。

++++++++++++++++++ SUCCESS ++++++++++++++++++++++

  

🟢 Your installation is complete!

  

🟢 Your frontend is running on http://localhost:3301

然后浏览器访问 http://localhost:3301 就可以看到 Dashboard 了。

现在的几个项目都是 SigNoz 默认的演示项目。

Pasted image 20230602002007.png

将 OpenTelemetry 的数据发送到 SigNoz

我们之前的代码实现是将 OpenTelemetry 的数据输出到控制台,现在我们要输出到 SigNoz。

要把 OpenTelemetry 的检测数据发送到 SigNoz,还需要安装三个包。

npm install @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions

然后修改代码。

import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
	traceExporter: new OTLPTraceExporter({
		url: 'http://localhost:4318/v1/traces',
	}),
	instrumentations: [getNodeAutoInstrumentations()],
	resource: new Resource({
		[SemanticResourceAttributes.SERVICE_NAME]: 'ichati-backend',
	}),

});

process.on('SIGTERM', () => {
	sdk
	.shutdown()
	.then(() => console.log('Tracing terminated'))
	.catch((error) => console.log('Error terminating tracing', error))
	.finally(() => process.exit(0));
});

export default sdk;

注意代码中的 4318 端口是 SigNoz 默认的端口。

最后在 main.ts 中导入 sdk 运行。

import instrumentation from './instrumentation';

instrumentation.start();

现在我们发送一些请求,就可以在 SigNoz 中查看到项目了。

Pasted image 20230602005221.png

SigNoz 云托管

实际上,像 ichati.cn 这类中小型团队,不想花太多时间在基础设施上面,所以运行了一段时间的 SigNoz 后,觉得管理服务器比较麻烦。

后面切换到了云服务商 elest 上面: elest.io

在 elest 上面,SigNoz 最便宜的套餐是每月 36 美元,2C、8G、80G 存储。我认为还算划算。虽然比自己买裸机要贵一点,但绝对比单独招个人来做运维划算得多。

数据的用途

以上步骤都只是一些繁琐无聊的项目搭建和配置,现在我们才正式进入主题。

我们应该关心应用的哪些数据呢?

第一是接口的延迟。

SigNoz 将接口延迟分为了三类:

  • P50,也就是中位数
  • P95,前 95% 的情况
  • p99,也就是前 99% 的情况。

这个数据指标可以反应系统的性能和稳定性。

第二是 Rate。单位是 ops/s,也就是系统每秒处理的请求数。

这个指标主要反应系统的性能。

Pasted image 20230602005338.png

第三是操作的错误率。

这个指标主要反映系统的稳定性。

Pasted image 20230602005947.png

我们还可以通过条件筛选来查看更详细的内容,SigNoz 支持的筛选项非常多,包括时段、操作名、HTTP 请求方法、请求 URL、请求来源、Trace ID 等。

Pasted image 20230602010310.png

如果你想查看某些特殊的接口,我们还可以查看它的火焰图和调用堆栈,比较有利于排查问题。

Pasted image 20230602010701.png

以上就是常见的数据功能。

SigNoz 的强大还不止于此,它还有一些其他功能,比如自定义 Dashboard、定义 Alert、服务地图等等。

这样当系统出现故障时可以方便的通过某行方式通知到对应的人。比如邮箱、飞书、微信等等。

但目前 ichati.cn 还用不到这么多功能。所以就不对这些功能做更深入地介绍了。

篇幅所限,本文暂时结束。

后面会持续更新,让我们一起期待《大型网站重构指南》的下一篇。

如果你对最新的技术感兴趣,特别是对 Web3、AI 相关的内容感兴趣,可以添加我的微信 LZQ20130415,拉你进群交流。