当前位置: 欣欣网 > 码农

PHP 服务实现性能剖析、跟踪和可观察性最佳实践

2024-06-10码农

简介

链路追踪Tracing Analysis为分布式应用的开发者提供了完整的调用链路还原、调用请求量统计、链路拓扑、应用依赖分析等工具,可以帮助开发者快速分析和诊断分布式应用架构下的性能瓶颈,提高微服务时代下的开发诊断效率。

官方地址:https://github.com/openzipkin/zipkin

Zipkin运行架构

产品架构(链路追踪)

链路追踪的主要工作流程如下

  1. 客户侧的应用程序通过集成链路追踪的多语言客户端SDK上报服务调用数据。链路追踪支持多种开源社区的SDK,且支持OpenTracing标准。

  2. 数据上报至链路追踪控制台后,链路追踪组件进行实时聚合计算和持久化,形成链路明细、性能总览、实时拓扑等监控数据。您可以据此进行问题排查与诊断。

  3. 调用链数据可对接下游阿里云产品,例如LogSearch、CloudMonitor、MaxCompute等,用于离线分析、报警等场景。

业务场景

随着业务越来越复杂,系统也随之进行各种拆分,特别是随着微服务架构和容器技术的兴起,看似简单的一个应用,后台可能有几十个甚至几百个服务在支撑;一个前端的请求可能需要多次的服务调用最后才能完成;当请求变慢或者不可用时,我们无法得知是哪个后台服务引起的,这时就需要解决如何快速定位服务故障点,zipkin分布式跟踪系统就能很好的解决这样的问题。

请求&响应

微服务架构下,一次请求后端会经历多个服务调用(所有请求链有相同的traceId和不同的spanId),都会沿着traceText带到每一个服务中。

数据是如何上报的?

直接上报数据

不通过Agent而直接上报数据的原理(传统框架。PHP-FPM + Nginx模式)

  • ThinkPHP6.0

  • Laravel

  • Yii2.0

  • 通过Agent上报数据

    通过Agent上报数据的原理(现代化框架。命令行模式)

  • webman

  • Swoole

  • 安装

    通过composer安装

    composer require openzipkin/zipkin

    使用

    创建Tracer

    Tracer对象可以用来创建Span对象(记录分布式操作时间)。Tracer对象还配置了上报数据的网关地址、本机IP、采样频率等数据,您可以通过调整采样率来减少因上报数据产生的开销。

    functioncreate_tracing($endpointName, $ipv4)
    {
    $endpoint = Endpoint::create($endpointName, $ipv4, null2555);
    /* Do not copy this logger into production.
    * Read https://github.com/Seldaek/monolog/blob/master/doc/01-usage.md#log-levels
    */

    $logger = new \Monolog\Logger('log');
    $logger->pushHandler(new \Monolog\Handler\ErrorLogHandler());
    $reporter = new Zipkin\Reporters\Http(\Zipkin\Reporters\Http\CurlFactory::create());
    $sampler = BinarySampler::createAsAlwaysSample();
    $tracing = TracingBuilder::create()
    ->havingLocalEndpoint($endpoint)
    ->havingSampler($sampler)
    ->havingReporter($reporter)
    ->build();
    return $tracing;
    }

    记录请求数据

    $rootSpan = $tracer->newTrace();
    $rootSpan->setName('encode');
    $rootSpan->start();
    try {
    doSomethingExpensive();
    finally {
    $rootSpan->finish();
    }

    以上代码用于记录请求的根操作,如果需要记录请求的上一步和下一步操作,则需要传入上下文。示例:

    $span = $tracer->newChild($parentSpan->getContext());
    $span->setName('encode');
    $span->start();
    try {
    doSomethingExpensive();
    finally {
    $span->finish();
    }

    总体流程如下

    Client Span Server Span
    ┌──────────────────┐ ┌──────────────────┐
    │ │ │ │
    │ TraceContext │ Http Request Headers │ TraceContext │
    │ ┌──────────────┐ │ ┌───────────────────┐ │ ┌──────────────┐ │
    │ │ TraceId │ │ │ X-B3-TraceId │ │ │ TraceId │ │
    │ │ │ │ │ │ │ │ │ │
    │ │ ParentSpanId │ │ Inject │ X-B3-ParentSpanId │Extract │ │ ParentSpanId │ │
    │ │ ├─┼─────────>│ ├────────┼>│ │ │
    │ │ SpanId │ │ │ X-B3-SpanId │ │ │ SpanId │ │
    │ │ │ │ │ │ │ │ │ │
    │ │ Sampled │ │ │ X-B3-Sampled │ │ │ Sampled │ │
    │ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │
    │ │ │ │
    └──────────────────┘ └──────────────────┘

    webman应用

    1. 开通ARMS

    开通ARMS地址 https://arms.console.aliyun.com/ (一般有15天试用)

    2. 获得数据上报接入点url

    进入 https://tracing.console.aliyun.com/#/globalSetting/cn-hangzhou/process 按照图示获得接入点url地址。

    如果你的服务器在阿里云上可以用阿里云vpc网络接入点,本示例用的是阿里云公网接入点

    安装

    通过composer安装:

    composer require openzipkin/zipkin

    使用

    1. 编写中间件

    链路监控中间件 app\middleware\ArmsMiddleware.php

    <?php
    /**
     * @desc 全链路监控中间件
     * @author Tinywan(ShaoBo Wan)
     * @date 2021/12/6 14:06
     */

    declare(strict_types=1);
    namespaceapp\middleware;
    useMonolog\Handler\ErrorLogHandler;
    useMonolog\Logger;
    usesupport\Log;
    usethink\facade\Db;
    useWebman\MiddlewareInterface;
    useWebman\Http\Response;
    useWebman\Http\Request;
    useZipkin\Reporters\Http;
    useZipkin\TracingBuilder;
    useZipkin\Samplers\BinarySampler;
    useZipkin\Endpoint;
    useWorkerman\Timer;
    useconstZipkin\Tags\SQL_QUERY;
    classArmsMiddlewareimplementsMiddlewareInterface
    {
    /**
    @desc: 方法描述
    @param Request $request
    @param callable $next
    @return Response
    @author Tinywan(ShaoBo Wan)
    */

    publicfunctionprocess(Request $request, callable $next) : Response
    {
    static $tracing = null, $tracer = null;
    if (!$tracing) {
    $endpoint = Endpoint::create('开源技术小栈', $request->getRealIp(), null2555);
    $logger = new Logger('log');
    $logger->pushHandler(new ErrorLogHandler());
    $reporter = new Http(['endpoint_url' => config('security')['endpoint_url']]);
    $sampler = BinarySampler::createAsAlwaysSample();
    $tracing = TracingBuilder::create()
    ->havingLocalEndpoint($endpoint)
    ->havingSampler($sampler)
    ->havingReporter($reporter)
    ->build();
    $tracer = $tracing->getTracer();
    // 55秒上报一次,尽量将上报对业务的影响减少到最低
    Timer::add(55function()use($tracer){
    $tracer->flush();
    });
    register_shutdown_function(function()use($tracer){
    $tracer->flush();
    });
    }
    $rootSpan = $tracer->newTrace();
    $rootSpan->setName($request->controller."::".$request->action);
    $rootSpan->start();
    $request->rootSpan = $rootSpan;
    $request->tracer = $tracer;
    $result = $next($request);
    // 统计sql(日志在内存)
    if ( class_exists(Db:: class)) {
    $logs = Db::getDbLog(true);
    if (!empty($logs['sql'])) {
    foreach ($logs['sql'as $sql) {
    $sqlSpan = $tracer->newChild($rootSpan->getContext());
    $sqlSpan->setName(SQL_QUERY);
    $sqlSpan->start();
    $sqlSpan->tag('db.statement', $sql);
    $sqlSpan->finish();
    }
    }
    }
    $rootSpan->finish();
    return $result;
    }
    }





    2. 配置中间件

    config/middleware.php 中添加全局中间件如下

    return [
    '' => [
    \app\middleware\ArmsMiddleware:: class,
    ],
    ...
    ];

    3. 查看监控

    访问地址 https://tracing.console.aliyun.com/ ,效果类似如下

    接口监控

    数据库监控