分布式服务中,通过日志查看错误以及追踪问题,是一件非常痛苦的事情,由此我们可以使用链路追踪来快速定位问题,查看服务调用请用情况,埋点做一些必要的参数标记,此处使用uber开源的jaeger,它完美实现了openTraceing 标准,支持不同语言的客户端,其它的不再赘述了,网上很多的先关学习文档,这里只给出简单的使用场景,为了快速展示应用,直接用docker启动一个服务,jaegertracing/all-in-one,自行参考官网文档
打开jaeger-ui的界面如下
package main
import (
"context"
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
"io"
"log"
"strings"
)
//初始化tracer
func InitTracer() (opentracing.Tracer,io.Closer){
cfg := &config.Configuration{
ServiceName: "zzy_jaeger_test",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,//固定采样
Param: 1,//1全采样,0不采样
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.1.246:6831",
},
}
//创建tracer
tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger))
//设置全局tracer
opentracing.SetGlobalTracer(tracer)
if err != nil {
log.Fatal(err)
}
return tracer,closer
}
func main() {
trace, closer := InitTracer()
defer closer.Close()
// 创建父span
spanRoot := trace.StartSpan("rootSpan 埋点")
// 在函数返回的时候调用finish结束这个span
defer spanRoot.Finish()
spanRoot.SetTag("gender","男")
spanRoot.SetBaggageItem("foo","todo")
//#创建上下文,使用上下文来传递span
ctx := opentracing.ContextWithSpan(context.Background(), spanRoot)
ct := spanRoot.Context().(jaeger.SpanContext)
fmt.Println("traceID:",ct.TraceID().String())
// 创建一个childspan
//childspan:=trace.StartSpan("childSpan 埋点",opentracing.ChildOf(spanRoot.Context()))
//defer childspan.Finish()
//# 下面为你的业务代码
//videoURL := c.DefaultPostForm("url", "")
//将ctx上下文传到调用的函数里
ExtractVideo(strings.TrimSpace("媳妇"), ctx)
}
func ExtractVideo(videoURL string, ctx context.Context) {
//创建子span
span, _ := opentracing.StartSpanFromContext(ctx, "childSpan 埋点")
defer func() {
span.SetTag("媳妇", videoURL)
span.Finish()
}()
}
直接运行,然后在jaeger-ui上查看service,选择最下面的trace,就可以看到刚才的追踪,以及经过的span,点击这个追踪的详情,可以看得到所有设置的tags的值
上边的是最基础简单的例子,好了,下面看看在web服务应用中的使用技巧了
package main
import (
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
"io"
"log"
"net/http"
)
func NewTrace() (opentracing.Tracer,io.Closer) {
conf := &config.Configuration{
ServiceName: "trace_srv",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst, //固定采样
Param: 1, //1全采样,0不采样
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.1.246:6831",
},
}
trace, closer, err := conf.NewTracer()
if err != nil {
log.Fatal(err)
}
//设置全局trace,一个微服务对应一个trace的service
opentracing.SetGlobalTracer(trace)
return trace, closer
}
//中间件
func tracingMW(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tracer, closer := NewTrace()
defer closer.Close()
//以请求的url为operatorName
rootSpan := tracer.StartSpan(r.URL.Path)
defer rootSpan.Finish()
//随便记录一些参数
rootSpan.SetTag("Method",r.Method)
rootSpan.SetTag("url",r.URL.Path)
//打印traceID
sctx := rootSpan.Context().(jaeger.SpanContext)
fmt.Println("traceID:",sctx.TraceID().String())
//利用context传递span在下一个请求中获取chidspan
ctx := opentracing.ContextWithSpan(r.Context(), rootSpan)
next.ServeHTTP(w,r.WithContext(ctx))
})
}
func test(w http.ResponseWriter, r *http.Request) {
//从context中拿childspan
span, _ := opentracing.StartSpanFromContext(r.Context(), "test")
defer span.Finish()
ctx := span.Context().(jaeger.SpanContext)
fmt.Println("traceID:",ctx.TraceID().String())
span.SetTag("todo","foo")
fmt.Fprint(w,"haha")
}
func main() {
http.HandleFunc("/",tracingMW(http.HandlerFunc(test)))
http.ListenAndServe(":8080",nil)
}
在web请求中设置一个中间件,在中间件设置rootSpan,然后利用context获取子span,在后续的业务处理中可以一次生成子span,并且为子span名命名
只演示大概的例子,每次中间件都要生成trace,将trace注入容器中保证唯一性,或者直接生成全局唯一的trace实例,根据需要自行扩展
下面演示跨服务(进程)之间实现追踪埋点,先看用http协议跨服务的调用追踪方式,直接在test方法中修改
func test(w http.ResponseWriter, r *http.Request) {
url := "http://localhost:8000"
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
}
span, _ := opentracing.StartSpanFromContext(r.Context(), "remote request svc2")
defer span.Finish()
span.SetTag("role", "admin")
span.SetBaggageItem("name", "媳妇")
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 := client.Do(req)
if err != nil {
log.Println("请求错误:",err)
}
log.Println(resp.Status)
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Fprint(w,err.Error())
}
fmt.Fprint(w,string(bytes))
}
接下来是目标服务的代码
package main
import (
"fmt"
"github.com/uber/jaeger-client-go"
"net/http"
"log"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go/config"
)
func fromEnv() (*config.Configuration,error) {
return &config.Configuration{
ServiceName: "svc2",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst, //固定采样
Param: 1, //1全采样,0不采样
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.1.246:6831",
},
},nil
}
func main(){
http.HandleFunc("/",test)
http.ListenAndServe(":8000",nil)
}
func test(w http.ResponseWriter,r *http.Request){
log.Println(r.Header,r.URL)
//cfg,err:=config.FromEnv()
cfg,err:=fromEnv()
if err!=nil {
log.Println(err)
}
tracer,closer,err:=cfg.NewTracer()
if err!=nil {
log.Println(err)
}
defer closer.Close()
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("get haha", ext.RPCServerOption(spanCtx))
defer span.Finish()
ctx := spanCtx.(jaeger.SpanContext)
log.Println("traceID:",ctx.TraceID().String())
log.Println(span.BaggageItem("name"))
span.LogFields(
otlog.String("event", "string-format"),
otlog.String("value", "hello wrold"),
)
fmt.Fprint(w,"呵呵,进程的追踪结束")
}
打开浏览器,输入http://localhost:8080/put请求,可以在jaeger-ui中查看请求经过过的span,以及在控制台,可以发现请求的traceID保持一致
追踪链路上经过的两个service,继续切换到系统的体系结构图中查看DAG图可以看到服务的调用结构如下:
下面演示在grpc的场景下,追踪的方式,和http跨进程调用差不多,直接附上代码
太晚了,明天继续。。。