Jaeger链路追踪在项目中的应用
前言
随着公司内部项目交互越来越多,一个请求的调用链路也会变长,如何快速定位接口问题也成为了项目中需要考虑的因素,链路追踪工具就是为了解决这个问题而生。常见的链路追踪工具:
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 协议》,转载必须注明作者和本文链接
都仍我脸上了,我也不会
都晒我嘴里了,硬是咽不下去
先mark一下下
我也部署了,但是怎么应用呢?我把第一层初始化和结束的trancer放在父级Controller里面的构造和析构方法了,但是总觉得不妥,而且如果每个地方都要加span的话,会不会不妥