alinode官方地址:https://help.aliyun.com/product/60298.html?spm=a2c4g.60418.0.0.710343961QetaC

介绍

阿里官方提供的nodejs性能平台面向所有 Node.js 应用,提供 性能监控、安全提醒、故障排查、性能优化 等服务的整体性解决方案,尤其适合业务发展迅速、应用发布频繁、流量上升明显的 Node.js 应用。

Node.js 性能平台特别适合业务发展迅速、应用发布频繁、流量上升明显的 Node.js 应用。

平台全部功能免费使用

Node.js性能平台使用指南

https://github.com/aliyun-node/Node.js-Troubleshooting-Guide/blob/master/0x04_%E5%B7%A5%E5%85%B7%E7%AF%87_Node.js%20%E6%80%A7%E8%83%BD%E5%B9%B3%E5%8F%B0%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md

使用

创建应用

服务器部署 Node.js 性能平台

安装 Node.js 性能平台所需组件

版本管理工具 tnvm github地址https://github.com/aliyun-node/tnvm

安装参考:https://help.aliyun.com/document_detail/60338.html?spm=a2c4g.60418.0.0.52d61a2eAL3KiM#section-k3e-y74-zq9

问题记录:

  • 官方提供了多种平台安装方式,alpine/centos等,但我在Debian环境下测试遇到了问题

但我在Debian环境下测试遇到了问题

在Debian系统中,默认的shell是dash shell,而不是Bash shell。因此,如果在Debian系统中使用source命令而没有指定shell类型,它将默认使用dash shell,而不是Bash shell。这可能导致脚本无法正确执行或产生错误。因此,需要在Debian系统中使用source命令时指定Bash shell类型,即使用/bin/bash -c "source xxx"的方式。

在CentOS系统中,默认的shell是Bash shell,因此可以直接使用source命令来执行脚本文件。

  • 采集性能指标数据出现调用错误等问题

官方也没给出回答,但是目前发现在节点资源比较吃紧的时候,采集性能指标会出现问题

  • 服务器无法访问外网环境

可以将https://raw.githubusercontent.com/aliyun-node/tnvm/master/install.sh脚本文件下载至本地,然后使用git方式下载tnvm资源,修改脚本文件中的NVM_SOURCE_URL,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tnvm_source() {
local NVM_METHOD
NVM_METHOD="$1"
local NVM_SOURCE_URL
NVM_SOURCE_URL="$NVM_SOURCE"
if [ -z "$NVM_SOURCE_URL" ]; then
if [ "_$NVM_METHOD" = "_script" ]; then
# 修改git地址为国内网络环境允许的位置
NVM_SOURCE_URL="https://gitee.com/xiaowu_wang/tnvm/raw/master/tnvm.sh"
elif [ "_$NVM_METHOD" = "_git" ] || [ -z "$NVM_METHOD" ]; then
# 修改git地址为国内网络环境允许的位置
NVM_SOURCE_URL="https://gitee.com/xiaowu_wang/tnvm.git"
else
echo >&2 "Unexpected value \"$NVM_METHOD\" for \$NVM_METHOD"
return 1
fi
fi
echo "$NVM_SOURCE_URL"
}
  • agenthub未启动成功
  1. 确保启动时是配置了ENABLE_NODE_LOG=YES
  2. 使用agenthub list检查列表中服务是否成功启动
  3. 使用debug的方式启动agenthub DEBUG=* agenthub start yourconfig.json,检查~/.agenthub.log日志文件是否正常
  4. 确保which pm2which nope等which指令显示的路径,是包含了.tnvm
  5. 确保在平台上检查进程当中,这些都是打了勾的
  • 其他问题

参考:https://help.aliyun.com/document_detail/60418.html?spm=a2c4g.60418.0.0.16e51a2en0PAf1

alinode.config.json

配置中可以使用#YYYY#、#MM#、#DD#、#HH#这类通配符

数组字段都可以指定多个值

1
2
3
4
5
6
7
8
9
10
{
"appid": "xxx",
"secret": "xxx",
"error_log": [
"/var/log/app/error.#YYYY#-#MM#-#DD#.log"
],
"packages": [
"/user/src/app/package.json"
]
}

完整Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM centos:centos7.9.2009
WORKDIR /user/src/app/
RUN yum update -y \
&& yum install -y net-tools gcc gcc-c++ git which make \
&& ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# alinode和nodejs版本队列列表:https://help.aliyun.com/document_detail/60811.html?spm=a2c4g.60810.0.0.7fac3ef7ED4mmO
# METHOD=git指定下载方式为git
ENV ENABLE_NODE_LOG=YES \
ALINODE_VERSION=v5.20.6 \
METHOD=git
# 拷贝本地的install.sh脚本文件
COPY install.sh ./
RUN chmod +x ./install.sh \
&& ./install.sh \
&& source ~/.bashrc && tnvm install alinode-$ALINODE_VERSION \
&& tnvm use alinode-$ALINODE_VERSION
# pm2/pm2-runtime/agenthub等指令文件都会放在~/.tnvm/versions/alinode/$ALINODE_VERSION/bin下
ENV PATH="~/.tnvm/versions/alinode/$ALINODE_VERSION/bin:$PATH"
# 执行npm指令的时候默认是使用/usr/bin/node去执行的,但本地没有/usr/bin/node,所以为了解决报错`/usr/bin/env: node: No such file or directory`这里创建一个软链接
RUN ln -s ~/.tnvm/versions/alinode/$ALINODE_VERSION/bin/node /usr/bin/ \
&& ln -s ~/.tnvm/versions/alinode/$ALINODE_VERSION/bin/npm /usr/bin/ \
&& npm install @alicloud/agenthub pm2 -g --registry=https://registry.npm.taobao.org
COPY package.json package-lock.json ./
RUN npm install --registry=https://registry.npm.taobao.org
COPY . .
# 以下示例以pm2方式启动
ENTRYPOINT ["sh", "-c", "agenthub start alinode.config.json && pm2-runtime start pm2.json"]

docker build -f Dockerfile -t ${REGISTRY_ADDRESS}/${IMAGE} .

K8S环境下的处理

为了使平台能有充足的时间去进行一些profile分析或者堆栈快照处理,可以将livenessProbe的失败时间或者次数设置的大一些,或者完全将将健康检查完全关闭,当问题找到后再还原

处理多个副本

官方未直接给出k8s环境下的使用样例,但针对一台服务器多实例给的建议是创建不同的应用:https://help.aliyun.com/document_detail/60418.html?spm=a2c4g.60317.0.0.7ecb4fa7oVzKAn#h2-u5982u4F55u5904u7406u4E00u53F0u670Du52A1u5668u90E8u7F72u591Au4E2Au5E94u75287

单个副本的情况

因为平台是将pod的hostname作为实例名称的,而k8s默认为pod名附上一个随机后缀,并将其作为hostname

但如果针对一个副本,我们完全可以为其指定hostname(同时为了防止有其他地方使用到hostname,我们将podname赋值给环境变量POD_NAME),这样就避免了实例过多问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment
metadata:
......
spec:
selector:
......
template:
metadata:
......
spec:
hostname: app
terminationGracePeriodSeconds: 60
containers:
- image: $REGISTRY_ADDRESS/${NODE_ENV}/${CI_PROJECT_NAME}:v${CI_PIPELINE_ID}
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name

Trace 链路追踪

使用 @alicloud/opentracing 埋点

安装

1
npm install @alicloud/opentracing

使用

公共类:Tracer(name[, option][, reporter])

参数:

  • name String - tracer 的名称
  • option Object - 可选
    • limit Number - 每分钟限制记录落盘的数据条数限制,防止大量异常的情况下大量日志写入文件造成磁盘溢出
    • logger Object - 日志句柄,最小需要实现 info、log、warn 和 error 方法,默认采用 console
  • reporter Object - 自定义发送方法,需要实现 report 方法,入参为 span

成员方法:startSpan(spanName[, option])

  • spanName String - span 的名称,用来标记此 span 下的异步调用
  • option Object - 可选
    • childOf Object - 传入当前 span 的父级 span 实例
  • 返回值 Object - 返回内置的类 Span 的实例
内置类:Span

成员方法:setTag(tag, value)

  • tag String - Tag 名称,可以自定义,一般从 opentracing.Tags 中获取(里面定义了常见的 host、url、statusCode 等链路信息 Key)
  • value String - Tag 名称对应的值

成员方法:log(key, value)

  • key String - 自定义的日志键
  • value String - 自定义的日志值

成员方法:finish(req)

  • req Object - http 请求的 request 对象

koa代码整合样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import opentracing from '@alicloud/opentracing';
import Koa from 'koa';

const tracer = new opentracing.Tracer('测试模块名称');
const app = new Koa();

// 模拟耗时的异步操作
function delay(time, req) {
const child = tracer.startSpan('子模块 1: 随机并发延迟', {childOf: req.parentSpan});
child.setTag('timeout', time);
child.log({state: 'timer1'});
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
child.finish(req);
}, time);
});
}

// 在所有中间件之前,开启一个根 span,记录下 hostname、method、url,以及收到 end 事件后的
app.use(async (ctx, next) => {
ctx.req.parentSpan = tracer.startSpan('根模块');
ctx.req.parentSpan.setTag(opentracing.Tags.PEER_HOSTNAME, ctx.req.socket ? ctx.req.socket.remoteAddress : 'NONE');
ctx.req.parentSpan.setTag(opentracing.Tags.HTTP_METHOD, ctx.req.method.toUpperCase());
ctx.req.parentSpan.setTag(opentracing.Tags.HTTP_URL, ctx.req.url);
await next();
ctx.req.parentSpan.setTag(opentracing.Tags.HTTP_STATUS_CODE, ctx.res.statusCode);
ctx.req.parentSpan.finish(ctx.req);
});


// 模拟并发的耗时异步操作
app.use(async (ctx, next) => {
Promise.all([
delay(Math.random() * 10 * 1000, ctx.req),
delay(Math.random() * 10 * 1000, ctx.req),
]).then(() => next());
});

// 继续模拟一个顺序的 3s 耗时异步操作
app.use(async (ctx, next) => {
const child = tracer.startSpan('子模块 2: 延迟 3s', {childOf: ctx.req.parentSpan});
child.setTag('timeout', '3s');
child.log({state: 'timer2'});
// 3s call
setTimeout(() => {
child.finish(ctx.req);
next();
}, 3000);
});

// error handler
app.on('error', async (err, ctx) => {
ctx.status = 500;
ctx.log.error('×××××× System error:', err.stack);
});

// listening
const port = Number(process.env.PORT);
app.listen(port, '0.0.0.0')
.on('listening', () => {
console.log(`Listening on port: ${port}`);
});

在浏览器请求 http://localhost:3000/delay, 等待约 1min 后,可以在控制台的相应 Tab 页看到:

点击 请求信息 栏下面对应的字符串或者长条可以看到开发者自行记录的当前请求详情,比如例子中在随机延迟中分别用 tag 和 log 记录了当前延迟 ms 数和定时器名称,那么点开 子模块1 对应的请求信息栏中的长条后可以看到如下内容:

关于Node.js 性能平台运行时是否会影响性能

https://help.aliyun.com/document_detail/60418.html?spm=a2c4g.60317.0.0.66893e06A9w1Ux#h2-node-js-node-js-1

来自官方回答

  • Node.js 性能平台运行时每分钟在主线程将监控数据写到内存中,通过额外的日志线程写日志到文件,因此对性能影响可以忽略。
  • 做故障诊断时,执行诊断功能 3 分钟,随后自动切回到正常运行状态。