icyboy / zipkin
PHP 的 Zipkin 仪表化工具
Requires
- php: ^5.6||^7.0
- guzzlehttp/guzzle: ^6.2
- psr/log: ^1.0
Requires (Dev)
- phpunit/phpunit: ~5.7.19
- squizlabs/php_codesniffer: 3.*
This package is not auto-updated.
Last update: 2024-09-15 04:36:57 UTC
README
这是一个 生产就绪 的 PHP Zipkin 库。
这是一个简单的使用示例,对于更完整的客户端/服务器端示例,请查看 此存储库。
安装
composer require icyboy/zipkin
设置
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(); });
追踪
追踪器创建并连接跨度,以模拟可能分布式工作的延迟。它可以采用采样来减少过程开销或减少发送到 Zipkin 的数据量。
追踪器返回的跨度在完成后向 Zipkin 报告数据,或者在未采样的情况下不执行任何操作。在启动跨度后,您可以注释感兴趣的事件或添加包含详细信息或查找键的标签。
跨度有一个上下文,其中包含放置在表示分布式操作的树中正确位置的跟踪标识符。
本地追踪
当追踪本地代码时,只需在跨度内运行它
$span = $tracer->newTrace()->setName('encode')->start(); try { doSomethingExpensive(); } finally { $span->finish(); }
在上面的示例中,跨度是跟踪的根。在许多情况下,您将是一个现有跟踪的一部分。在这种情况下,调用 newChild
而不是 newTrace
$span = $tracer->newChild($root->getContext())->setName('encode')->start(); try { doSomethingExpensive(); } finally { $span->finish(); }
自定义跨度
一旦您有了跨度,您就可以向其添加标签,这些标签可以用作查找键或详细信息。例如,您可能添加一个包含您的运行时版本的标签。
$span->tag('http.status_code', '200');
RPC 追踪
RPC 追踪通常由拦截器自动执行。幕后,它们添加与它们在 RPC 操作中角色相关的标签和事件。
这是一个客户端跨度的示例
// 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();
单向追踪
有时您需要模拟一个异步操作,其中有一个请求,但没有响应。在正常的 RPC 追踪中,您使用 $span->finish()
来指示已收到响应。在单向追踪中,您使用 $span->flush()
代替,因为您不期望收到响应。
这是一个客户端如何模拟单向操作的示例
// 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();
服务器可能这样处理...
// 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();
采样
采样可以用于减少收集和报告的进程数据。当一个跨度未被采样时,它不会增加任何开销(空操作)。
采样是一个前瞻性决策,这意味着在跟踪中的第一个操作处就做出报告数据的决策,并且该决策会向下传播。
默认情况下,有一个全局采样器,它对所有追踪操作应用一个单一速率。 Sampler
是您指示此的方式,并且默认为追踪每个请求。
自定义采样
您可能希望根据操作应用不同的策略。例如,您可能不想追踪对静态资源(如图像)的请求,或者您可能想追踪对新 api 的所有请求。
大多数用户将使用框架拦截器来自动化此类策略。以下是它们可能内部工作的示例。
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); }
传播
传播对于确保来自同一根的活动被收集到相同的跟踪中是必需的。最常用的传播方法是将跟踪上下文从发送 RPC 请求的客户端复制到接收它的服务器。
例如,当进行下游 Http 调用时,其跟踪上下文将随请求一起发送,编码为请求头。
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 │ │
│ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │
│ │ │ │
└──────────────────┘ └──────────────────┘
上面的名称来自 B3 Propagation,它是 Brave 内置的,并且在许多语言和框架中都有实现。
大多数用户会使用框架拦截器来自动化传播。以下是它们可能如何内部工作的。
以下是客户端传播可能的样子
// 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);
以下是服务器端传播可能的样子
// configure a function that extracts the trace context from a request $extracted = $tracing->getPropagation()->extractor(new RequestHeaders); $span = $tracer->newChild($extracted)->setKind(Kind\SERVER);
提取传播上下文
Extractor
从传入的请求或消息中读取跟踪标识符和采样状态。载体通常是请求对象或头。
SamplingFlags|TraceContext
通常仅与 $tracer->newChild(extracted)
一起使用,除非你在客户端和服务器之间共享span ID。
实现传播
Extractor
将输出一个包含以下之一的 SamplingFlags|TraceContext
:
- 如果存在跟踪和span ID,则输出
TraceContext
。 - 如果没有标识符,则输出
SamplingFlags
。
当前span
Zipkin 支持一个“当前span”的概念,它表示正在进行的操作。Tracer::currentSpan()
可以用来向span添加自定义标签,而 Tracer::nextSpan()
可以用来创建当前正在进行的span的子span。
当前span的一个常见用例是用于检测RPC客户端。例如
/** * 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(); } } }
手动设置作用域内的span
当编写新的检测代码时,将你创建的span放置在作用域内作为当前span是很重要的。
在边缘情况下,你可能需要暂时清除当前span。例如,启动一个不应该与当前请求关联的任务。为此,只需将null传递给 openScope
。
测试
可以通过以下方式运行测试:
composer test