导读】本文介绍了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 tests
import (
"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 tracer
type 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则这样获取context
func 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多平台公布
发表回复