最近需要对NodeJS中的调用进行链路跟踪,所以需要使用Jaeger进行跟踪,这里我们选用的是阿里云ARMS中的Jaeger链路跟踪,但是在Golang项目中使用没问题的链路跟踪,却在NodeJS踩了个大坑。
什么是Jaeger
Jaeger 是用于追踪分布式服务之间事务的开源软件。它用来监控复杂的微服务环境并对其进行故障排除。它实现了OpenTracing API,非常好用,同时它也是CNCF毕业的项目,使用Go语言编写。类似的开源软件还有skywalking等。
接入Jaeger
在Golang语言中接入Jaeger非常简单,仅需几行代码就可以搞定:
1
2
3
4
5
6
7
8
9
10
|
// SDK上报需要:设置链路追踪的网关(不同region对应不同的值,从http://tracing.console.aliyun.com/ 的配置查看中获取)
const TracingAnalysisEndpoint = "http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxxxx_xxxxx/api/traces"
sender := transport.NewHTTPTransport(
TracingAnalysisEndpoint,
)
tracer, _ := jaeger.NewTracer(service,
jaeger.NewConstSampler(true),
jaeger.NewRemoteReporter(sender, jaeger.ReporterOptions.Logger(jaeger.StdLogger)),
)
|
这里的TracingAnalysisEndpoint 是链路跟踪的接入点,我这里是以阿里云的为例,你也可以换成自己的。
service
表示微服务的名称,同时我这里使用ConstSampler固定采样率
有了 tracer
后就可以使用它来创建自己的span进行链路跟踪了:
1
2
3
4
5
6
7
8
|
// 创建Span。
span := tracer.StartSpan("myspan")
// 设置Tag。
clientSpan.SetTag("mytag", "123")
// 透传traceId。
tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
...
defer span.Finish()
|
这里不再对链路跟踪展开讲,感兴趣的可以参考 OpenTracing API。
远程采样策略配置
在上面的示例中,我们使用的是固定的采样率,但是这种方式很不灵活,粒度也比较粗,针对这样情况,jaeger支持远程采样策略配置,其实就是在服务端配置后,客户端拉取使用,这样方式可以让服务端控制客户端的采样率,同时也可以根据每个span定制不同的采样率,所以非常方便。
在Go语言中,使用远程采样策略配置同样简单,客户端把上面示例中的 jaeger.NewConstSampler
换成 jaeger.NewRemotelyControlledSampler
即可。
1
2
3
4
5
6
7
8
9
|
const samplingServerURL = "http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxxxx_xxxxx/api/sampling"
options := []jaeger.SamplerOption{
jaeger.SamplerOptions.Metrics(metrics),
jaeger.SamplerOptions.SamplingServerURL(samplingServerURL),
}
tracer, _ := jaeger.NewTracer(service,
jaeger.NewRemotelyControlledSampler(service,options...),
jaeger.NewRemoteReporter(sender, jaeger.ReporterOptions.Logger(jaeger.StdLogger)),
)
|
以上就配置好了一个使用远程采样策略的tracer,它每隔一段时间会自动更新远程配置,默认是1分钟。
NodeJS 中的坑
但是这在NodeJS中就不可行了,因为在NodeJS中配置远程采样并不是一个完整的URL,而是被分为了host、port等好几部分,就是这个拆分引起了巨坑。
1
2
3
4
5
6
7
8
9
10
11
12
|
tracerConfig: {
serviceName: 'demo',
sampler: {
type: 'remote',
param: 1,
host: '<host>',
port: '<port>'
},
reporter: {
collectorEndpoint: "<endpoint>"
}
}
|
以上就是一个远程采样的配置,需要把里面的 host
、 port
换成自己的,比如阿里云的给的这样的采样配置URL:
1
|
http://tracing-analysis-dc-hz.aliyuncs.com/adapt_xxxxxx_xxxxx/api/sampling
|
我们拆解出来host和port就是:
- host:tracing-analysis-dc-hz.aliyuncs.com
- port:80
用这两个值可以替换以上的配置,但是配置完之后使用,会发现不行,不行的原因是404,为什么是404?这个也是我读了它的源代码才知道的。
因为它最终拼接出来的url是:‘http’+host+port+’/sampling’,比如上面阿里云的,拼接出来的URL就是 http://tracing-analysis-dc-hz.aliyuncs.com/sampling
,和阿里云给我们的完全不一样,所以就会提示404,没这个URL。
导致这个坑的原因是NodeJS SDK 发现没有 samplingPath
,就使用了默认的 /sampling
,所以拼接出来的URL,无法拿到远程采样配置,但是NodeJS Tracer Config又没有提供samplingPath
的配置,陷入僵局。
出现这个问题的原因,我认为有两种:
- Jaeger NodeJS SDK 比较古板,对OpenTracing API 规范执行的太严格,只有path是
samplingPath
是 /sampling
才能使用。
- 阿里云没有完全按照OpenTracing API规范来定义自己的Server config URL,在
samplingPath
中添加了多余的授权信息。
那么如何做更好的,其实我更愿意对SDK做一些改变,让它灵活一些,比如就像Golang一样,直接用给的URL就可以了,没必要拆分为host、port让填写,反而搞出来坑。
如何解决
既然NodeJS SDK搞出来这么一个坑,那么如何解决呢?我把目光转移到了 initTracer
函数上,这个函数除了 config
这个参数外,还有一个 options
配置的参数,并且还用于创建sampler。
1
2
3
4
5
6
7
8
9
10
11
|
static initTracer(config, options = {}) {
let sampler;
if (!config.serviceName) {
throw new Error(`config.serviceName must be provided`);
}
if (config.sampler) {
sampler = Configuration._getSampler(config, options);
} else {
sampler = new RemoteSampler(config.serviceName, options);
}
}
|
从以上NodeJS SDK的源代码可以看到,如果我们自己在 config
配置中不定义 sampler
这个配置,它会new一个 RemoteSampler
,也就是我们正需要的远程采样,并且把 options
当做参数传给了 RemoteSampler
,感觉有戏,继续去看RemoteSampler
的构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* Creates a sampler remotely controlled by jaeger-agent.
*
* @param {string} [serviceName] - name of the current service / application, same as given to Tracer
* @param {object} [options] - optional settings
* @param {object} [options.sampler] - initial sampler to use prior to retrieving strategies from Agent
* @param {object} [options.logger] - optional logger, see _flow/logger.js
* @param {object} [options.metrics] - instance of Metrics object
* @param {number} [options.refreshInterval] - interval in milliseconds before sampling strategy refreshes (0 to not refresh)
* @param {string} [options.hostPort] - host and port for jaeger-agent, defaults to 'localhost:5778'
* @param {string} [options.host] - host for jaeger-agent, defaults to 'localhost'
* @param {number} [options.port] - port for jaeger-agent for SamplingManager endpoint
* @param {string} [options.samplingPath] - path on jaeger-agent for SamplingManager endpoint
* @param {number} [options.maxOperations] - max number of operations to track in PerOperationSampler
* @param {function} [options.onSamplerUpdate]
*/
constructor(serviceName: string, options: any = {}) {
}
|
哈哈,配置全的一比,host、port、samplingPath都有,这样我们就可以自定义samplingPath了,所以解决方案也有了,不再使用tracer config配置,使用构造好的options即可。
1
2
3
4
5
6
7
8
|
const options = {
logger: console,
host: 'tracing-analysis-dc-hz.aliyuncs.com',
port: 80,
samplingPath: '/adapt_xxxxxx_xxxxx/api/sampling'
}
options.sampler = new ProbabilisticSampler(1)
const tracer = initTracer(config, options)
|
一个URL由三部分组成,我们把缺少的 samplingPath
这部分配置上就可以了。
小结
这次踩坑可以总结出来几个不错的经验:
- SDK在满足规范API的基础上要稍微灵活点,不要太严格
- 服务提供商方比如阿里云,要尽可能遵守规范
- 我们自己不要放弃,要找,总会有解决办法的。
好了,再给jaeger提交个PR,修复这个问题。
本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org
或者网站 https://www.flysnow.org/ ,第一时间看后续精彩文章。觉得好的话,请猛击文章右下角「在看」,感谢支持。