icyboy/zipkin 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

icyboy/zipkin

Composer 安装命令:

composer require icyboy/zipkin

包简介

A Zipkin instrumentation for PHP

README 文档

README

Build Status Minimum PHP Version Total Downloads License

This is a production ready PHP library for Zipkin.

This is a simple example of usage, for a more complete frontend/backend example, check this repository.

Installation

composer require icyboy/zipkin

Setup

use GuzzleHttp\Client;
use Zipkin\Annotations;
use Zipkin\Endpoint;
use Zipkin\Samplers\BinarySampler;
use Zipkin\Timestamp;
use Zipkin\TracingBuilder;
use Zipkin\Reporters\HttpLogging;
use Zipkin\Propagation\Map;
use Zipkin\Propagation\DefaultSamplingFlags;
use Symfony\Component\HttpFoundation\Request;

$request  = Request::createFromGlobals();

$endpoint = Endpoint::create('server_name', 'server_ipv4', 'server_ipv6', 'server_ipv4');
$client = new Client();

// Logger to stdout
$logger = new \Monolog\Logger('log');
$logger->pushHandler(new \Monolog\Handler\ErrorLogHandler());

$reporter = new HttpLogging($client, $logger, ["host"=>"zipkin_server_address"]);
$sampler = BinarySampler::createAsAlwaysSample();
$tracing = TracingBuilder::create()
    ->havingLocalEndpoint($endpoint)
    ->havingSampler($sampler)
    ->havingReporter($reporter)
    ->build();

/***** this for client *****/
/* Always sample traces */
//$defaultSamplingFlags = DefaultSamplingFlags::createAsSampled();
/* Creates the main span */
//$span = $tracer->newTrace($defaultSamplingFlags);
/* Creates the span for getting the users list */
//$childSpan = $tracer->newChild($span->getContext());
//$childSpan->start();
//$childSpan->setName('users:get_list');
//$headers = [];
/* Injects the context into the wire */
//$injector = $tracing->getPropagation()->getInjector(new Map());
//$injector($childSpan->getContext(), $headers);
/* HTTP Request to the backend */
//$httpClient = new Client();
//$request = new \GuzzleHttp\Psr7\Request('POST', 'localhost:9000', $headers);
//$response = $httpClient->send($request);


//get request header
$carrier  = array_map(function ($header) {
    return $header[0];
}, $request->headers->all());

/* Extracts the context from the HTTP headers */
//get X─B3─xxx from headers if existed or auto generate
$extractor    = $tracing->getPropagation()->getExtractor(new Map());
$traceContext = $extractor($carrier);

//create trace
$tracer       = $tracing->getTracer();
$span         = $tracer->newChild($traceContext);
$span->start();

//get request parameters
$content = $request->getContent();
$data = json_decode($content, true);
if (is_array($data)) {
    $output = $data;
} elseif (!empty($content)) {
    parse_str($content, $output);
}

if (!empty($output) && is_array($output)) {
    foreach ($output as $k => $v) {
        $value = is_array($v) ? json_encode($v) : $v;
        $span->tag($k, $value);
    }
}

//set name
$span->setName(sprintf("%s %s", $request->getMethod(), $request->server->get("REQUEST_URI")));

//set Annotation
$span->annotate(Annotations\SERVER_RECV, Timestamp\now());
$span->finish(Timestamp\now());

//push info to zipkin server befor close program
register_shutdown_function(function () use ($tracer) {
    $tracer->flush();
});

Tracing

The tracer creates and joins spans that model the latency of potentially distributed work. It can employ sampling to reduce overhead in process or to reduce the amount of data sent to Zipkin.

Spans returned by a tracer report data to Zipkin when finished, or do nothing if unsampled. After starting a span, you can annotate events of interest or add tags containing details or lookup keys.

Spans have a context which includes trace identifiers that place it at the correct spot in the tree representing the distributed operation.

Local Tracing

When tracing local code, just run it inside a span

$span = $tracer->newTrace()->setName('encode')->start();

try {
  doSomethingExpensive();
} finally {
  $span->finish();
}

In the above example, the span is the root of the trace. In many cases, you will be a part of an existing trace. When this is the case, call newChild instead of newTrace

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

Customizing spans

Once you have a span, you can add tags to it, which can be used as lookup keys or details. For example, you might add a tag with your runtime version.

$span->tag('http.status_code', '200');

RPC tracing

RPC tracing is often done automatically by interceptors. Under the scenes, they add tags and events that relate to their role in an RPC operation.

Here's an example of a client span:

// before you send a request, add metadata that describes the operation
$span = $tracer->newTrace()->setName('get')->setKind(Kind\CLIENT);
$span->tag('http.status_code', '200');
$span->tag(Tags\HTTP_PATH, '/api');
$span->setRemoteEndpoint(Remote::create('backend', 127 << 24 | 1, null, 8080);

// when the request is scheduled, start the span
$span->start();

// if you have callbacks for when data is on the wire, note those events
$span->annotate(Annotations\SERVER_SEND);
$span->annotate(Annotations\SERVER_RECV);

// when the response is complete, finish the span
$span->finish();

One-Way tracing

Sometimes you need to model an asynchronous operation, where there is a request, but no response. In normal RPC tracing, you use $span->finish() which indicates the response was received. In one-way tracing, you use $span->flush() instead, as you don't expect a response.

Here's how a client might model a one-way operation

// start a new span representing a client request
$oneWaySend = $tracer->newChild($parent)->setKind(Kind\CLIENT);

// Add the trace context to the request, so it can be propagated in-band
$tracing->getPropagation()->getInjector(new RequestHeaders)
                     ->inject($oneWaySend->getContext(), $request);

// fire off the request asynchronously, totally dropping any response
$client->execute($request);

// start the client side and flush instead of finish
$oneWaySend->start()->flush();

And here's how a server might handle this...

// pull the context out of the incoming request
$extractor = $tracing->getPropagation()->getExtractor(new RequestHeaders);

// convert that context to a span which you can name and add tags to
$oneWayReceive = $tracer->newChild($extractor($request))
    ->setName('process-request')
    ->setKind(Kind\SERVER)
    ... add tags etc.

// start the server side and flush instead of finish
$oneWayReceive->start()->flush();

// you should not modify this span anymore as it is complete. However,
// you can create children to represent follow-up work.
$next = $tracer->newChild($oneWayReceive->getContext())->setName('step2')->start();

Sampling

Sampling may be employed to reduce the data collected and reported out of process. When a span isn't sampled, it adds no overhead (noop).

Sampling is an up-front decision, meaning that the decision to report data is made at the first operation in a trace, and that decision is propagated downstream.

By default, there's a global sampler that applies a single rate to all traced operations. Sampler is how you indicate this, and it defaults to trace every request.

Custom sampling

You may want to apply different policies depending on what the operation is. For example, you might not want to trace requests to static resources such as images, or you might want to trace all requests to a new api.

Most users will use a framework interceptor which automates this sort of policy. Here's how they might work internally.

private function newTrace(Request $request) {
  $flags = SamplingFlags::createAsEmpty();
  if (strpos($request->getUri(), '/experimental') === 0) {
    $flags = DefaultSamplingFlags::createAsSampled();
  } else if (strpos($request->getUri(), '/static') === 0) {
    $flags = DefaultSamplingFlags::createAsSampled();
  }
  return $tracer->newTrace($flags);
}

Propagation

Propagation is needed to ensure activity originating from the same root are collected together in the same trace. The most common propagation approach is to copy a trace context from a client sending an RPC request to a server receiving it.

For example, when an downstream Http call is made, its trace context is sent along with it, encoded as request headers:

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

The names above are from B3 Propagation, which is built-in to Brave and has implementations in many languages and frameworks.

Most users will use a framework interceptor which automates propagation. Here's how they might work internally.

Here's what client-side propagation might look like

// configure a function that injects a trace context into a request
$injector = $tracing->getPropagation()->getInjector(new RequestHeaders);

// before a request is sent, add the current span's context to it
$injector->inject($span->getContext(), $request);

Here's what server-side propagation might look like

// configure a function that extracts the trace context from a request
$extracted = $tracing->getPropagation()->extractor(new RequestHeaders);

$span = $tracer->newChild($extracted)->setKind(Kind\SERVER);

Extracting a propagated context

The Extractor reads trace identifiers and sampling status from an incoming request or message. The carrier is usually a request object or headers.

SamplingFlags|TraceContext is usually only used with $tracer->newChild(extracted), unless you are sharing span IDs between a client and a server.

Implementing Propagation

Extractor will output a SamplingFlags|TraceContext with one of the following:

  • TraceContext if trace and span IDs were present.
  • SamplingFlags if no identifiers were present

Current Span

Zipkin supports a "current span" concept which represents the in-flight operation. Tracer::currentSpan() can be used to add custom tags to a span and Tracer::nextSpan() can be used to create a child of whatever is in-flight.

A common use case for the current span is to instrument RPC clients. For example:

/**
  * This http clients composes an http client using PSR7
  */
class TraceClient implements ClientInterface
{
    public function request($method, $uri = '', array $options = [])
    {
        /* Gets the child Span of the current one */
        $span = $this->tracer->nextSpan();
        $span->setKind(Zipkin\Kind\CLIENT);
        $span->tag(Tags\HTTP_PATH, $uri);
        
        try {
            $response = $this->client->request($method, $uri, $options);
            $span->tag(Tags\HTTP_STATUS_CODE, $response->getStatusCode());
            
            return $response;
        catch (\Exception $e) {
            $span->tag(Tags\Error, $response->getReasonPhrase());        
            $span->tag(Tags\HTTP_STATUS_CODE, $e->getResponse->getStatusCode());
            
            throw $e;
        } finally {
            $span->finish();
        }
    }
}

Setting a span in scope manually

When writing new instrumentation, it is important to place a span you created in scope as the current span.

In edge cases, you may need to clear the current span temporarily. For example, launching a task that should not be associated with the current request. To do this, simply pass null to openScope.

Tests

Tests can be run by

composer test

Reference

icyboy/zipkin 适用场景与选型建议

icyboy/zipkin 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 516 次下载、GitHub Stars 达 0, 最近一次更新时间为 2017 年 11 月 23 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 icyboy/zipkin 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 icyboy/zipkin 我们能提供哪些服务?
定制开发 / 二次开发

基于 icyboy/zipkin 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 516
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 10
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 60
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-11-23