Dapper链路跟踪
一、设计目标
- 低消耗:跟踪系统对在线服务的影响应该做到足够小。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到。
- 应用级的透明:对于应用的程序员来说,是不需要知道有跟踪系统这回事的。
- 延展性:至少在未来几年的服务和集群的规模,监控系统都应该能完全把控住。
二、分布式跟踪
2.1 跟踪树Trace与span
分布式跟踪系统要做的就是记录每次发送和接受动作的标识符和时间戳,将一次请求涉及到的所有服务串联起来,只有这样才能搞清楚一次请求的完整调用链。 在这篇论文中使用Trace
表示对一次请求完整调用链的跟踪,将两个服务例如上面的服务A和服务B的请求/响应过程叫做一次Span
。 我们可以看出每一次跟踪Trace都是一个树型结构,Span可以提现出服务之间的具体依赖关系。
每个跟踪树Trace都要定义一个全局唯一的TraceID,论文中推荐用64位的整数表示,在这个跟踪中的所有Span都将获取到这个TraceID。 每个Span都有一个ParentSpanID和它自己的SpanID。上图那个例子中A服务的ParentSpanID为空,SpanID为1;然后B服务的ParentSpanID为1,SpanID为2;C服务的ParentSpanID也为1,SpanID为3,以此类推。
2.2 Span的内部结构
**Span除了记录ParentSpanID和自己的SpanID外,还会记录自己请求其他服务的时间和响应时间。**这里记录时间有个问题就是每个服务器的时间可能不是完全相同,为了解决这个问题需要约定一个前提,即RPC客户端必须发出请求后,服务端才能收到,即如果服务端的时间戳比客户端发出请求的时间戳还靠前,那么就按请求时间来算,响应时间也是如此。
从这个图可以首先能看出来这个Span是一次"Hello.Call"调用,SpanID是5,ParentSpanID是3,TraceID是100。 我们重点看一下Client Send
, Server Recv
, Server Send
, Client Recv
即CS, SR, SS, CR。
CS
:客户端发送时间SR
:服务端接收时间SS
: 服务端发送时间CR
: 客户端接收时间
通过收集这四个时间戳,就可以在一次请求完成后计算出整个Trace的执行耗时和网络耗时,以及Trace中每个Span过程的执行耗时和网络耗时。
- 服务调用耗时 = CR - CS
- 服务处理耗时 = SS - SR
- 网络耗时 = 服务调用耗时 - 服务处理耗时
2.3 生成Span
基于标注Annotation的方式,具体的做法就是
- 根据请求中的TraceID来获取Trace这个实例。
- 接下来就是记录Span,记录值先直接以日志的形式存在本地,然后跟踪系统会启动一个Collector Daemon来收集日志,然后整理日志写入数据库。 解析的日志结果建议放在BigTable(Cassandra或者HDFS)这类稀疏表的数据库里。因为每个Trace携带的Span可能不一样,最终的记录是每一行代表一个Trace,这一行的每一列代表一个Span。
对于减少代码的侵入性,论文建议将核心跟踪代码做的很轻巧,然后把它植入公共组件中,比如线程调用、控制流以及RPC库。
三、采样率
分布式跟踪系统的实现要求是性能低损耗的,尤其在生产环境中分布式跟踪系统不能影响到核心业务的性能。 Google也不可能每次请求都跟踪的,所以要进行采样,每个应用和服务可以自己设置采样率。采样率应该是每个应用自己的配置里配置的,这样每个应用可以动态调整,特别是刚应用刚上线使可以适当调高采样率。
一般在系统峰值流量很大的情况下,只需要采样其中很小一部分请求,例如1/1000的采样率,即分布式跟踪系统只会在1000次请求中采样其中的某一次。
Comments | 0 条评论