各位观众老爷,晚上好!今天咱们聊聊PHP里那些“隐形的翅膀”——Tracing,也就是分布式调用链追踪,配合OpenTelemetry和Jaeger,让你的代码像开了天眼一样,哪里慢、哪里出错,一览无遗!
啥是Tracing?为啥要Tracing?
想象一下,你开发了一个电商网站,用户下单流程涉及用户服务、商品服务、订单服务、支付服务等等,每个服务都可能部署在不同的服务器上。一旦用户下单失败,你面对的是一堆日志,想要找到问题根源,简直像大海捞针!
Tracing就是来解决这个问题的。它可以记录一次请求在各个服务之间的调用路径、耗时,让你清晰地看到整个调用链,快速定位性能瓶颈和错误。
简单来说,Tracing就是给你一张调用流程图,告诉你请求“从哪里来,到哪里去,中间经历了什么”。
OpenTelemetry:追踪界的“瑞士军刀”
OpenTelemetry (简称OTel) 是一个开源的可观测性框架,提供了一套标准的API、SDK和工具,用于生成、收集、处理和导出遥测数据,包括 Traces (追踪)、Metrics (指标) 和 Logs (日志)。
你可以把它理解为追踪界的“瑞士军刀”,它定义了一套通用的标准,让你可以轻松地接入各种追踪系统,比如 Jaeger、Zipkin、SkyWalking等等。
Jaeger:追踪数据的“可视化大师”
Jaeger 是一个开源的分布式追踪系统,由 Uber 开源。它可以收集、存储和可视化追踪数据,让你通过Web界面查看调用链、分析性能瓶颈。
你可以把它理解为追踪数据的“可视化大师”,它能把OpenTelemetry收集到的数据,变成漂亮的调用链图表,让你一目了然。
PHP + OpenTelemetry + Jaeger:完美搭档
接下来,咱们就来看看如何在PHP项目中,使用OpenTelemetry和Jaeger来实现分布式调用链追踪。
1. 安装扩展和依赖
首先,你需要安装OpenTelemetry PHP扩展和Jaeger客户端。
pecl install opentelemetry
pecl install opentelemetry_exporter_jaeger
如果你的PHP版本比较新,可能还需要安装一些其他的依赖,具体可以参考OpenTelemetry PHP扩展的官方文档。
然后,使用Composer安装OpenTelemetry SDK:
composer require open-telemetry/sdk
composer require open-telemetry/exporter-jaeger
2. 初始化OpenTelemetry SDK
在你的PHP项目中,你需要初始化OpenTelemetry SDK,配置Jaeger exporter。
<?php
use OpenTelemetrySDKTraceTracerProviderFactory;
use OpenTelemetrySDKResourceResourceInfoFactory;
use OpenTelemetrySDKTraceSamplerAlwaysOnSampler;
use OpenTelemetrySDKTraceSpanProcessorSimpleSpanProcessor;
use OpenTelemetryContribOtlpGrpcExporter as OtlpExporter;
use OpenTelemetryContribJaegerExporter as JaegerExporter;
use OpenTelemetrySDKTraceTracerProvider;
use OpenTelemetryAPIGlobals;
use OpenTelemetryAPICommonAttributes;
use OpenTelemetrySDKResourceResourceInfo;
use OpenTelemetrySemConvResourceAttributes;
use OpenTelemetryAPITraceTracerInterface;
require 'vendor/autoload.php';
// 配置Jaeger exporter
$exporter = new JaegerExporter(
'MyService', // 服务名称
'localhost:6831' // Jaeger Agent 地址
);
// 配置采样器 (AlwaysOnSampler 意味着所有请求都会被采样)
$sampler = new AlwaysOnSampler();
// 创建Span处理器
$spanProcessor = new SimpleSpanProcessor($exporter);
// 创建资源信息
$resource = ResourceInfoFactory::create(
ResourceInfo::create(new Attributes([
ResourceAttributes::SERVICE_NAME => 'MyService',
ResourceAttributes::SERVICE_VERSION => '1.0.0',
]))
);
// 创建TracerProvider
$tracerProvider = new TracerProviderFactory(
[
$spanProcessor
],
$sampler,
$resource
)->createTracerProvider();
// 获取Tracer
$tracer = $tracerProvider->getTracer('MyService', '1.0.0');
// 将Tracer设置为全局变量
Globals::registerTracerProvider($tracerProvider);
代码解释:
JaegerExporter
:配置Jaeger exporter,指定服务名称和Jaeger Agent的地址。Jaeger Agent负责接收追踪数据,并将其发送到Jaeger Collector。AlwaysOnSampler
:配置采样器。AlwaysOnSampler
意味着所有请求都会被采样,用于开发和调试环境。在生产环境中,可以考虑使用TraceIdRatioBasedSampler
,根据一定的比例进行采样,减少追踪数据的量。SimpleSpanProcessor
:配置Span处理器。SimpleSpanProcessor
会将Span立即发送到exporter。TracerProvider
:创建TracerProvider,它是追踪系统的核心组件,负责创建和管理Tracer。Globals::registerTracerProvider()
:将TracerProvider设置为全局变量,方便在代码中使用。
3. 创建Span
在你的代码中,你需要手动创建Span,来记录请求的开始和结束。
<?php
use OpenTelemetryAPIGlobals;
use OpenTelemetryContextContext;
// 获取Tracer
$tracer = Globals::tracerProvider()->getTracer('MyService', '1.0.0');
// 创建根Span
$span = $tracer->spanBuilder('MyController::index')->startSpan();
$scope = $span->activate(); // 激活 Span
try {
// 模拟一些业务逻辑
echo "Hello, Tracing!n";
sleep(1); // 模拟耗时操作
// 创建子Span
$childSpan = $tracer->spanBuilder('MyDatabase::query')->startSpan();
$childScope = $childSpan->activate();
try {
// 模拟数据库查询
echo "Querying database...n";
sleep(2); // 模拟耗时操作
$childSpan->setAttribute('db.statement', 'SELECT * FROM users WHERE id = 1'); // 添加属性
} finally {
$childSpan->end(); // 结束子Span
$childScope->detach(); // Detach 子 Span
}
// 记录事件
$span->addEvent('User logged in', ['user.id' => 123]);
} catch (Exception $e) {
$span->setStatus(OpenTelemetryAPITraceStatusCode::STATUS_ERROR, $e->getMessage()); // 设置状态码为错误
$span->recordException($e); // 记录异常
} finally {
$span->end(); // 结束根Span
$scope->detach(); // Detach 根 Span
}
代码解释:
$tracer->spanBuilder('MyController::index')->startSpan()
:创建一个Span,并设置Span的名称为MyController::index
。$span->activate()
:激活Span,将其设置为当前上下文的活动Span。$span->end()
:结束Span。$span->setAttribute('db.statement', 'SELECT * FROM users WHERE id = 1')
:添加属性,用于记录Span的额外信息,比如数据库查询语句。$span->addEvent('User logged in', ['user.id' => 123])
:记录事件,用于记录Span的关键事件,比如用户登录。$span->setStatus(OpenTelemetryAPITraceStatusCode::STATUS_ERROR, $e->getMessage())
:设置Span的状态码为错误,并记录错误信息。$span->recordException($e)
:记录异常。
4. 运行代码,查看Jaeger UI
运行你的PHP代码,然后访问Jaeger UI (通常是 http://localhost:16686
),你就可以看到调用链了!
5. 进阶技巧
-
Context Propagation:如果你的请求跨越多个服务,你需要使用Context Propagation来传递追踪信息。OpenTelemetry提供了一套标准的Context Propagation机制,可以将追踪信息注入到HTTP Header中,并在下游服务中提取出来。
<?php use OpenTelemetryAPIGlobals; use OpenTelemetryContextContext; use OpenTelemetryAPIPropagationPropagationInterface; use OpenTelemetryAPIPropagationContextContextPropagatorInterface; use OpenTelemetryContextPropagationArrayAccessGetterSetter; // 获取Tracer $tracer = Globals::tracerProvider()->getTracer('MyService', '1.0.0'); // 获取 TextMapPropagator (例如 W3C Trace Context) $propagator = Globals::propagator(); // 创建根Span $span = $tracer->spanBuilder('MyService::callRemoteService')->startSpan(); $scope = $span->activate(); try { // 模拟调用远程服务 // 准备 HTTP Header 数组 $carrier = []; // 注入 Context 到 HTTP Header $propagator->inject($carrier, new ArrayAccessGetterSetter(), Context::getCurrent()); // 打印 HTTP Header,用于传递给下游服务 print_r($carrier); // 模拟下游服务接收 HTTP Header $remoteCarrier = $carrier; // 从 HTTP Header 中提取 Context $remoteContext = $propagator->extract($remoteCarrier, new ArrayAccessGetterSetter()); // 创建下游服务的 Span,并设置为父 Span $remoteSpan = $tracer->spanBuilder('RemoteService::processRequest') ->setParent($remoteContext) // 设置父 Span ->startSpan(); $remoteScope = $remoteSpan->activate(); try { // 模拟远程服务处理请求 echo "Remote service processing request...n"; sleep(1); } finally { $remoteSpan->end(); $remoteScope->detach(); } } finally { $span->end(); $scope->detach(); }
代码解释:
Globals::propagator()
:获取 TextMapPropagator,用于将Context注入到HTTP Header中,或者从HTTP Header中提取Context。OpenTelemetry默认使用W3C Trace Context propagator。$propagator->inject($carrier, new ArrayAccessGetterSetter(), Context::getCurrent())
:将当前Context注入到HTTP Header数组中。ArrayAccessGetterSetter
用于访问和设置数组中的值。$propagator->extract($remoteCarrier, new ArrayAccessGetterSetter())
:从HTTP Header数组中提取Context。$tracer->spanBuilder('RemoteService::processRequest')->setParent($remoteContext)->startSpan()
:创建下游服务的Span,并使用setParent()
方法将父Span设置为从HTTP Header中提取的Context。
-
自定义属性和事件:你可以使用
setAttribute()
和addEvent()
方法,为Span添加自定义属性和事件,用于记录Span的额外信息。<?php use OpenTelemetryAPIGlobals; // 获取Tracer $tracer = Globals::tracerProvider()->getTracer('MyService', '1.0.0'); // 创建Span $span = $tracer->spanBuilder('MyFunction')->startSpan(); $scope = $span->activate(); try { // 添加自定义属性 $span->setAttribute('user.id', 123); $span->setAttribute('product.id', 456); // 记录事件 $span->addEvent('Product added to cart', ['quantity' => 2]); // 模拟一些业务逻辑 echo "Processing...n"; sleep(1); } finally { $span->end(); $scope->detach(); }
-
使用Instrumentations:OpenTelemetry提供了一些Instrumentations,可以自动为你创建Span,无需手动编写代码。比如,你可以使用PDO Instrumentation来自动追踪数据库查询。
<?php use OpenTelemetryContribInstrumentationPDOPDO; use OpenTelemetryAPIGlobals; // 获取Tracer $tracer = Globals::tracerProvider()->getTracer('MyService', '1.0.0'); // 创建PDO连接 $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password'); // 执行查询 $statement = $pdo->prepare('SELECT * FROM users WHERE id = ?'); $statement->execute([1]); // 获取结果 $result = $statement->fetchAll(); // 打印结果 print_r($result);
注意: 你需要安装对应的Instrumentation:
composer require open-telemetry/instrumentation-pdo
总结
Tracing是分布式系统中不可或缺的一部分,它可以帮助你快速定位性能瓶颈和错误,提高开发效率。OpenTelemetry和Jaeger是强大的追踪工具,可以让你轻松地实现分布式调用链追踪。
希望今天的讲座能帮助你更好地理解Tracing,并在你的PHP项目中应用起来!
一些小提示:
- 在生产环境中,要合理配置采样率,避免追踪数据量过大。
- 可以使用日志关联,将日志和追踪信息关联起来,方便排查问题。
- 可以结合Metrics和Logs,构建全面的可观测性体系。
好了,今天的讲座就到这里,感谢大家的观看!有问题欢迎随时提问!