前言
隨著公司內部專案互動越來越多,一個請求的呼叫鏈路也會變長,如何快速定位介面問題也成為了專案中需要考慮的因素,鏈路追蹤工具就是為了解決這個問題而生。常見的鏈路追蹤工具:
APM
這是Elasticsearch
內建的一個鏈路追蹤工具,支援主流的語言,如果你的應用是fpm
的架構,並且想要最小的改動在專案中接入鏈路的話,這是一個不錯的選擇,只需要加一個擴充套件就行了。但由於我們的專案使用的大多數都是Hyperf
框架,APM
擴充套件會和Swoole
衝突,所以放棄。Jaeger
這是Uber
推出的一款比較成熟的鏈路追蹤工具,關於它的介紹自己百度一下就行了,我們這裡就選用的是jaeger
,Hyperf
框架已經有了jaeger
的包,可以直接使用,這裡主要是記錄如何在傳統框架接入jaeger
效果圖:
一、接入
網上幾乎搜不到php接入jaeger的有用的文章,全是長篇大論的理論知識,但對於php開發人員來講,他只想直接精通,最好把程式碼扔臉上,所以,我就是來幹這個事情的。
明確一點,一個請求有一個rootSpan
(根),在上圖中就是最開始的那個request
,後續的所有需要記錄的操作,比如db
和redis
都是request
的childSpan
,他們是request
的子級,而db
和redis
是同級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 協議》,轉載必須註明作者和本文連結