追蹤(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/
特點
- jaeger的開發語言是
golang
- jaeger支持OpenTracing協議,同屬於CNCF基金會
- jaeger支持各種各樣的客戶端,包括Go、Java、Node、Python、C++等
- jaeger支持udp協議傳輸,當然也支持http
jaeger能夠解決以下問題
- 分布式事務監控
- 性能分析與性能優化
- 調用鏈,找到根源問題
- 服務依賴分析(需大數據分析)
安裝需了解的技術棧:
- OpenTracing
- Golang
- ElasticSearch
- 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服務只需要一行代碼:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:1.12
本人使用下載好的golang二進制文件啟動的,jaeger官網地址:https://www.jaegertracing.io/downlo
jaeger的二進制發行包包含五個二進制文件:
- jaeger-agent
- jaeger-collector
- jaeger-query
- jaeger-standalone
- jaeger-ingester
如果沒有執行權限,可以使用
- chmod a+x jaeger-*
選擇存儲
trace數據總要存在一個地方。jaeger支持 ES
和 Canssandra
兩種后端DB。國內用ES的多一點,我們就以ES為例,來介紹其安裝方式。
ES請先自行安裝。
由於上面四個命令都有很多參數,所以我們可以創建幾個腳本,來支持jaeger的啟動。start-collector.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- nohup ./jaeger-collector --es.server-urls http://10.66.177.152:9200/ --log-level=debug > collector.log 2>&1 &
start-agent.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- nohup .\jaeger-agent.exe --reporter.grpc.host-port=192.168.1.234:14250 --log-level=debug > agent.log 2>&1 &
start-query.sh
- export SPAN_STORAGE_TYPE=elasticsearch
- 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
- 5775 UDP協議,接收兼容zipkin的協議數據
- 6831 UDP協議,接收兼容jaeger的兼容協議
- 6832 UDP協議,接收jaeger的二進制協議
- 5778 HTTP協議,數據量大不建議使用
它們之間的傳輸協議都是基於thrift封裝的。我們默認使用5775作為傳輸端口
Collector
- 14267 tcp agent發送jaeger.thrift格式數據
- 14250 tcp agent發送proto格式數據(背后gRPC)
- 14268 http 直接接受客戶端數據
- 14269 http 健康檢查
Query
- 16686 http jaeger的前端,放給用戶的接口
- 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\n", 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() }
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\n", 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() }
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\n", 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 }

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\n", 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)) }

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\n", 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)) }
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", }

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 }

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=

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\n", 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) }
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", }

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\n", err)) } return tracer, closer }

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 }

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

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) }
運行客戶端:
D:\gocode1.14\code\grpccli>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.14\code\grpcpro>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