分布式链路追踪实践(二) - 基于 OpenTracing 设计通用的链路追踪设计

上一章中有对分布式追踪链路方案 (Jaeger, Zipkin, SkyWalking 等) 进行了介绍,不同的方案适用不同场景、团队、开发语言。在接入分布式链路追踪过程中,会有不同的团队使用不同的方案实现,如 A 团队使用的是 Zipkin, 而 B 团队使用的 Jaeger, 这样就会存在不同追踪系统的 API 兼容问题 (切换方案会带来比较大的改动),为了解决这个问题,诞生了 OpenTracing 规范。

OpenTracing 规范

OpenTracing 是⼀个轻量级的标准化层,它位于应⽤程序/类库和追踪或⽇志分析程序之间,是⼀套分布式追踪协议,与平台,语⾔⽆关,统⼀接⼝,⽅便开发接⼊不同的分布式追踪系统。

  • 语义规范 : 描述定义的数据模型 Tracer,Sapn 和 SpanContext 等;

  • 语义惯例 : 罗列出 tag 和 logging 操作时,标准的key值;

OpenTracing

OpenTracing API

Trace

OpenTracing 中的 Trace(调⽤链)通过归属此链的 Span 来隐性定义。⼀条 Trace 可以认为⼀个有多个 Span 组成的有向⽆环图(DAG图),Span 是⼀个逻辑执⾏单元,Span 与 Span 的因果关系命名为 References。

OpenTracing 定义两种关系:

  • Childof:如下例⼦中, SpanC 是 childof SpanA
  • FollowsFrom:如下例⼦中,SpanG 是 followsFrom SpanF
Trace
  • SpanC 是 childof SpanA, SpanG 是 FollowsFrom SpanF

  • childof指的是垂直链路, FollowsFrom 指的是横向链路

Span

Span封装了如下状态

  • 操作名称

  • 开始时间戳

  • 结束时间戳

  • ⼀组零或多个键:值结构的 Span标签 (Tags)。键必须是字符串。值可以是字符串,布尔或数值类型.

  • ⼀组零或多个 Span⽇志 (Logs),其中每个都是⼀个键:值映射并与⼀个时间戳配对。键必须是字符串,值可以是任何类型。 并⾮所有的 OpenTracing 实现都必须⽀持每种值
    类型。

  • ⼀个 SpanContext

  • 零或多个因果相关的 Span 间的 References (通过那些相关的 Span 的 SpanContext )

SpanContext 封装了如下状态

  • 任何需要跟跨进程 Span 关联的,依赖于 OpenTracing 实现的状态(例如 Trace 和 Span 的 id)

  • 键:值 结构的跨进程的 Baggage Items(区别于 span tag,baggage 是全局范围,在 span 间保持传递,⽽tag 是 span 内部,不会被⼦ span 继承使⽤。)

Trace/Span Identity

Trace/Span Identity 
Key 
uber-trace-id
▪ Case-insensitive in HTTP
▪ Lower-case in protocols that preserve header case
Value 
{trace-id}:{span-id}:{parent-span-id}:{flags}
▪ {trace-id} ▪ 64-bit or 128-bit random number in base16 format
▪ Can be variable length, shorter values are 0-padded on the left
▪ Clients in some languages support 128-bit, migration pending
▪ Value of 0 is not valid
▪ {span-id} ▪ 64-bit random number in base16 format
▪ Value of 0 is not valid
▪ {parent-span-id} ▪ 64-bit value in base16 format representing parent span id
▪ Deprecated, most Jaeger clients ignore on the receiving side, but still include it on the sending side
▪ 0 value is valid and means “root span” (when not ignored)
▪ {flags} ▪ One byte bitmap, as two hex digits
▪ Bit 1 (right-most, least significant, bit mask 0x01) is “sampled” flag
▪ 1 means the trace is sampled and all downstream services are advised to respect that
▪ 0 means the trace is not sampled and all downstream services are advised to respect that
▪ We’re considering a new feature that allows downstream services to upsample if they find their tracing level is too low
▪ Bit 2 (bit mask 0x02 ) is “debug” flag
▪ Debug flag should only be set when the sampled flag is set
▪ Instructs the backend to try really hard not to drop this trace
▪ Bit 3 (bit mask 0x04 ) is not used
▪ Bit 4 (bit mask 0x08 ) is “firehose” flag
▪ Spans tagged as “firehose” are excluded from being indexed in the storage
▪ The traces can only be retrieved by trace ID (usually available from other sources, like logs)
▪ Other bits are unused

Inject 和 Extract 操作

  1. 跨进程,机器通讯,通过传递 Spancontext 来提供⾜够的信息建⽴ span 间的关系。

  2. SpanContext 通过 Inject 操作向 Carrier 中增加,传递后通过 Extracted 从 Carrier 中取出。

Inject

// TracerWrapper tracer wrapper
func AddTracer(ctx context.Context, r *http.Request, tracer opentracing.Tracer, tags map[string]string) context.Context {
    //初始化 tracer
    opentracing.InitGlobalTracer(tracer)
    var sp opentracing.Span
    //从 header 中获取 span
    spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, 
        opentracing.HTTPHeadersCarrier(r.Header))
    if spanCtx != nil {
        sp = opentracing.GlobalTracer().StartSpan(r.URL.Path, opentracing.ChildOf(spanCtx))
    }else{
    //如果 header 中没有携带 context, 则新建 span
        sp = tracer.StartSpan(r.URL.Path)
    }
    //写入 tag 或者 日志
    for k, v := range tags {
        // sp.LogKV(k, v)
        // sp.SetTag(k, v)
        sp.LogFields(
            spanLog.String(k, v),
        )
    }
    //注入span (用于传递)
    if err := opentracing.GlobalTracer().Inject(
        sp.Context(),
        opentracing.HTTPHeaders,
        opentracing.HTTPHeadersCarrier(r.Header)); err != nil {
        logger.Fatalln("inject failed", err)
    }
    ...
}

Extracted

// TracerWrapper tracer wrapper
 func AddTracer(ctx context.Context, r *http.Request, tracer opentracing.Tracer, tags map[string]string) context.Context {
    //初始化 tracer
    opentracing.InitGlobalTracer(tracer)
    var sp opentracing.Span
    //从 header 中获取 span
    spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, 
        opentracing.HTTPHeadersCarrier(r.Header))
    if spanCtx != nil {
        sp = opentracing.GlobalTracer().StartSpan(r.URL.Path, opentracing.ChildOf(spanCtx))
    }else{
    //如果 header 中没有携带 context, 则新建 span
        sp = tracer.StartSpan(r.URL.Path)
    }
    //写入 tag 或者 日志
    for k, v := range tags {
        // sp.LogKV(k, v)
        // sp.SetTag(k, v)
        sp.LogFields(
            spanLog.String(k, v),
        )
    }
    ...
 }
  1. 分布式跟踪(包扩跨服务,进程,主机等)需要对 trace 上下⽂ (spanContext) 进⾏传递,通过 Inject ⽅法, 在 io.Writer 中注⼊上下⽂信息,服务端通过 Extracted 取出,通过对上下⽂和业务逻辑判断 span 之间的关系(childof or followsfrom)。

Sampling,采样

OpenTracing API 不强调采样的概念,但是⼤多数追踪系统通过不同⽅式实现采样。有些情况下,应⽤系统需要通知追踪程序,这条特定的调⽤需要被记录,即使根据默认采样规则,它不需要被记录。sampling.priority tag 提供这样的⽅式。追踪系统不保证⼀定采纳这个参数,但是会尽可能的保留这条调⽤。

sampling.priority - integer

  • 如果⼤于 0, 追踪系统尽可能保存这条调⽤链
  • 等于 0, 追踪系统不保存这条调⽤链
  • 如果此tag没有提供,追踪系统使⽤⾃⼰的默认采样规则

采样速率

⽣产环境系统性能很重要,所以对于所有的请求都开启 Trace 显然会带来⽐较⼤的压⼒,另外,⼤量的数据也会带来很⼤存储压⼒。为此,jaeger ⽀持设置采样速率,根据系统实际情况设置合适的采样频率。

Jaeger 官⽅提供了多种采集策略,使⽤者可以按需选择使⽤:

  • const,全量采集,采样率设置0,1 分别对应打开和关闭;
  • probabilistic,概率采集,默认万份之⼀,0~1之间取值;
  • rateLimiting,限速采集,每秒只能采集⼀定量的数据;
  • remote,⼀种动态采集策略,根据当前系统的访问量调节采集策略;

总结

  1. OpenTracing 设计类似于 Interface 抽象层设计, 很适合不同方案兼容的场景, 并且具有很好的扩展性, 后期改动成本较小;
  2. 适合生产方案迁移 ;

在生产中使用 OpenTracing 需要考虑兼容不同协议的链路追踪问题,下一章和大家分享 支持 HTTP 和 gRPC 分布式链路追踪 SDK 设计 。

原文: 分布式链路追踪实践(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容