本文介紹Node.js運(yùn)行環(huán)境的鏈路追蹤相關(guān)內(nèi)容。

背景信息

阿里云鏈路追蹤服務(wù)(Tracing Analysis)基于OpenTracing標(biāo)準(zhǔn),兼容開源社區(qū),為分布式應(yīng)用的開發(fā)者提供了完整地分布式調(diào)用鏈查詢和診斷、分布式拓?fù)鋭?dòng)態(tài)發(fā)現(xiàn)、應(yīng)用性能實(shí)時(shí)匯總等功能。

函數(shù)計(jì)算與鏈路追蹤集成后,支持使用Jaeger SDKOpenTelemetry上傳鏈路信息,使您能夠跟蹤函數(shù)的執(zhí)行,幫助您快速分析和診斷Serverless架構(gòu)下的性能瓶頸,提高Serverless場景的開發(fā)診斷效率。

功能簡介

您可以在函數(shù)計(jì)算控制臺(tái)配置鏈路追蹤。具體操作,請參見配置鏈路追蹤。

為服務(wù)開啟鏈路追蹤后,函數(shù)計(jì)算會(huì)自動(dòng)記錄請求在系統(tǒng)側(cè)的耗時(shí),包含冷啟動(dòng)耗時(shí)、Initializer函數(shù)的耗時(shí)和函數(shù)的執(zhí)行時(shí)間等。關(guān)于下圖中系統(tǒng)Span的說明,請參見Span名稱說明。鏈路追蹤

如您還需查看函數(shù)內(nèi)業(yè)務(wù)側(cè)的耗時(shí),例如,在函數(shù)內(nèi)訪問RDS,NAS等服務(wù)的耗時(shí),可以通過創(chuàng)建自定義Span來實(shí)現(xiàn)。

示例代碼

函數(shù)計(jì)算的鏈路分析基于OpenTracing協(xié)議的Jaeger實(shí)現(xiàn),Node.js運(yùn)行時(shí)提供以下兩種方式自定義Span。

使用OpenTelemetry(推薦)

在Node.js語言的代碼中,您可以通過OpenTelemetry SDK手動(dòng)埋點(diǎn)將數(shù)據(jù)上報(bào)到鏈路追蹤服務(wù)端。完整的示例代碼,請參見nodejs-tracing-openTelemetry

示例代碼解析如下。
  • 在工程目錄中配置依賴文件package.json。
      "dependencies": {
        "@opentelemetry/api": "^1.0.2",
        "@opentelemetry/exporter-jaeger": "0.25.0",
        "@opentelemetry/exporter-zipkin": "0.25.0",
        "@opentelemetry/instrumentation": "0.25.0",
        "@opentelemetry/instrumentation-http": "0.25.0",
        "@opentelemetry/resources": "0.25.0",
        "@opentelemetry/semantic-conventions": "0.25.0",
        "@opentelemetry/sdk-trace-node": "0.25.0",
        "@opentelemetry/sdk-trace-base": "0.25.0"
      }
  • 上報(bào)數(shù)據(jù)到鏈路追蹤服務(wù)端。
    module.exports.handler = function(event, context, callback)
    {
        tracer = require('./tracer')('fc-tracer',context.tracing.jaegerEndpoint);
    
        var spanContext = contextFromString( context.tracing.openTracingSpanContext);
    
        startMySpan(spanContext);
    
        callback(null,'success');
    }
  • 創(chuàng)建一個(gè)tracer對象,用于創(chuàng)建Span。
    module.exports = (serviceName,endpoint) => {
      const provider = new NodeTracerProvider({
        resource: new Resource({
          [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
        }),
      });
    
      let exporter = new JaegerExporter({endpoint:endpoint});
    
      provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
      provider.register();
    
      registerInstrumentations({
        instrumentations: [
          new HttpInstrumentation(),
        ],
      });
    
      return opentelemetry.trace.getTracer('http-example');
    };
  • 獲取函數(shù)計(jì)算上下文的Tracing信息,轉(zhuǎn)換為SpanContext。
    function contextFromString(value){
      const arr = value.split(`:`);
      const spanContext={
        traceId:`0000000000000000`+arr[0],
        spanId:arr[1],
        traceFlags:api.TraceFlags.SAMPLED,
        isRemote:true
      }
      return spanContext;
    }
  • 創(chuàng)建tracer并通過轉(zhuǎn)換的Context創(chuàng)建子Span。每一個(gè)Span代表調(diào)用鏈中被命名并計(jì)時(shí)的連續(xù)性執(zhí)行片段,您也可以基于該Span繼續(xù)創(chuàng)建子Span。
    function startMySpan(spanContext){
      var FcSpan=api.trace.wrapSpanContext(spanContext);
      var ctx = api.trace.setSpan(api.ROOT_CONTEXT,FcSpan);
      tracer.startActiveSpan("fc-operation",undefined,ctx,parentSpan => {
        parentSpan.setAttribute("version","fc-v1");
        sleep(150);
        child();
        parentSpan.end()
      })
    }
    
    function child(){
      tracer.startActiveSpan("fc-operation-child",span =>{
        sleep(100);
        span.addEvent("timeout");
        span.end();
      })
    }

使用Jaeger SDK

您可以通過Jaeger SDK埋點(diǎn),將數(shù)據(jù)上報(bào)到鏈路追蹤服務(wù)端。完整的示例代碼,請參見nodejs-tracing。

示例代碼解析如下。
  • 在工程目錄中配置依賴文件package.json。
    "dependencies": {
      "jaeger-client": "^3.19.0"
    }
  • 上報(bào)數(shù)據(jù)到鏈路追蹤服務(wù)端。
    module.exports.handler = function(event, context, callback)
    {
      tracer=newTracer(context);
    
      var invokeSpanContext = spanContext.fromString(context.tracing.openTracingSpanContext);
    
      startMySpan(invokeSpanContext);
    
      callback(null,'success')
    }
  • 根據(jù)上下文的Tracing信息,創(chuàng)建一個(gè)tracer對象。
    function newTracer(context){
      var config = {
        serviceName: 'fc-tracer',
        reporter: {
          // Provide the traces endpoint; this forces the client to connect directly to the Collector and send spans over HTTP
          collectorEndpoint: context.tracing.jaegerEndpoint,
          flushIntervalMs: 10,
        },
        sampler: {
          type: "const",
          param: 1
        },
      };
      var options = {
        tags: {
          'version': 'fc-v1',
        },
      };
      var tracer = initTracer(config, options);
      return tracer
    }
  • 轉(zhuǎn)換SpanContext對象并創(chuàng)建自定義Span,您也可以基于該Span繼續(xù)創(chuàng)建子Span。
    function startMySpan(spanContext){
      var parentSpan = tracer.startSpan("fc-operation", {
        childOf: spanContext
      });
      sleep(150);
      child(parentSpan.context())
      parentSpan.finish();
    }
    
    function child(spanContext){
      var childSpan = tracer.startSpan("fc-operation-child", {
        childOf: spanContext
      });
      childSpan.log({event:"timeout"});
      sleep(100);
      childSpan.finish();
    }