Jaeger链路追踪在项目中的应用

前言

随着公司内部项目交互越来越多,一个请求的调用链路也会变长,如何快速定位接口问题也成为了项目中需要考虑的因素,链路追踪工具就是为了解决这个问题而生。常见的链路追踪工具:

  • APM
    这是Elasticsearch内置的一个链路追踪工具,支持主流的语言,如果你的应用是fpm的架构,并且想要最小的改动在项目中接入链路的话,这是一个不错的选择,只需要加一个扩展就行了。但由于我们的项目使用的大多数都是Hyperf框架,APM扩展会和Swoole冲突,所以放弃。

  • Jaeger
    这是Uber推出的一款比较成熟的链路追踪工具,关于它的介绍自己百度一下就行了,我们这里就选用的是jaegerHyperf框架已经有了jaeger的包,可以直接使用,这里主要是记录如何在传统框架接入jaeger

效果图:

一、接入

网上几乎搜不到php接入jaeger的有用的文章,全是长篇大论的理论知识,但对于php开发人员来讲,他只想直接精通,最好把代码扔脸上,所以,我就是来干这个事情的。

明确一点,一个请求有一个rootSpan(根),在上图中就是最开始的那个request,后续的所有需要记录的操作,比如dbredis都是requestchildSpan,他们是request的子级,而dbredis是同级fllowSpan,他们是兄弟关系,先用最简单的示例看一下(startActiveSpan这种方式不太好直接应用到服务中,这里只用作演示):

  • 安装包
    composer require jonahgeorge/jaeger-client-php

  • 测试代码

      // 初始化全局配置(可以放到框架启动的时候执行)
      $config = new Config(
          [
              'sampler'       => [
                  'type'  => Jaeger\SAMPLER_TYPE_CONST,
                  'param' => true,
              ],
              'logging'       => true,
              // tags记录需要记录的参数有哪些,自定义
              "tags"          => [
                  'http.url'         => 'http.url',
                  'http.method'      => 'http.method',
                  'http.status_code' => 'http.status_code',
    
                  'db.query'      => 'db.query',
                  'db.statement'  => 'db.statement',
                  'db.query_time' => 'db.query_time',
    
                  'path'   => 'request.path',
                  'method' => 'request.method',
                  'header' => 'request.header',
    
                  'status_code' => 'response.status_code',
              ],
              // jaeger的地址,这个需要你提交搭建好jaeger服务
              "local_agent"   => [
                  "reporting_host" => "web-jaeger.your-domain.com",
                  "reporting_port" => 5775
              ],
              // 使用udp协议传输数据
              'dispatch_mode' => Config::ZIPKIN_OVER_COMPACT_UDP,
          ],
          // 服务的名字,比如订单中心
          'order-center'
      );
      // 初始化配置
      $config->initializeTracer();
      // 全局的trancer对象,这是一个单例对象
      $tracer = GlobalTracer::get();
      // 创建一个根时间分段,startActiveSpan第一个参数是自定义当前操作的名称,这里定义成
      // request,代表一个请求,注意scope的代码结构,是一个包含的关系
      // 每一个span对应一个start和一个close,它们是成对出现。
    
      $scope = $tracer->startActiveSpan('request', []);
      // tag记录一些短参数,比如header头,请求方法等
      $scope->getSpan()->setTag("tag1", "value1");
      $scope->getSpan()->setTag("tag2", "value2");
      $scope->getSpan()->setTag("tag3", "value2");
      // log记录一些大的参数,比如请求的json
      $scope->getSpan()->log([
          "key1" => "value1",
          "key2" => 2,
          "key3" => true
      ]);
    
          // 创建$scope子分段
          $nestedSpanScope1= $tracer->startActiveSpan("db.query");
          $nestedSpanScope1->getSpan()->setTag("tag1", "value1");
          $nestedSpanScope1->getSpan()->setTag("tag2", "value2");
          $nestedSpanScope1->getSpan()->setTag("tag3", "value2");
          $nestedSpanScope1->getSpan()->log([
              "key1" => "value1",
              "key2" => 2,
              "key3" => true
          ]);
    
                  // 创建$nestedSpanScope1的子分段
                  $nestedSpanScope11 = $tracer->startActiveSpan("redis.get");
                  $nestedSpanScope11->getSpan()->setTag("tag1", "value1");
                  $nestedSpanScope11->getSpan()->setTag("tag2", "value2");
                  $nestedSpanScope11->getSpan()->setTag("tag3", "value2");
                  $nestedSpanScope11->getSpan()->log([
                      "key1" => "value1",
                      "key2" => 2,
                      "key3" => true
                  ]);
                  $nestedSpanScope11->close();
    
                  $nestedSpanScope12= $tracer->startActiveSpan("oa");
                  // 假设oa接口返回的数据是这样的
                  $oaResponseData = [];
    
                  $nestedSpanScope12->getSpan()->setTag("http.url", "xxx.oa.com");
                  $nestedSpanScope12->getSpan()->setTag("http.method", "POST");
                  $nestedSpanScope12->getSpan()->setTag("http.status_code", 200);
                  $nestedSpanScope12->getSpan()->log($oaResponseData);
                  $nestedSpanScope12->close();
         $nestedSpanScope1->close();
    
          // 创建$scope的子分段
          $nestedSpanScope2 = $tracer->startActiveSpan("db.insert");
          $nestedSpanScope2->getSpan()->setTag("tag1", "value1");
          $nestedSpanScope2->getSpan()->setTag("tag2", "value2");
          $nestedSpanScope2->getSpan()->setTag("tag3", "value2");
          $nestedSpanScope2->getSpan()->log([
              "key1" => "value1",
              "key2" => 2,
              "key3" => true
          ]);
    
              // 创建$nestedSpanScope2的子分段
              $nestedSpanScope21 = $tracer->startActiveSpan("redis.hget");
              $nestedSpanScope21->getSpan()->setTag("tag1", "value1");
              $nestedSpanScope21->getSpan()->setTag("tag2", "value2");
              $nestedSpanScope21->getSpan()->setTag("tag3", "value2");
              $nestedSpanScope21->getSpan()->log([
                  "key1" => "value1",
                  "key2" => 2,
                  "key3" => true
              ]);
              $nestedSpanScope21->close();
          $nestedSpanScope2->close();
    
      $scope->close();
      // 一个请求最终只有一个flush,表示把之前记录的span关系全部写入jaeger服务中
      $tracer->flush();
  • 层级结构

    order-center request
    |--- order-center db.query
    |--- order-center redis.get
    order-center db.insert
    |---  order-center redis.hge

二、项目应用

先不写了,过会儿再写

本作品采用《CC 协议》,转载必须注明作者和本文链接
失色天空
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 9

都仍我脸上了,我也不会

1年前 评论
薄荷蓝的晴天 1年前

都晒我嘴里了,硬是咽不下去

1年前 评论

我也部署了,但是怎么应用呢?我把第一层初始化和结束的trancer放在父级Controller里面的构造和析构方法了,但是总觉得不妥,而且如果每个地方都要加span的话,会不会不妥

1年前 评论
hufei1995 (作者) 1年前
失色天空 (楼主) 1年前
请多多指教 1年前
失色天空 (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!