Jaeger鏈路追蹤在專案中的應用

失色天空發表於2022-06-08

前言

隨著公司內部專案互動越來越多,一個請求的呼叫鏈路也會變長,如何快速定位介面問題也成為了專案中需要考慮的因素,鏈路追蹤工具就是為了解決這個問題而生。常見的鏈路追蹤工具:

  • 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 協議》,轉載必須註明作者和本文連結
失色天空

相關文章