golang分布式链路追踪 OpenTracing jaeger

追踪(Tracing)

靠人终究靠不住
不知道大家是怎么处理开头提到的那种问题的呢?最简单粗暴的办法就是把相关人员集中到一个会议室里面对数据,怎么对呢?
  客户端开发人员:我查了日志,客户端的请求过程一共用了5s,请求是从几点几分几秒发起的,你们查下服务端的日志;
  交易系统开发人员:我这边是几点几分几秒收到的请求,交易系统一共花了4s多一些,其中调用支付网关花了将近4s,网关那边看下日志吧;
  网关开发人员:我这边是几点几分几秒收到的请求,网关一共花了3s多一点,大部分时间都花在了调用第三方上;
估计大多数人最开始都是这么处理此类问题的,简单粗暴。但如果三天两头给你来这么一下子你还受得了吗?每天给你几百个上千个订单号让你对数据,你还能抽时间写代码吗?估计连带薪上厕所的时间都没了吧。最后这个问题可能传到了领导那里,领导一般喜欢要全局报表数据,你怎么给他出这个报表?是不是束手无策,突然有点想换工作了,哈哈。我们还真是接到过这种需求,一堆人在那里awk然后就没有然后了。
“当一件事情成为一件常态,那意味着我们可能需要一件工具来解放自己了,靠人终究是靠不住的”,就在这种背景之下我们决定引入一个调用链追踪的工具来解放我们,也就是今天的主角jaeger。关于jaeger的说明网上很多,推荐去官网系统的了解一下 https://www.jaegertracing.io,我这里只是把搭建过程和使用上的一些心得分享出来和大家一起交流。

 

背景

 

随着应用容器化和微服务的兴起,借由Docker和 Kubernetes 等工具, 服务的快速开发和部署成为可能,构建微服务应用变得越来越简单。但是随着大型单体应用拆分为微服务,服务之间的依赖和调用变得极为复杂,这些服务可能是不同团队开发的,可能基于不同的语言,微服务之间可能是利用RPC, RESTful API, 也可能是通过消息队列实现调用或通讯。如何理清服务依赖调用关系,如何在这样的环境下快速debug, 追踪服务处理耗时,查找服务性能瓶颈, 合理对服务的容量评估都变成一个棘手的事情。

Tracing在微服务中的作用

 

和传统单体服务不同, 微服务通常部署在一个分布式的系统中, 并且一个请求可能会经过好几个微服务的处理, 这样的环境下错误和性能问题就会更容易发生, 所以观察(Observe)尤为重要,
这就是Tracing的用武之地, 它收集调用过程中的信息并可视化, 让你知道在每一个服务调用过程的耗时等情况, 以便及早发现问题.


在上图可以看到api层一共花了4.03s, 然后其中调用其他服务: 'service-1'花了2.12s, 而service-1又调用了'service-2'花费了2.12s, 用这样的图示很容易就能排查到系统存在的问题. 在这里我只展示了时间, 如果需要追踪其他信息(如错误信息)也是可以实现的.

为什么是Jaeger

 

支持 OpenTracing 的 server 端有很多,我们总要选一个 。在这里,选用 jaeger 。 jaeger 的开发较为活跃,支持的客户端实现也较多。由于采用了 golang 开发,发行包也比较简洁。
jaeger的官网是 www.jaegertracing.io/

特点

 
  1. jaeger的开发语言是golang
  2. jaeger支持OpenTracing协议,同属于CNCF基金会
  3. jaeger支持各种各样的客户端,包括Go、Java、Node、Python、C++等
  4. jaeger支持udp协议传输,当然也支持http

jaeger能够解决以下问题

 
  1. 分布式事务监控
  2. 性能分析与性能优化
  3. 调用链,找到根源问题
  4. 服务依赖分析(需大数据分析)

安装需了解的技术栈:

 
  1. OpenTracing
  2. Golang
  3. ElasticSearch
  4. Kafka (可选)

 

OpenTracing 标准

 

云原生基金会(CNCF) 推出了 OpenTracing 标准,推进Tracing协议和工具的标准化, 统一 Trace 数据结构和格式。 OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。比如从Zipkin替换成Jaeger/Skywalking等后端。
在OpenTracing中,有两个主要概念:
1、Trace(调用链): OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。

2、Span(跨度):可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问. 只要是一个具有完整时间周期的程序访问,都可以被认为是一个Span。

单个Trace中,Span间的因果关系如下图:

 

这里使用目前比较流行的Tracing开源方案Jaeger进行实践,使用jaeger-client-go这个库作为client

github地址:GitHub - jaegertracing/jaeger-client-go: Jaeger Bindings for Go OpenTracing API.

开放分布式追踪(OpenTracing)入门与 Jaeger 实现

jaeger架构

 

jaeger组件介绍:
jaeger-client:jaeger 的客户端,实现了opentracing协议;
jaeger-agent:jaeger client的一个代理程序,client将收集到的调用链数据发给agent,然后由agent发给collector;
jaeger-collector:负责接收jaeger client或者jaeger agent上报上来的调用链数据,然后做一些校验,比如时间范围是否合法等,最终会经过内部的处理存储到后端存储;
jaeger-query:专门负责调用链查询的一个服务,有自己独立的UI;
jaeger-ingester:中文名称“摄食者”,可用从kafka读取数据然后写到jaeger的后端存储,比如Cassandra和Elasticsearch;
spark-job:基于spark的运算任务,可以计算服务的依赖关系,调用次数等;

 

其中jaeger-collector和jaeger-query是必须的,其余的都是可选的,我们没有采用agent上报的方式,而是让客户端直接通过endpoint上报到collector。

官方文档的demo:example
首先,本地起一个jaeger服务作为测试用的服务端,官方提供了”All in One”的docker镜像, 启动Jaeger服务只需要一行代码:

  1. docker run -d --name jaeger
  2. -e COLLECTOR_ZIPKIN_HTTP_PORT=9411
  3. -p 5775:5775/udp
  4. -p 6831:6831/udp
  5. -p 6832:6832/udp
  6. -p 5778:5778
  7. -p 16686:16686
  8. -p 14268:14268
  9. -p 9411:9411
  10. jaegertracing/all-in-one:1.12

本人使用下载好的golang二进制文件启动的,jaeger官网地址:https://www.jaegertracing.io/downlo

 

 

jaeger的二进制发行包包含五个二进制文件:

  1. jaeger-agent
  2. jaeger-collector
  3. jaeger-query
  4. jaeger-standalone
  5. jaeger-ingester

如果没有执行权限,可以使用

  1. chmod a+x jaeger-*

选择存储

 

trace数据总要存在一个地方。jaeger支持 ES 和 Canssandra 两种后端DB。国内用ES的多一点,我们就以ES为例,来介绍其安装方式。
ES请先自行安装。
由于上面四个命令都有很多参数,所以我们可以创建几个脚本,来支持jaeger的启动。
start-collector.sh

 

  1. export SPAN_STORAGE_TYPE=elasticsearch
  1. nohup ./jaeger-collector --es.server-urls http://10.66.177.152:9200/ --log-level=debug > collector.log 2>&1 &

 

start-agent.sh

  1. export SPAN_STORAGE_TYPE=elasticsearch
  2. nohup .jaeger-agent.exe --reporter.grpc.host-port=192.168.1.234:14250  --log-level=debug > agent.log 2>&1 &

start-query.sh

  1. export SPAN_STORAGE_TYPE=elasticsearch
  2. nohup ./jaeger-query --span-storage.type=elasticsearch --es.server-urls=http://10.66.177.152:9200/ > query.log 2>&1 &

 

部署方式

 

jaeger有两种部署方式。下面一一介绍。如果你的数据量特别多,使用kafka缓冲一下也是可以的(所以就引入了另外一个组件jaeger-ingester),不多做介绍。

简易环境

 

 

这种方式一般用在dev环境或者其他测试环境。只需要部署一个单一节点即可。我们的app,需要手动填写agent的地址,这个地址一般都是固定的。
这些环境的流量很小,一个agent是足够的。

 

生产环境

 

 

上面这种部署方式,适合生产环境。agent安装在每一台业务机器上。Client端的目标agent只需要填写localhost即可。
这种方式的好处是生产环境的配置非常的简单。即使你的机器是混合部署的,也能正常收集trace信息。

调用关系图

 

jaeger的调用关系图是使用spark任务进行计算的。项目地址为:
https://github.com/jaegertracing/spark-dependencies

 

端口整理

 

Agent

  1. 5775 UDP协议,接收兼容zipkin的协议数据
  2. 6831 UDP协议,接收兼容jaeger的兼容协议
  3. 6832 UDP协议,接收jaeger的二进制协议
  4. 5778 HTTP协议,数据量大不建议使用

 

它们之间的传输协议都是基于thrift封装的。我们默认使用5775作为传输端口

Collector

  1. 14267 tcp agent发送jaeger.thrift格式数据
  2. 14250 tcp agent发送proto格式数据(背后gRPC)
  3. 14268 http 直接接受客户端数据
  4. 14269 http 健康检查

Query

  1. 16686 http jaeger的前端,放给用户的接口
  2. 16687 http 健康检查

至此,我们的jaeger就安装完毕。
以上,就是我们的环境准备。有了一个server接收数据,调用链的主要工作就在于客户端开发

接下来,代码时间, 参考项目的Readme和搜索引擎不难写出以下代码

 1,最简单的使用模式

package main

import (
    "context"
    "io"
    "time"
    "fmt"


    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
    "github.com/opentracing/opentracing-go/log"
    "github.com/uber/jaeger-client-go/config"



)

/**
初始化
 */
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.2.246:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}

func TestDemo(req string, ctx context.Context) (reply string) {
    // 1. 创建span
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo_1")
    defer func() {
        // 4. 接口调用完,在tag中设置request和reply
        span.SetTag("request", req)
        span.SetTag("reply", reply)
        span.LogFields(
            log.String("event", "你又是谁?"),
            log.String("value", "我是你爷爷!^_^"),
        )
        span.Finish()
    }()

    println(req)
    //2. 模拟耗时
    time.Sleep(time.Second/2)
    //3. 返回reply
    reply = "TestDemoReply"
    return
}

// TestDemo2, 和上面TestDemo 逻辑代码一样
func TestDemo2(req string, ctx context.Context) (reply string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo2_1")
    defer func() {
        span.SetTag("request", req)
        span.SetTag("reply", reply)
        span.LogFields(
            log.String("event", "你是谁?"),
            log.String("value", "我是你爸!^_^"),
        )
        span.Finish()
    }()

    println(req)
    time.Sleep(time.Second/2)
    reply = "TestDemo2Reply"
    return
}

func main() {
    tracer, closer := initJaeger("jager-test-demo")
    defer closer.Close()
    //设置全局的tracer
    opentracing.SetGlobalTracer(tracer)
    //设置父的span
    span := tracer.StartSpan("span_root")
    ctx := opentracing.ContextWithSpan(context.Background(), span)
    r1 := TestDemo("Hello TestDemo", ctx)
    r2 := TestDemo2("Hello TestDemo2", ctx)
    fmt.Println(r1, r2)
    span.Finish()
}
View Code

 2,多个函数之间调用

package main

import (
    "context"
    "io"
    "time"
    "fmt"


    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
    "github.com/opentracing/opentracing-go/log"
    "github.com/uber/jaeger-client-go/config"



)

func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.2.246:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}

func TestDemo(req string, ctx context.Context) (reply string) {
    // 1. 创建span
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo_sl")
    defer span.Finish()
    span.SetTag("request", req)
    span.SetTag("reply", "TestDemo")
    span.LogFields(
        log.String("event", "你又是谁?"),
        log.String("value", "TestDemo^_^"),
    )

    println(req)
    //2. 模拟耗时
    time.Sleep(time.Second/2)
    //3. 返回reply
    reply = "TestDemoReply"
    return
}

// TestDemo2, 和上面TestDemo 逻辑代码一样
func TestDemo2(req string, ctx context.Context) (reply string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo2_sl")
    defer span.Finish()

    span.SetTag("request", req)
    span.SetTag("reply", "TestDemo2")
    span.LogFields(
        log.String("event", "你是谁?"),
        log.String("value", "我是TestDemo2^_^"),
    )

    println(req)
    time.Sleep(time.Second/2)
    reply = "TestDemo2Reply"
    ctx2 := opentracing.ContextWithSpan(ctx, span)
    r3 := TestDemo3("Hello TestDemo3", ctx2)
    fmt.Println(r3)
    return
}

func TestDemo3(req string, ctx context.Context) (reply string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo3_sl")
    defer span.Finish()

    span.SetTag("request", req)
    span.SetTag("reply", "TestDemo3")
    span.LogFields(
        log.String("event", "你是谁?"),
        log.String("value", "TestDemo2的儿子^_^"),
    )

    span.LogKV("event2", "println")

    println(req)
    time.Sleep(time.Second/2)
    reply = "TestDemo2Reply"

    ctx2 := opentracing.ContextWithSpan(ctx, span)
    r3 := TestDemo4("Hello TestDemo3", ctx2)
    fmt.Println(r3)
    return
}

func TestDemo4(req string, ctx context.Context) (reply string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "span_testdemo4_sl")
    defer span.Finish()

    span.SetTag("request", req)
    span.SetTag("reply", "TestDemo4" +
        "+")
    span.LogFields(
        log.String("event", "你是谁?"),
        log.String("value", "TestDemo3的儿子^_^"),
    )

    span.LogKV("event2", "println")

    println(req)
    time.Sleep(time.Second/2)
    reply = "TestDemo2Reply"
    return
}

func main() {
    tracer, closer := initJaeger("jager-test-function")
    defer closer.Close()
    opentracing.SetGlobalTracer(tracer)

    span := tracer.StartSpan("span_root_sl")
    ctx := opentracing.ContextWithSpan(context.Background(), span)
    r1 := TestDemo("Hello TestDemo", ctx)
    r2 := TestDemo2("Hello TestDemo2", ctx)
    fmt.Println(r1, r2)
    span.Finish()
}
View Code

 3,http请求

 clinet中同步请求:8081/format,:8088/publish

package main

import (
    "fmt"
    "context"
    "github.com/opentracing/opentracing-go/ext"
    "io/ioutil"
    "net/http"
    opentracing "github.com/opentracing/opentracing-go"
    jaeger "github.com/uber/jaeger-client-go"
    config "github.com/uber/jaeger-client-go/config"
    "github.com/opentracing/opentracing-go/log"
    "io"
    "net/url"
)

// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.2.246:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}



func main() {
    tracer, closer := initJaeger("http-demo-client")
    defer closer.Close()
    opentracing.SetGlobalTracer(tracer)

    span := tracer.StartSpan("say-hello")
    span.SetTag("hello-to", "helloTo")
    defer span.Finish()

    ctx := opentracing.ContextWithSpan(context.Background(), span)
    helloStr := formatString(ctx, "helloTo")
    printHello(ctx, helloStr)
    fmt.Println("exit")

}

func formatString(ctx context.Context, helloTo string) string {
    span, _ := opentracing.StartSpanFromContext(ctx, "formatString")
    defer span.Finish()

    v := url.Values{}
    v.Set("helloTo", helloTo)
    url := "http://localhost:8081/format?" + v.Encode()
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err.Error())
    }

    ext.SpanKindRPCClient.Set(span)
    ext.HTTPUrl.Set(span, url)
    ext.HTTPMethod.Set(span, "GET")

    span.Tracer().Inject(
        span.Context(),
        opentracing.HTTPHeaders,
        opentracing.HTTPHeadersCarrier(req.Header),
    )

    resp, err := httpDo(req)
    if err != nil {
        panic(err.Error())
    }

    helloStr := string(resp)

    span.LogFields(
        log.String("event", "string-format"),
        log.String("value", helloStr),
    )

    return helloStr
}

func printHello(ctx context.Context, helloStr string) {
    span, _ := opentracing.StartSpanFromContext(ctx, "printHello")
    defer span.Finish()

    v := url.Values{}
    v.Set("helloStr", helloStr)
    url := "http://localhost:8088/publish?" + v.Encode()
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err.Error())
    }

    ext.SpanKindRPCClient.Set(span)
    ext.HTTPUrl.Set(span, url)
    ext.HTTPMethod.Set(span, "GET")
    span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))

    if _, err := httpDo(req); err != nil {
        panic(err.Error())
    }
}

func httpDo(req *http.Request) ([]byte, error) {
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("StatusCode: %d, Body: %s", resp.StatusCode, body)
    }

    return body, nil
}
clinet/main.go
package main

import (
    "fmt"
    "io"
    "log"
    "net/http"

    opentracing "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
    otlog "github.com/opentracing/opentracing-go/log"
    jaeger "github.com/uber/jaeger-client-go"
    config "github.com/uber/jaeger-client-go/config"
)

// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.2.246:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}

func main() {
    tracer, closer := initJaeger("http-formatter")
    defer closer.Close()

    http.HandleFunc("/format", func(w http.ResponseWriter, r *http.Request) {
        spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
        span := tracer.StartSpan("format", ext.RPCServerOption(spanCtx))
        defer span.Finish()

        helloTo := r.FormValue("helloTo")
        helloStr := fmt.Sprintf("Hello, I am format %s!", helloTo)
        span.LogFields(
            otlog.String("event", "string-format"),
            otlog.String("value", helloStr),
        )
        w.Write([]byte(helloStr))
    })

    log.Fatal(http.ListenAndServe(":8081", nil))
}
formater/main.go
package main

import (
    "fmt"
    "io"
    "log"
    "net/http"

    opentracing "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
    otlog "github.com/opentracing/opentracing-go/log"
    jaeger "github.com/uber/jaeger-client-go"
    config "github.com/uber/jaeger-client-go/config"
)

// Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout.
func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.2.246:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}

func main() {
    tracer, closer := initJaeger("http-publish")
    defer closer.Close()

    http.HandleFunc("/publish", func(w http.ResponseWriter, r *http.Request) {
        spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
        span := tracer.StartSpan("format", ext.RPCServerOption(spanCtx))
        defer span.Finish()

        helloTo := r.FormValue("helloTo")
        helloStr := fmt.Sprintf("Hello, I am publish %s!", helloTo)
        span.LogFields(
            otlog.String("event", "string-publish"),
            otlog.String("value", helloStr),
        )
        w.Write([]byte(helloStr))
    })

    log.Fatal(http.ListenAndServe(":8088", nil))
}
publish/main.go

 4,grpc请求-追踪网络调用

 在上面例子中,http之间的链路追踪,我们发现是通过header传递traceI的,但是在grpc中怎么传递呢?

grpc底层采用http2协议也是支持传递数据的,采用的是metadata,

Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。
就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。


Metadata详细介绍参见: https://www.cnblogs.com/sunlong88/p/14339928.html

Prod.proto

syntax="proto3";

package services;
 

message  ProdRequest {
    int32 prod_id =1;   //传入的商品ID
}
message ProdResponse{
    int32 prod_stock=1;//商品库存
}

service ProdService {
    rpc GetProdStock (ProdRequest) returns (ProdResponse);
}

 

gprc-客户端:

 生成文件:

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: Prod.proto

package services

import (
    context "context"
    fmt "fmt"
    proto "github.com/golang/protobuf/proto"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type ProdRequest struct {
    ProdId               int32    `protobuf:"varint,1,opt,name=prod_id,json=prodId,proto3" json:"prod_id,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *ProdRequest) Reset()         { *m = ProdRequest{} }
func (m *ProdRequest) String() string { return proto.CompactTextString(m) }
func (*ProdRequest) ProtoMessage()    {}
func (*ProdRequest) Descriptor() ([]byte, []int) {
    return fileDescriptor_8b02cd6816510a0e, []int{0}
}

func (m *ProdRequest) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_ProdRequest.Unmarshal(m, b)
}
func (m *ProdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_ProdRequest.Marshal(b, m, deterministic)
}
func (m *ProdRequest) XXX_Merge(src proto.Message) {
    xxx_messageInfo_ProdRequest.Merge(m, src)
}
func (m *ProdRequest) XXX_Size() int {
    return xxx_messageInfo_ProdRequest.Size(m)
}
func (m *ProdRequest) XXX_DiscardUnknown() {
    xxx_messageInfo_ProdRequest.DiscardUnknown(m)
}

var xxx_messageInfo_ProdRequest proto.InternalMessageInfo

func (m *ProdRequest) GetProdId() int32 {
    if m != nil {
        return m.ProdId
    }
    return 0
}

type ProdResponse struct {
    ProdStock            int32    `protobuf:"varint,1,opt,name=prod_stock,json=prodStock,proto3" json:"prod_stock,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *ProdResponse) Reset()         { *m = ProdResponse{} }
func (m *ProdResponse) String() string { return proto.CompactTextString(m) }
func (*ProdResponse) ProtoMessage()    {}
func (*ProdResponse) Descriptor() ([]byte, []int) {
    return fileDescriptor_8b02cd6816510a0e, []int{1}
}

func (m *ProdResponse) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_ProdResponse.Unmarshal(m, b)
}
func (m *ProdResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_ProdResponse.Marshal(b, m, deterministic)
}
func (m *ProdResponse) XXX_Merge(src proto.Message) {
    xxx_messageInfo_ProdResponse.Merge(m, src)
}
func (m *ProdResponse) XXX_Size() int {
    return xxx_messageInfo_ProdResponse.Size(m)
}
func (m *ProdResponse) XXX_DiscardUnknown() {
    xxx_messageInfo_ProdResponse.DiscardUnknown(m)
}

var xxx_messageInfo_ProdResponse proto.InternalMessageInfo

func (m *ProdResponse) GetProdStock() int32 {
    if m != nil {
        return m.ProdStock
    }
    return 0
}

func init() {
    proto.RegisterType((*ProdRequest)(nil), "services.ProdRequest")
    proto.RegisterType((*ProdResponse)(nil), "services.ProdResponse")
}

func init() { proto.RegisterFile("Prod.proto", fileDescriptor_8b02cd6816510a0e) }

var fileDescriptor_8b02cd6816510a0e = []byte{
    // 149 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x0a, 0x28, 0xca, 0x4f,
    0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x28, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x2d,
    0x56, 0x52, 0xe3, 0xe2, 0x06, 0x89, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x89, 0x73,
    0xb1, 0x17, 0x14, 0xe5, 0xa7, 0xc4, 0x67, 0xa6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0xb1,
    0x81, 0xb8, 0x9e, 0x29, 0x4a, 0xba, 0x5c, 0x3c, 0x10, 0x75, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9,
    0x42, 0xb2, 0x5c, 0x5c, 0x60, 0x85, 0xc5, 0x25, 0xf9, 0xc9, 0xd9, 0x50, 0xb5, 0x9c, 0x20, 0x91,
    0x60, 0x90, 0x80, 0x91, 0x0f, 0xc4, 0xd8, 0x60, 0x88, 0x35, 0x42, 0xb6, 0x5c, 0x3c, 0xee, 0xa9,
    0x25, 0x01, 0x30, 0x69, 0x21, 0x51, 0x3d, 0x98, 0x03, 0xf4, 0x90, 0x6c, 0x97, 0x12, 0x43, 0x17,
    0x86, 0x58, 0x96, 0xc4, 0x06, 0x76, 0xb5, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x78, 0x03,
    0x6e, 0xc3, 0x00, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// ProdServiceClient is the client API for ProdService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ProdServiceClient interface {
    GetProdStock(ctx context.Context, in *ProdRequest, opts ...grpc.CallOption) (*ProdResponse, error)
}

type prodServiceClient struct {
    cc *grpc.ClientConn
}

func NewProdServiceClient(cc *grpc.ClientConn) ProdServiceClient {
    return &prodServiceClient{cc}
}

func (c *prodServiceClient) GetProdStock(ctx context.Context, in *ProdRequest, opts ...grpc.CallOption) (*ProdResponse, error) {
    out := new(ProdResponse)
    err := c.cc.Invoke(ctx, "/services.ProdService/GetProdStock", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// ProdServiceServer is the server API for ProdService service.
type ProdServiceServer interface {
    GetProdStock(context.Context, *ProdRequest) (*ProdResponse, error)
}

// UnimplementedProdServiceServer can be embedded to have forward compatible implementations.
type UnimplementedProdServiceServer struct {
}

func (*UnimplementedProdServiceServer) GetProdStock(ctx context.Context, req *ProdRequest) (*ProdResponse, error) {
    return nil, status.Errorf(codes.Unimplemented, "method GetProdStock not implemented")
}

func RegisterProdServiceServer(s *grpc.Server, srv ProdServiceServer) {
    s.RegisterService(&_ProdService_serviceDesc, srv)
}

func _ProdService_GetProdStock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(ProdRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(ProdServiceServer).GetProdStock(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/services.ProdService/GetProdStock",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(ProdServiceServer).GetProdStock(ctx, req.(*ProdRequest))
    }
    return interceptor(ctx, in, info, handler)
}

var _ProdService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "services.ProdService",
    HandlerType: (*ProdServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "GetProdStock",
            Handler:    _ProdService_GetProdStock_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "Prod.proto",
}
View Code
package until

import (
    "encoding/base64"
    "strings"

    "fmt"

    "google.golang.org/grpc/metadata"
)

const (
    binHdrSuffix = "-bin"
)

// metadataTextMap extends a metadata.MD to be an opentracing textmap
type MetadataTextMap metadata.MD

// Set is a opentracing.TextMapReader interface that extracts values.
func (m MetadataTextMap) Set(key, val string) {
    // gRPC allows for complex binary values to be written.
    encodedKey, encodedVal := encodeKeyValue(key, val)
    // The metadata object is a multimap, and previous values may exist, but for opentracing headers, we do not append
    // we just override.
    m[encodedKey] = []string{encodedVal}
}

// ForeachKey is a opentracing.TextMapReader interface that extracts values.
func (m MetadataTextMap) ForeachKey(callback func(key, val string) error) error {
    for k, vv := range m {
        for _, v := range vv {
            if decodedKey, decodedVal, err := metadata.DecodeKeyValue(k, v); err == nil {
                if err = callback(decodedKey, decodedVal); err != nil {
                    return err
                }
            } else {
                return fmt.Errorf("failed decoding opentracing from gRPC metadata: %v", err)
            }
        }
    }
    return nil
}

// encodeKeyValue encodes key and value qualified for transmission via gRPC.
// note: copy pasted from private values of grpc.metadata
func encodeKeyValue(k, v string) (string, string) {
    k = strings.ToLower(k)
    if strings.HasSuffix(k, binHdrSuffix) {
        val := base64.StdEncoding.EncodeToString([]byte(v))
        v = string(val)
    }
    return k, v
}
trace_util.go
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/uber/jaeger-client-go v1.6.0 h1:3+zLlq+4npI5fg8IsgAje3YsP7TcEdNzJScyqFIzxEQ=
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo=
github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=
github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
go.sum
package main

import (
    "context"
    "fmt"
    "gprccli/until"

    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "gprccli/services"
    "io"
    logger "log"

    opentracing "github.com/opentracing/opentracing-go"
    jaeger "github.com/uber/jaeger-client-go"
    config "github.com/uber/jaeger-client-go/config"
)

func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.1.234:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}

func main(){

    tracer, closer := initJaeger("grpc-client")
    defer closer.Close()
    opentracing.SetGlobalTracer(tracer)

    md := metadata.Pairs("key1","val1","key2","val2","key3","val3")
    ctx := metadata.NewOutgoingContext(context.Background(),md)


    span := tracer.StartSpan("say-hello")
    span.SetTag("hello-to", "helloTo")
    defer span.Finish()

    ctx = opentracing.ContextWithSpan(ctx, span)



    md, ok := metadata.FromOutgoingContext(ctx)
    if !ok {
        md = metadata.Pairs()
    }

    if err := tracer.Inject(span.Context(), opentracing.HTTPHeaders,  until.MetadataTextMap(md)); err != nil {
        fmt.Println(ctx, "grpc_opentracing: failed serializing trace information: %v", err)
    }

    ctx = metadata.NewOutgoingContext(ctx, md)
    //ctx = metadata.AppendToOutgoingContext(ctx, util.TraceID, logs.GetTraceId(ctx))
    ctx = opentracing.ContextWithSpan(ctx, span)



    conn,err:=grpc.Dial(":8089",grpc.WithInsecure())
    if err!=nil{
        logger.Fatal(err)
    }
    defer conn.Close()

    prodClient:=services.NewProdServiceClient(conn)
    prodRes,err:=prodClient.GetProdStock(ctx,
        &services.ProdRequest{ProdId:12})
    if err!=nil{
        logger.Fatal(err)
    }
    fmt.Println(prodRes.ProdStock)
}
main.go

gprc-服务端

生成文件

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: Prod.proto

package services

import (
    context "context"
    fmt "fmt"
    proto "github.com/golang/protobuf/proto"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type ProdRequest struct {
    ProdId               int32    `protobuf:"varint,1,opt,name=prod_id,json=prodId,proto3" json:"prod_id,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *ProdRequest) Reset()         { *m = ProdRequest{} }
func (m *ProdRequest) String() string { return proto.CompactTextString(m) }
func (*ProdRequest) ProtoMessage()    {}
func (*ProdRequest) Descriptor() ([]byte, []int) {
    return fileDescriptor_8b02cd6816510a0e, []int{0}
}

func (m *ProdRequest) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_ProdRequest.Unmarshal(m, b)
}
func (m *ProdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_ProdRequest.Marshal(b, m, deterministic)
}
func (m *ProdRequest) XXX_Merge(src proto.Message) {
    xxx_messageInfo_ProdRequest.Merge(m, src)
}
func (m *ProdRequest) XXX_Size() int {
    return xxx_messageInfo_ProdRequest.Size(m)
}
func (m *ProdRequest) XXX_DiscardUnknown() {
    xxx_messageInfo_ProdRequest.DiscardUnknown(m)
}

var xxx_messageInfo_ProdRequest proto.InternalMessageInfo

func (m *ProdRequest) GetProdId() int32 {
    if m != nil {
        return m.ProdId
    }
    return 0
}

type ProdResponse struct {
    ProdStock            int32    `protobuf:"varint,1,opt,name=prod_stock,json=prodStock,proto3" json:"prod_stock,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *ProdResponse) Reset()         { *m = ProdResponse{} }
func (m *ProdResponse) String() string { return proto.CompactTextString(m) }
func (*ProdResponse) ProtoMessage()    {}
func (*ProdResponse) Descriptor() ([]byte, []int) {
    return fileDescriptor_8b02cd6816510a0e, []int{1}
}

func (m *ProdResponse) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_ProdResponse.Unmarshal(m, b)
}
func (m *ProdResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_ProdResponse.Marshal(b, m, deterministic)
}
func (m *ProdResponse) XXX_Merge(src proto.Message) {
    xxx_messageInfo_ProdResponse.Merge(m, src)
}
func (m *ProdResponse) XXX_Size() int {
    return xxx_messageInfo_ProdResponse.Size(m)
}
func (m *ProdResponse) XXX_DiscardUnknown() {
    xxx_messageInfo_ProdResponse.DiscardUnknown(m)
}

var xxx_messageInfo_ProdResponse proto.InternalMessageInfo

func (m *ProdResponse) GetProdStock() int32 {
    if m != nil {
        return m.ProdStock
    }
    return 0
}

func init() {
    proto.RegisterType((*ProdRequest)(nil), "services.ProdRequest")
    proto.RegisterType((*ProdResponse)(nil), "services.ProdResponse")
}

func init() { proto.RegisterFile("Prod.proto", fileDescriptor_8b02cd6816510a0e) }

var fileDescriptor_8b02cd6816510a0e = []byte{
    // 149 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x0a, 0x28, 0xca, 0x4f,
    0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x28, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x2d,
    0x56, 0x52, 0xe3, 0xe2, 0x06, 0x89, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x89, 0x73,
    0xb1, 0x17, 0x14, 0xe5, 0xa7, 0xc4, 0x67, 0xa6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0xb1,
    0x81, 0xb8, 0x9e, 0x29, 0x4a, 0xba, 0x5c, 0x3c, 0x10, 0x75, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9,
    0x42, 0xb2, 0x5c, 0x5c, 0x60, 0x85, 0xc5, 0x25, 0xf9, 0xc9, 0xd9, 0x50, 0xb5, 0x9c, 0x20, 0x91,
    0x60, 0x90, 0x80, 0x91, 0x0f, 0xc4, 0xd8, 0x60, 0x88, 0x35, 0x42, 0xb6, 0x5c, 0x3c, 0xee, 0xa9,
    0x25, 0x01, 0x30, 0x69, 0x21, 0x51, 0x3d, 0x98, 0x03, 0xf4, 0x90, 0x6c, 0x97, 0x12, 0x43, 0x17,
    0x86, 0x58, 0x96, 0xc4, 0x06, 0x76, 0xb5, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x78, 0x03,
    0x6e, 0xc3, 0x00, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// ProdServiceClient is the client API for ProdService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ProdServiceClient interface {
    GetProdStock(ctx context.Context, in *ProdRequest, opts ...grpc.CallOption) (*ProdResponse, error)
}

type prodServiceClient struct {
    cc *grpc.ClientConn
}

func NewProdServiceClient(cc *grpc.ClientConn) ProdServiceClient {
    return &prodServiceClient{cc}
}

func (c *prodServiceClient) GetProdStock(ctx context.Context, in *ProdRequest, opts ...grpc.CallOption) (*ProdResponse, error) {
    out := new(ProdResponse)
    err := c.cc.Invoke(ctx, "/services.ProdService/GetProdStock", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// ProdServiceServer is the server API for ProdService service.
type ProdServiceServer interface {
    GetProdStock(context.Context, *ProdRequest) (*ProdResponse, error)
}

// UnimplementedProdServiceServer can be embedded to have forward compatible implementations.
type UnimplementedProdServiceServer struct {
}

func (*UnimplementedProdServiceServer) GetProdStock(ctx context.Context, req *ProdRequest) (*ProdResponse, error) {
    return nil, status.Errorf(codes.Unimplemented, "method GetProdStock not implemented")
}

func RegisterProdServiceServer(s *grpc.Server, srv ProdServiceServer) {
    s.RegisterService(&_ProdService_serviceDesc, srv)
}

func _ProdService_GetProdStock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    in := new(ProdRequest)
    if err := dec(in); err != nil {
        return nil, err
    }
    if interceptor == nil {
        return srv.(ProdServiceServer).GetProdStock(ctx, in)
    }
    info := &grpc.UnaryServerInfo{
        Server:     srv,
        FullMethod: "/services.ProdService/GetProdStock",
    }
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(ProdServiceServer).GetProdStock(ctx, req.(*ProdRequest))
    }
    return interceptor(ctx, in, info, handler)
}

var _ProdService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "services.ProdService",
    HandlerType: (*ProdServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "GetProdStock",
            Handler:    _ProdService_GetProdStock_Handler,
        },
    },
    Streams:  []grpc.StreamDesc{},
    Metadata: "Prod.proto",
}
View Code
package services

import (
    "context"
    "fmt"
    "github.com/opentracing/opentracing-go/ext"
    "google.golang.org/grpc/grpclog"
    "google.golang.org/grpc/metadata"
    "io"
    "grpcpro/until"

    opentracing "github.com/opentracing/opentracing-go"
    jaeger "github.com/uber/jaeger-client-go"
    config "github.com/uber/jaeger-client-go/config"
)

type ProdService struct {

}

func(this *ProdService) GetProdStock(ctx context.Context, request *ProdRequest) (*ProdResponse, error) {
        md,ok :=metadata.FromIncomingContext(ctx)
        fmt.Println(md,ok)
        if !ok {
            md = metadata.New(nil)
        }
        tracer, closer := initJaeger("grpc-server")
        defer closer.Close()
        spanContext, err := tracer.Extract(opentracing.HTTPHeaders,until.MetadataTextMap(md))
        if err != nil && err != opentracing.ErrSpanContextNotFound {
            grpclog.Errorf("extract from metadata err %v", err)
        }
        //开始追踪该方法
        serverSpan := tracer.StartSpan(
            "grpc-server-name",
            ext.RPCServerOption(spanContext),
            ext.SpanKindRPCServer,
        )
        serverSpan.SetTag("grpc-server-tag", "grpc-server-tag-value")
        ctx = opentracing.ContextWithSpan(ctx, serverSpan)

        defer serverSpan.Finish()

          return &ProdResponse{ProdStock:20},nil
}


func initJaeger(service string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        Sampler:&config.SamplerConfig{
            Type:     "const",
            Param:1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            //LocalAgentHostPort:  "192.168.1.234:6831",
            LocalAgentHostPort:  "192.168.1.234:6831",
        },
    }
    tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger))
    if err != nil {
        panic(fmt.Sprintf("Error: connot init Jaeger: %v
", err))
    }
    return tracer, closer
}
ProdService.go
package until

import (
    "encoding/base64"
    "strings"

    "fmt"

    "google.golang.org/grpc/metadata"
)

const (
    binHdrSuffix = "-bin"
)

// metadataTextMap extends a metadata.MD to be an opentracing textmap
type MetadataTextMap metadata.MD

// Set is a opentracing.TextMapReader interface that extracts values.
func (m MetadataTextMap) Set(key, val string) {
    // gRPC allows for complex binary values to be written.
    encodedKey, encodedVal := encodeKeyValue(key, val)
    // The metadata object is a multimap, and previous values may exist, but for opentracing headers, we do not append
    // we just override.
    m[encodedKey] = []string{encodedVal}
}

// ForeachKey is a opentracing.TextMapReader interface that extracts values.
func (m MetadataTextMap) ForeachKey(callback func(key, val string) error) error {
    for k, vv := range m {
        for _, v := range vv {
            if decodedKey, decodedVal, err := metadata.DecodeKeyValue(k, v); err == nil {
                if err = callback(decodedKey, decodedVal); err != nil {
                    return err
                }
            } else {
                return fmt.Errorf("failed decoding opentracing from gRPC metadata: %v", err)
            }
        }
    }
    return nil
}

// encodeKeyValue encodes key and value qualified for transmission via gRPC.
// note: copy pasted from private values of grpc.metadata
func encodeKeyValue(k, v string) (string, string) {
    k = strings.ToLower(k)
    if strings.HasSuffix(k, binHdrSuffix) {
        val := base64.StdEncoding.EncodeToString([]byte(v))
        v = string(val)
    }
    return k, v
}
trace_util
module grpcpro

go 1.12

require (
    github.com/golang/protobuf v1.4.3
    github.com/ibinarytree/koala v1.9.15
    github.com/opentracing/opentracing-go v1.2.0
    github.com/pkg/errors v0.9.1 // indirect
    github.com/uber/jaeger-client-go v2.29.1+incompatible
    github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
    go.uber.org/atomic v1.8.0 // indirect
    google.golang.org/grpc v1.35.0
)

replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
go.sum
package main

import (
    "google.golang.org/grpc"
    "grpcpro/services"
    "net"
)

func main()  {
    rpcServer:=grpc.NewServer()
    services.RegisterProdServiceServer(rpcServer,new(services.ProdService))

    lis,_:=net.Listen("tcp",":8089")

    rpcServer.Serve(lis)


}
server.go

运行客户端:

D:gocode1.14codegrpccli>go run main.go
2021/07/14 19:54:32 debug logging disabled
2021/07/14 19:54:32 Initializing logging reporter
2021/07/14 19:54:32 debug logging disabled
20
2021/07/14 19:54:33 Reporting span 1681e58f68e9d7f9:1681e58f68e9d7f9:0000000000000000:1

运行服务端:

D:gocode1.14codegrpcpro>go run server.go
map[:authority:[:8089] content-type:[application/grpc] key1:[val1] key2:[val2] key3:[val3] uber-trace-id:[1681e58f68e9d7f9:1681e58f68e9d7f9:0000000000000000:1] user-agent:[grpc-go/1.25.1]] true
2021/07/14 19:54:33 debug logging disabled
2021/07/14 19:54:33 Initializing logging reporter
2021/07/14 19:54:33 debug logging disabled
2021/07/14 19:54:33 Reporting span 1681e58f68e9d7f9:1b9d237a6dd28aca:1681e58f68e9d7f9:1

最终显示查询结果:

gin中间件中链路追踪http中间件 gprc中间件待完善中

 项目源码地址:https://github.com/sunlongv520/go-jaeger-openstracing

原文地址:https://www.cnblogs.com/sunlong88/p/14324911.html