全链路设计与实践

背景

随着公司业务的高速发展,公司服务之间的调用关系愈加复杂,如何理清并跟踪它们之间的调用关系就显的比较关键。线上每一个请求会经过多个业务系统,并产生对各种缓存或者 DB 的访问,但是这些分散的数据对于问题排查,或者流程优化提供的帮助有限。在这样复杂的业务场景下,业务流会经过很多个微服务的处理和传递,我们难免会遇到这些问题:

  • 一次请求的流量从哪个服务而来? 最终落到了哪个服务中去?
  • 为什么这个请求这么慢? 到底哪个环节出了问题?
  • 这个操作需要依赖哪些东西? 是数据库还是消息队列? Redis挂了,哪些业务受影响?

设计目标

  • 低消耗性:跟踪系统对业务系统的影响应该做到足够小。在一些高度优化过的服务,即使一点点损耗也容易察觉到,而且有可能迫使在线负责的部署团队不得不将跟踪系统关停
  • 低侵入性:作为非业务组件,应当尽可能少侵入或者无侵入业务系统,对于使用方透明,减少开发人员的负担
  • 时效性:从数据的收集产生,到数据计算处理,再到最终展现,都要求尽可能快
  • 决策支持:这些数据是否能在决策支持层面发挥作用,特别是从 DevOps 的角度
  • 数据可视化:做到不用看日志通过可视化进行筛选

实现功能

  • 故障定位:调用链路跟踪,一次请求的逻辑轨迹可以完整清晰的展示出来。
  • 性能分析:调用链的各个环节分别添加调用耗时,可以分析出系统的性能瓶颈,并针对性的优化。
  • 数据分析:调用链是一条完整的业务日志,可以得到请求的行为路径,汇总分析应用在很多业务场景

设计思路

对于上面那些问题,业内已经有了一些具体实践和解决方案。通过调用链的方式,把一次请求调用过程完整的串联起来,这样就实现了对请求链路的监控。

在业界,目前已知的分布式跟踪系统,比如「Twitter Zipkin 与淘宝鹰眼」,设计思想都是来自 Google Dapper 的论文 「Dapper, a Large-Scale Distributed Systems Tracing Infrastructure」

典型分布式调用过程

全链路设计与实践

图1:这个路径由用户的X请求发起,穿过一个简单的服务系统。用字母标识的节点代表分布式系统中的不同处理过程。

分布式服务的跟踪系统需要记录在一次特定的请求后系统中完成的所有工作的信息。举个例子,图1展现的是一个和5台服务器相关的一个服务,包括:前端(A),两个中间层(B和C),以及两个后端(D和E)。当一个用户(这个用例的发起人)发起一个请求时,首先到达前端,然后发送两个 RPC 到服务器 B 和 C 。B 会马上做出反应,但是 C 需要和后端的 D 和 E 交互之后再返还给 A ,由 A 来响应最初的请求。对于这样一个请求,简单实用的全链路跟踪的实现,就是为服务器上每一次你发送和接收动作来收集与跟踪

调用链路关系图

全链路设计与实践

cs - CLIENT_SEND,客户端发起请求
sr - SERVER_RECIEVE,服务端收到请求
ss - SERVER_SEND,服务端处理完成,发送结果给客户端
cr - CLIENT_RECIEVE,客户端收到响应

技术选型

公司 选项 是否开源 优缺点
淘宝 EagleEye 主要基于内部 HSF 实现,HSF 没有开源,故鹰眼也没有开源
Twitter Zipkin 基于 Http 实现,支持语言较多,比较适合我们公司业务
点评 CAT 自定义改造难度大,代码比较复杂,侵入代码,需要埋点
京东 Hydra 主要基于 Dubbo 实现,不适合公司 Http 请求为主的场景

综上,考虑到公司目前以 Http 请求为主的场景,最终决定采用参考 Zipkin 的实现思路,同时以 OpenTracing 标准来兼容多语言客户端

系统设计

整体架构图及说明

全链路设计与实践

一般全链路跟踪系统主要有四个部分:数据埋点、数据传输、数据存储、查询界面

数据埋点

  • 通过集成 SDK 到沪江统一开发框架中,进行低侵入性的数据收集
  • 采用 AOP 切面方式,将收集的数据存储在本地线程变量 ThreadLocal 中,对应用透明
  • 数据记录 TraceId、事件应用、接口、开始时间、耗时
  • 数据采用异步线程队列的方式发送到 Kafka 队列中,减少对业务的影响

目前支持的中间件有:

  • Http 中间件
  • Mysql 中间件
  • RabbitMQ 中间件

数据传输

我们在 SDK 与后端服务之间加了一层 Kafka ,这样做既可以实现工程解耦,又可以实现数据的延迟消费,起到削峰填谷的作用。我们不希望因为瞬时 QPS 过高而导致数据丢失,当然为此也付出了一些实效性上的代价。

Kafka Manager 展示

全链路设计与实践

数据存储

数据存储采用 ElasticSearch ,主要存储 Span 与 Annotation 相关的数据,考虑到数据量的规模,先期只保存最近 1 个月的数据。

ELasticSearch Head 数据

全链路设计与实践

查询界面

通过可视化 Web 界面来查询分布式调用链路,同时还提供根据项目维度分析依赖聚合

查询页面

全链路设计与实践

Trace 树

全链路设计与实践

依赖分析

全链路设计与实践

遇到的一些坑

Web 页面加载超时

问题

使用 Zipkin 官网 UI 的时候,会偶发性的出现业务加载超时。

解决方案

原因是 Zipkin 加载页面的时候会一次性加载该项目里面所有的 Span ,当项目使用 Restful 形式的 API 时,就会产生几百万的 Span。最后,我们重写 UI 页面采用懒加载的方式,默认展示最近 10 条 Span,同时支持输入字符来动态查询 Span 的功能。

Span 堆积过多

全链路设计与实践

问题

当使用页面查询 Trace 的时候,发现某个链路堆积到上千条 Span

解决方案

排查下来,业务方使用 HttpClient 中间件发送 Http 请求超时的时候,SDK 并未拦截超时对应的异常,导致事件一直在存储在线程对应的 ThreadLocal 中

总结

全链路跟踪系统关键点:调用链。

每次请求都生成全局唯一的 TraceID,通过该 ID 将不同的系统串联起来。实现调用链跟踪,路径分析,帮助业务人员快速定位性能瓶颈,排查故障原因。

参考资料