本次分享OpenTracing目的:
通过本次分享,你可以快速的上手使用OpenTracing,部署链路追踪到你的系统,但是你只能简单的认识它,并掌握如何使用,而它的实现原理是无法通过本次分享掌握的。
将通过以下几点来介绍OpenTracing
1.什么是链路追踪 ?
2.OpenTracing 是什么 ?
3.OpenTracing 的主要几个数据模型
4.基于jaegertracing 实现全链路追踪
5.如何查看追踪信息查看 ?
1.什么是链路追踪 ?
在微服务架构的系统中,请求在各服务之间流转,调用链错综复杂,一旦出现了问题和异常,很难追查定位,这个时候就需要链路追踪来帮忙了。链路追踪系统能追踪并记录请求在系统中的调用顺序,调用时间等一系列关键信息,从而帮助我们定位异常服务和发现性能瓶颈。
目前由各大互联网公司和开源社区开发的分布式链路追踪产品有很多,同时也给使用者带来了一个问题,各个分布式链路追踪产品的 API 并不兼容,如果用户在各个产品之间进行切换,成本非常高。而 OpenTracing 就完美的解决了这个问题,OpenTracing 通过提供平台无关、厂商无关的 API,帮助开发人员能够方便地添加(或更换)追踪系统。
2.OpenTracing是什么?
OpenTracing 是分布式链路追踪的一种规范标准,是CNCF(云原生计算基金会)下的项目之一。和一般的规范标准不同,OpenTracing 不是传输协议,消息格式层面上的规范标准,而是一种语言层面上的API标准。无论使用什么开发语言,只要某链路追踪系统实现了OpenTracing 规定的接口(interface),符合OpenTracing 定义的表现行为,那么就可以说该应用符合OpenTracing 标准。这意味着开发者只需修改少量的配置代码,就可以在符合OpenTracing 标准的链路追踪系统之间自由切换。
OpenTracing 对各种语言的支持:
- Go - opentracing-go
- Python - opentracing-python
- Javascript - opentracing-javascript
- Java - opentracing-java
- C# - opentracing-csharp
- Objective-C - opentracing-objc
- C++ - opentracing-cpp
- Ruby - opentracing-ruby
- PHP - opentracing-php
3.OpenTracing 的主要几个数据模型
在使用Opentracing来实现全链路追踪前,有必要先了解一下它所定义的数据模型。
Span
Span是一条追踪链路中的基本组成要素,一个span表示一个独立的工作单元,比如可以表示一次函数调用,一次http请求等等。span会记录如下基本要素:
- 服务名称(operation name)
- 服务的开始时间和结束时间
- K/V形式的Tags
- K/V形式的Logs
- SpanContext
- References:该span对一个或多个span的引用(通过引用SpanContext)。
Tags
Tags以K/V键值对的形式保存用户自定义标签,主要用于链路追踪结果的查询过滤。例如: http.method="GET",http.status_code=200
。其中key值必须为字符串,value必须是字符串,布尔型或者数值型。 span中的tag仅自己可见,不会随着 SpanContext传递给后续span。
例如:
span.SetTag(“http.method”,“GET”)
span.SetTag(“http.status_code”,200)
Logs
Logs与tags类似,也是K/V键值对形式。与tags不同的是,logs还会记录写入logs的时间,因此logs主要用于记录某些事件发生的时间。logs的key值同样必须为字符串,但对value类型则没有限制。例如:
span.LogFields(
log.String(“event”, “soft error”),
log.String(“type”, “cache timeout”),
log.Int(“waited.millis”, 1500),
)
SpanContext
SpanContext携带着一些用于跨服务通信的(跨进程)数据,主要包含:
- 足够在系统中标识该span的信息,比如:
span_id,trace_id
。 - Baggage Items,为整条追踪连保存跨服务(跨进程)的K/V格式的用户自定义数据。
Baggage Items
Baggage Items与tags类似,也是K/V键值对。与tags不同的是:
- 其key跟value都只能是字符串格式
- Baggage items不仅当前span可见,其会随着SpanContext传递给后续所有的子span。要小心谨慎的使用baggage items——因为在所有的span中传递这些K,V会带来不小的网络和CPU开销。
References
Opentracing定义了两种引用关系:ChildOf
和FollowFrom
。
ChildOf: 父span的执行依赖子span的执行结果时,此时子span对父span的引用关系是ChildOf
。比如对于一次RPC调用,服务端的span(子span)与客户端调用的span(父span)是ChildOf
关系。
FollowFrom:父span的执不依赖子span执行结果时,此时子span对父span的引用关系是FollowFrom
。FollowFrom
常用于异步调用的表示,例如消息队列中consumer
span与producer
span之间的关系。
Trace
Trace表示一次完整的追踪链路,trace由一个或多个span组成。下图示例表示了一个由8个span组成的trace:
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]
↑
↑
↑
(Span G `FollowsFrom` Span F)
时间轴的展现方式会更容易理解:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
4.基于jaegertracing 实现全链路追踪
认识 Jaeger :
Jaeger 是Uber开发的一套分布式追踪系统,受启发于 dapper 和 OpenZipkin,兼容 OpenTracing 标准,CNCF的开源项目。
Jaeger 系统框架:
- Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。
- Agent - 是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,推荐部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。
- Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。
- Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。
- Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。
作为链路信息的生产者,只需要使用Jaeger-client 的API 写入数据即可。
Jaeger-client 也支持多种语言,在这里使用Jaeger-client-cpp 来展示一下,如何跨请求追踪:
OpenTracing 是将span的SpanContext 注入了HTTPheader 来实现传递span的。
所以我们需要先继承它提供的读写类:
1.继承opentracing::HTTPHeadersWriter 并重写set 函数,将SpanContext 写入HTTPheader
class CustomHeaderWriter : public opentracing::HTTPHeadersWriter{
public:
CustomHeaderWriter(httplib::Headers* headers) {
headers_ =headers;
}
~CustomHeaderWriter(){
headers_=nullptr;
}
opentracing::expected<void> Set(opentracing::string_view key, opentracing::string_view value) const{
headers_->emplace(key.data(), value.data());
opentracing::expected<void> _void;
return _void;
};
private:
httplib::Headers* headers_;
};
2.继承opentracing::HTTPHeadersReader 并重写ForeachKey,从HTTPHeader里将SpanContext读出来
class CustomHeaderReader : public opentracing::HTTPHeadersReader {
public:
CustomHeaderReader(const httplib::Request* req) {
req_ = req;
};
~CustomHeaderReader(){
req_=nullptr;
};
opentracing::expected<void> ForeachKey(
std::function<opentracing::expected<void> (opentracing::string_view key, opentracing::string_view value)> f) const {
for(auto it = req_->headers.begin(); it!=req_->headers.end();it++){
auto result = f(it->first, it->second);
if (!result) return result;
}
return {};
};
private:
const httplib::Request* req_;
};
使用样例
注入请求(假设请求目标为下ExtractDemo):
int InjectDemo(){
//init tracer
auto configYAML = YAML::LoadFile("JaegerConfigPATH");
auto config = jaegertracing::Config::parse(configYAML);
auto tracer = jaegertracing::Tracer::make("servername", config, jaegertracing::logging::consoleLogger());
opentracing::Tracer::InitGlobal( std::static_pointer_cast<opentracing::Tracer>(tracer));
auto root_span = opentracing::Tracer::Global()->StartSpan("start a span");
httplib::Headers headers;
CustomHeaderWriter header_write(&headers);
opentracing::Tracer::Global()->Inject(root_span->context(), header_write); //inject ctx
string host;
httplib::Client cli(host.c_str()); //request host address
string path; //request path
string req_data;
auto res = cli.Post(path.c_str(),headers, req_data, "application/octet-stream"); //transmit root_span.context
if(res->status!=200){
//err
}
root_span->Finish();
opentracing::Tracer::Global()->Close();
return 0;
}
解析请求,并继承上面的span (假设请求来自上InjectDemo)
int ExtractDemo(){
//init tracer
auto configYAML = YAML::LoadFile("JaegerConfigPATH");
auto config = jaegertracing::Config::parse(configYAML);
auto tracer = jaegertracing::Tracer::make("apiservername", config, jaegertracing::logging::consoleLogger());
opentracing::Tracer::InitGlobal( std::static_pointer_cast<opentracing::Tracer>(tracer));
httplib::Server server;
server.Post("/cci_ai/service/v1/layout_analysis", [](const httplib::Request &req, httplib::Response &res) {
CustomHeaderReader carrier(&req);//use a request init a carrier
auto context = opentracing::Tracer::Global()->Extract(carrier); // get a context from a carrier
auto span = opentracing::Tracer::Global()->StartSpan("doLayoutAnalysis",
{ opentracing::ChildOf(context->get()) });//use this context make a child_span
{
//with this span , you can make relation with the request
}
span->Finish();
});
server.listen("0.0.0.0",8080);
opentracing::Tracer::Global()->Close();
return 0;
}
关于配置文件:
config.yaml
disabled: false
reporter:
logSpans: true
sampler:
type: const
param: 1
如果不配置localAgentHostPort 的话,默认链路信息会被127.0.0.1:6831 收集,
如果想指定jaeger-agent ,在reporter下 添加 localAgentHostPort: jaeger-agent:6831
具体描述可以点击查看源项目描述
5.如何查看追踪信息 ?
使用docker 部署一下jaeger-ui:
docker pull jaegertracing/all-in-one:1.21
docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest
访问 http://localhost:16686/search 就可以根据service 查到相关追踪信息了