前期准备
具体步骤
1、运行Jaeger
# UDP端口6831是用来接收compact thrift协议的跟踪数据的
# HTTP端口16686是用来显示Jaeger界面的
docker run -d \ --rm \ --name jaeger \ -p6831:6831/udp \ -p16686:16686 \ jaegertracing/all-in-one:1.7
2、运行样例程序
docker run -d \
--rm \
--link jaeger \
-p8080-8083:8080-8083 \
jaegertracing/example-hotrod:1.7 \
all \
--jaeger-agent.host-port=jaeger:6831
每个按钮就是一个顾客,随便点击一个按钮,那个顾客就会打到一辆车(就是简单的uber打车或滴滴打车)
上图中有几个信息:Your web client's id: 7094。这个是页面Javascript分配的会话ID,如果你刷新页面,你会分配到一个新的ID
req:7094-1。对每个叫车请求页面都会分配一个唯一的请求ID,由会话ID加一个单向递增的序列号组成
latency: 738ms。表示这个叫车请求用了738ms返回结果。
3、通过Jaeger查看这个样例的架构
回到Jaeger界面,点击Dependencies和DAG
可以看到,这个样例由4个微服务(frontend, driver, customer, route)和两个存储模块(redis和mysql)组成。redis和mysql只是虚拟出来的存储模块,但那4个微服务是真的在运行的,你可以从日志看出来,他们分别占用了一个端口。
docker logs 14d9b7cf3333
2018-10-10T06:39:20.541ZINFOcmd/root.go:86Using expvar as metrics backend
2018-10-10T06:39:20.541ZINFOcmd/all.go:25Starting all services
2018-10-10T06:39:20.651ZINFOlog/logger.go:37Starting{"service": "frontend", "address": "http://0.0.0.0:8080"}
2018-10-10T06:39:20.651ZINFOlog/logger.go:37Starting{"service": "route", "address": "http://0.0.0.0:8083"}
2018-10-10T06:39:20.759ZINFOlog/logger.go:37Starting{"service": "customer", "address": "http://0.0.0.0:8081"}
2018-10-10T06:39:20.760ZINFOlog/logger.go:37TChannel listening{"service": "driver", "hostPort": "[::]:8082"}
边上的数字表示请求数,可见要处理一个打车请求,需要对route服务请求10次和对redis服务请求14次。
4、查看跟踪数据
回到Jaeger主页http://127.0.0.1:16686/, 在Service中选择frontend并点击Find Traces,你可以得到一个跟踪数据
点击frontend: HTTP GET /dispatch获得具体信息
从这个跟踪数据,我们可以看出一个打车请求到底做了些什么和每个步骤的耗时:frontend服务收到了一个GET /dispatch的请求
frontend服务对customer服务发出了GET /customer的请求
customer服务收到请求后对MySql进行了SELECT查询,结果返回给frontend服务
frontend服务接着对driver服务发出了Driver::findNearest的请求
driver服务对redis发出了一系列的请求,其中有一些请求还失败了。并返回结果给frontend服务
frontend服务接着对route服务发出了一系列的/route请求
最终返回结果
我们可以点任何一个span获取跟多的信息。比如下面是一个失败的redis请求
5、关于Span标签和Span日志
以下是MySql查询的Span
可以看到查询的语句SELECT * FROM customer WHERE customer_id=123被作为Span标签(Tags)记录了下来,而Acquired lock with 0 transactions则作为Span日志记录了下来,什么样的信息要用Tag什么样的信息要用日志呢, opentracing没有明确的规定,但opentracing/specification 给出了一个建议的标准:标签是对Span整个生命周期都不变的,而日志则是在Span中的某个时间发生的事件。
6、通过Jaeger找到这个服务的瓶颈
快速点击样例的界面来模拟同时发生多次打车的请求。
然后回到Jaeger界面找到延时最长的一个跟踪,发现大多数时间都花在MySQL查询上面
可以看到样例等待了将近1秒在获取锁上,再看上面一条日志Waiting for lock behind 3 transactions. blockers = [7065-16 7065-17 7065-18] 它甚至还包含了同时竞争这个锁的其他请求的ID!这个是因为在opentracing API中使用了一个叫baggage的技术,它本质上是一个key-value键值对,frontend服务在存储跟踪数据的时候,除了存储Span ID和Trace ID,它还存储了baggage信息,因此跟这个请求有关的所有路径都包含了这个信息。Pivot Tracing for Distributed Systems 项目也是用了baggage技术来进行动态跟踪。
让我们找到代码中获取锁的部分
// simulate misconfigured connection pool that only gives
// one connection at a time
d.lock.Lock(ctx)
defer d.lock.Unlock()
// simulate db query delay
delay.Sleep(config.MySQLGetDelay, config.MySQLGetDelayStdDev)
其中ctx是传递的跟踪信息。里面包含了Span ID, Trace ID和bagage信息。如果我们去掉这个锁和把config.MySQLGetDelay从300ms减少到100msfrom https://medium.com/opentracing/take-opentracing-for-a-hotrod-ride-f6e3141f7941
可以看到MySQL请求不再是瓶颈,稳定在100ms左右,而/route请求则占用了50%的时间,发现原因是/route请求的并行化不够,同时只能有一两个请求同时进行,于是提高/route的线程池从RouteWorkerPoolSize = 3提高到RouteWorkerPoolSize = 100
延时进一步减少,现在/driver请求成为大头。留给读者自行优化。
7、其他通过Baggage, 我们可以方便的统计某个服务上对哪些客户的具体耗时。因为请求中包含{"customer" : "John"}的信息,所以服务中,只要维护一个列表以"John"为Key,统计所有包含"John"的请求的总耗时即可。
本样例并没有用多少Opentracing的东西,留给读者自行替换Jaegar后端成lightstep后端或者sourcegraph/appdash后端。
日志可以参考pkg/log/spanlogger.go ,并不是Opentracing的库,而是使用了Zap
因为Jaeger记录了跟踪信息,它自然而然的统计了用于监控的基本信息,比如请求数,错误率,请求延时分布等等。可以通过http://127.0.0.1:8083/debug/vars 获得