导读】本文介绍了Tracing零碎Jeager在go我的项目中的集成实际。
一个残缺的微服务体系至多须要包含:
- CI / CD 也就是自动化部署
- 服务发现
- 对立的PRC协定
- 监控
- 追踪(Tracing)
要配置下面这些货色堪称说超级简单, 所以我倡议读者 如果能够间接应用istio
它弱小到蕴含了微服务开发须要思考的所有货色, 上图中的”Observe”就包含了这篇文章所说的”链路追踪(Tracing)”.
但软件行业没有银弹, 弱小的工具天然须要弱小的人员去治理, 在进阶为大佬之前, 还是得钻研一些传统的计划以便成长, 所以便有了这篇文章.
Tracing在微服务中的作用
和传统单体服务不同, 微服务通常部署在一个分布式的零碎中, 并且一个申请可能会通过好几个微服务的解决, 这样的环境下谬误和性能问题就会更容易产生, 所以察看(Observe)尤为重要,
这就是Tracing的用武之地, 它收集调用过程中的信息并可视化, 让你晓得在每一个服务调用过程的耗时等状况, 以便及早发现问题.
为什么是Jaeger
笔者正在学习Golang, 选用应用Golang并开源的Tracing零碎 – Jaeger当然就不再须要理由了. (`⌒´メ)
Uber出品也不会太差。
装置
为了疾速上手, 官网提供了”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
具体端口作用就不再赘述, 官网文档都有.
All in One只应该用于试验环境. 如果是生产环境, 你须要按官网这样部署
本文在前面会讲到部署并应用Elasticsearch作为存储后端.
当初用于测试的服务端就实现了, 你能够拜访
http://{host}:16686
来拜访JaegerUI, 它就像这样:
客户端
当初就能够编写客户端了, 官网提供了Go/Java/Node.js/Python/C++/C#语言的客户端库, 读者可自行抉择, 应用形式可在各自的仓库中查看.
我也只试验了Golang客户端, 先从最简略的场景动手:
在单体利用中实现Tracing.
在编写代码之前还得了解下Jaeger中最根底的几个概念, 也是OpenTracing
的数据模型: Trace / Span
Trace: 调用链, 其中蕴含了多个Span.
Span: 跨度, 计量的最小单位, 每个跨度都有开始工夫与截止工夫. Span和Span之间能够存在References(关系): ChildOf 与 FollowsFrom
如下图 (来至凋谢分布式追踪(OpenTracing)入门与 Jaeger 实现)
单个 Trace 中,span 间的因果关系
[Span A] ←←←(the root span) | +------+------+ | | [Span B] [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf) | | [Span D] +---+-------+ | | [Span E] [Span F] >>> [Span G] >>> [Span H] ↑ ↑ ↑ (Span G 在 Span F 后被调用, FollowsFrom)
接下来是代码工夫, 参考我的项目的Readme(https://github.com/jaegertrac...)和搜索引擎不难写出以下代码
package testsimport ( "context" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go" "log" "testing" "time" jaegercfg "github.com/uber/jaeger-client-go/config")func TestJaeger(t *testing.T) { cfg := jaegercfg.Configuration{ Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, LocalAgentHostPort: "{host}:6831", // 替换host }, } closer, err := cfg.InitGlobalTracer( "serviceName", ) if err != nil { log.Printf("Could not initialize jaeger tracer: %s", err.Error()) return } var ctx = context.TODO() span1, ctx := opentracing.StartSpanFromContext(ctx, "span_1") time.Sleep(time.Second / 2) span11, _ := opentracing.StartSpanFromContext(ctx, "span_1-1") time.Sleep(time.Second / 2) span11.Finish() span1.Finish() defer closer.Close()}
代码惟一须要留神的中央是closer, 这个closer在程序完结时肯定记得敞开, 因为在客户端中span信息的发送不是同步发送的, 而是有一个暂存区, 调用closer.Close()就会让暂存区的span强制发送到agent.
运行之, 咱们就能够在UI看到:
点击进入详情就能看到咱们刚刚收集到的调用信息
通过Grpc中间件应用
在单体程序中, 父子Span通过context关联, 而context是在内存中的, 不言而喻这样的办法在垮利用的场景下是行不通的.
垮利用通信应用的形式通常是”序列化”, 在jaeger-client-go库中也是通过相似的操作去传递信息, 它们叫:Tracer.Inject() 与 Tracer.Extract().
其中inject办法反对将span系列化成几种格局:
- Binary: 二进制
- TextMap: key=>value
- HTTPHeaders: Http头, 其实也是key=>value
正好grpc反对传递metadata也是string的key=>value模式, 所以咱们就能通过metadata实现在不同利用间传递Span了.
这段代码在github上有人实现了
题外话:下面的库应用到了grpc的Interceptor, 但grpc不反对多个Interceptor, 所以当你又应用到了其余中间件(如grpc_retry)的话就能导致抵触. 同样也能够应用这个库grpc_middleware.ChainUnaryClient解决这个问题.
在grpc服务端的中间件代码如下(已省略错误处理)
import ( "context" "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" "google.golang.org/grpc")jcfg := jaegercfg.Configuration{ Sampler: &jaegercfg.SamplerConfig{ Type: "const", Param: 1, }, ServiceName: "serviceName", }report := jaegercfg.ReporterConfig{ LogSpans: true, LocalAgentHostPort: "locahost:6831", }reporter, _ := report.NewReporter(serviceName, jaeger.NewNullMetrics(), jaeger.NullLogger)tracer, closer, _ = jcfg.NewTracer( jaegercfg.Reporter(reporter),)server := grpc.NewServer(grpc.UnaryInterceptor(grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer))))在grpc客户端的中间件代码如下conn, err := grpc.Dial(addr, grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor( grpc_opentracing.WithTracer(tracer),)))
当初服务端和客户端之间的调用状况就能被jaeger收集到了.
在业务代码中应用
有时候只监控一个”api”是不够的,还须要监控到程序中的代码片段(如办法),能够这样封装一个办法
package tracertype SpanOption func(span opentracing.Span)func SpanWithError(err error) SpanOption { return func(span opentracing.Span) { if err != nil { ext.Error.Set(span, true) span.LogFields(tlog.String("event", "error"), tlog.String("msg", err.Error())) } }}// example:// SpanWithLog(// "event", "soft error",// "type", "cache timeout",// "waited.millis", 1500)func SpanWithLog(arg ...interface{}) SpanOption { return func(span opentracing.Span) { span.LogKV(arg...) }}func Start(tracer opentracing.Tracer, spanName string, ctx context.Context) (newCtx context.Context, finish func(...SpanOption)) { if ctx == nil { ctx = context.TODO() } span, newCtx := opentracing.StartSpanFromContextWithTracer(ctx, tracer, spanName, opentracing.Tag{Key: string(ext.Component), Value: "func"}, ) finish = func(ops ...SpanOption) { for _, o := range ops { o(span) } span.Finish() } return}
应用
newCtx, finish := tracer.Start("DoSomeThing", ctx)err := DoSomeThing(newCtx)finish(tracer.SpanWithError(err))if err != nil{ ...}
最初能失去一个像这样的后果
能够看到在服务的调用过程中各个span的工夫,这个span能够是一个微服务之间的调用也能够是某个办法的调用。
点开某个span也能看到额定的log信息。
通过Gin中间件中应用
在我的我的项目中应用http服务作为网关提供给前端应用,那么这个http服务层就是root span而不必关怀父span了,编写代码就要简略一些。
封装一个gin中间件就能实现
import ( "context" "github.com/gin-gonic/gin" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext")engine.Use(func(ctx *gin.Context) { path := ctx.Request.URL.Path span := j.tracer.StartSpan(path, ext.SpanKindRPCServer) ext.HTTPUrl.Set(span, path) ext.HTTPMethod.Set(span, ctx.Request.Method) c := opentracing.ContextWithSpan(context.Background(), span) ctx.Set("ctx", c) ctx.Next() ext.HTTPStatusCode.Set(span, uint16(ctx.Writer.Status())) span.Finish() }) 如果须要向上层传递context则这样获取contextfunc Api(gtx *gin.Context) { ctx = gtx.Get("ctx").(context.Context)}
结语
应用trace会入侵局部代码,特地是追踪一个办法,但这是不可避免的(应用istio框架能缓解这个问题,倡议有趣味的敌人钻研一下)。其实并不是整个零碎的服务都须要追踪,可只针对于重要或者有性能问题的中央进行追踪。
部署篇
应用Elasticsearch作为存储后端
笔者对于Elasticsearch更为相熟, 故抉择它了.
es的部署就不说了.
这里是jaeger的docker-compose.yaml
version: '2'services: jaeger-agent: image: jaegertracing/jaeger-agent:1.12 stdin_open: true tty: true links: - jaeger-collector:jaeger-collector ports: - 6831:6831/udp command: - --reporter.grpc.host-port=jaeger-collector:14250 jaeger-collector: image: jaegertracing/jaeger-collector:1.12 environment: SPAN_STORAGE_TYPE: elasticsearch ES_SERVER_URLS: http://elasticsearch:9200 stdin_open: true external_links: - elasticsearch/elasticsearch:elasticsearch tty: true jaeger-query: image: jaegertracing/jaeger-query:1.12 environment: SPAN_STORAGE_TYPE: elasticsearch ES_SERVER_URLS: http://elasticsearch:9200 stdin_open: true external_links: - elasticsearch/elasticsearch:elasticsearch tty: true ports: - 16686:16686/tcp
其中agent和collect都被设计成无状态的,也就意味着他们能够被放在代理(如Nginx)前面而实现负载平衡。
侥幸的是笔者在部署过程中没有遇见任何问题,所以也就没有”疑难杂症”环节了。一般来说遇到的问题都能够去issue搜到。
转自:
cnblogs.com/ExMan/p/12084524.html
本文由mdnice多平台公布