分布式系统中的Tracer

转载自:http://www.cnblogs.com/zhengyun_ustc/p/55solution2.html
   https://blog.****.net/u014229282/article/details/81097188
   http://www.aboutyun.com/thread-20646-1-1.html

  Tracer 是一个用于分布式系统调用跟踪的组件,通过统一的 ID,将调用链路中的各种网络调用情况以日志的方式记录下来,以达到透视化网络调用的目的。这些日志可用于故障的快速发现,服务治理等等。

  电商平台由数以百计的分布式服务构成,每一个请求路由过来后,会经过多个业务系统并留下足迹,并产生对各种Cache或DB的访问,但是这些分散的数据对于问题排查,或是流程优化都帮助有限。对于这么一个跨进程/跨线程的场景,汇总收集并分析海量日志就显得尤为重要。要能做到追踪每个请求的完整调用链路,收集调用链路上每个服务的性能数据,计算性能数据和比对性能指标(SLA),甚至在更远的未来能够再反馈到服务治理中,那么这就是分布式跟踪的目标了。

  分布式系统的跟踪系统的目标要求有如下几点:

  1. 探针的性能消耗
      APM组件服务的影响应该做到足够小。服务调用埋点本身会带来性能损耗,这就需要调用跟踪的低损耗,实际中还会通过配置采样率的方式,选择一部分请求去分析请求路径。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。
      
  2. 代码的侵入性
      即也作为业务组件,应当尽可能少入侵或者无入侵其他业务系统,对于使用方透明,减少开发人员的负担。
      对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样才是无法满足对跟踪系统“无所不在的部署”这个需求。

  3. 可扩展性
      一个优秀的调用跟踪系统必须支持分布式部署,具备良好的可扩展性。能够支持的组件越多当然越好。或者提供便捷的插件开发API,对于一些没有监控到的组件,应用开发者也可以自行扩展。

  4. 数据的分析
      数据的分析要快 ,分析的维度尽可能多。跟踪系统能提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应。分析的全面,能够避免二次开发。


  一个请求完整的调用链可能如下图,经过多个系统服务,调用关系复杂。如下图:
分布式系统中的Tracer
  在前端请求到达服务器时,应用容器在执行实际业务处理之前,会先执行埋点逻辑(类似 Filter 的机制),埋点逻辑为这个前端请求分配一个全局唯一的调用链ID,即TraceId。埋点逻辑把TraceId放在一个调用上下文对象里面,而调用上下文对象会存储在 ThreadLocal里面。
  调用上下文对象里还有一个ID非常重要,在淘宝的EagleEye里面被称作RpcId。RpcId用于区分同一个调用链下的多个网络调用的发生顺序和嵌套层次关系。对于前端收到请求,生成的RpcId固定都是0。
  当这个前端执行业务处理需要发起RPC调用时,会首先从当前线程ThreadLocal上面获取之前EagleEye设置的调用上下文。然后,把RpcId递增一个序号。在 EagleEye里使用多级序号来表示RpcId,比如应用A收到请求的RpcId是0,那么它第一次调用RPC服务B时,会把RpcId改成 0.1。之后,调用上下文会作为附件随这次请求一起发送到远程服务器。
  服务端收到这个请求之后,会从请求附件里取出调用上下文,并放到当前线程ThreadLocal上面。如果服务B在处理时,需要调用另一个服务,这个时候它会重复之前提到的操作,唯一的差别就是RpcId会先改成0.1.1 再传过去。服务A的逻辑全部处理完毕之后,在返回响应对象之前,会把这次调用情况以及TraceId、RpcId都打印到它的访问日志之中,同时,会从ThreadLocal 清理掉调用上下文。


TraceId 生成规则参考
  Tracer 通过 TraceId 来将一个请求在各个服务器上的调用日志串联起来,TraceId一般由接收请求经过的第一个服务器产生,产生规则是:服务器IP + 进程的ID + 产生ID时候的时间 + 自增序列。产生TracerId的机器的IP可以用一个十六进制的数字,每两位代表 IP 中的一段。时间可以用毫秒数表示,自增的序列可以是四位的数字表示。

RPC ID 生成规则参考
  Tracer中的 RPC ID代表本次调用在整个调用链路中的位置,假设一个 Web系统A接收了一次用户请求,那么在这个系统的Tracer日志中,记录下的 RPC ID是0,代表是整个调用的根节点,如果A系统处理这次请求,需要通过 RPC依次调用B,C,D三个系统,那么在 B,C,D三个系统的Tracer服务端日志中,RPC ID也分别是0.1,0.2和0.3;如果C系统在处理请求的时候又调用了E,F两个系统,那么E,F两个系统对应的Tracer服务端日志是0.2.1 和0.2.2。