分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)

Biz-UI团队在核心业务系统的开发过程中,将具有共性的功能模块抽象出来,逐渐完成了中台的构建,为业务逻辑提供了强有力的基础组件支撑。其中分布式追踪系统作为一个重要的组成部分,为监控服务之间的调用、定位和调试线上问题,提供了有力的支撑。本文将详细剖析FreeWheel Biz-UI团队从0到1构建和改进全链路分布式追踪系统的过程。

微服务-捉虫记

小志所在的技术部门刚刚对臃肿的单体应用完成了拆解,推行微服务理念,将之前杂糅得不可开交的代码按业务模块拆分成一个一个的微服务。随着项目的推动,大家确实感受到微服务带来的收益,拆解完以后对单个微服务维护起来也更加方便。但与此同时也带来了一些之前未曾遇到的问题......

一阵急促的手机铃声打断了小志的思绪,看着熟悉的来电号码,小志心想真是怕什么来什么,新上的服务凌晨又出问题了。

“喂,小志啊,线上报警了,挺紧急的,你赶紧看一下吧,一会线上聊。”

熟练地翻开报警邮件,处理这类问题对小志来说已经轻车熟路。详细分析了一下报警内容,小志断定是下游服务出问题导致的报警。

“老李,我是小志,我们这边刚刚出来一个报警,挺严重的,刚看了一下系统日志,是在调你们订单服务的时候出错了,你帮看一下吧,我一会把日志发你钉钉。”

老李经过排查,发现是上游服务的问题,于是抓紧联系老钱。“老钱啊,还没睡呢吧?刚刚小志那边出问题,影响了不少客户。查日志发现是订单这边报错了,我看了一下订单服务的日志,是你们那边的库存服务报了不少500,你赶紧起来看一下吧。”

屏幕前小志、老李、老钱正在热火朝天地捉虫找bug.

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(1)

微服务,作为一个近几年非常火热的话题,切切实实解决了很多单体应用的痛点,但与此同时也带来了一些新的痛点。

FreeWheel核心业务部门结合自身的实际情况,以微服务的方式对之前的单一应用做了拆分。同时,为了避免上面故事里的情况发生,我们引入了分布式追踪系统用来解决【如何在微服务系统中快速定位问题?】、【如何观察复杂的调用链、分析调用的网络结构?】等等问题。

分布式追踪系统

分布式追踪系统(Distributed Tracing System)可以用来解决微服务系统中的常问题定位、bug 追踪、网络结构分析等问题。该系统的数据模型最早由Google’s Dapper 论文提出,主要包含如下几个部分:

  • Trace: 用来描述分布式系统中一个完整的调用链,每一个Trace会有一个独有的Trace ID。
  • Span: 分布式系统中的一个小的调用单元,可以是一个微服务中的service, 也可以是一次方法调用,甚至一个简单的代码块调用。Span可以包含起始时间戳、log等信息。每一个Span会有一个独有的Span ID.
  • Span Context: 包含额外Trace信息的数据结构,span context可以包含Trace ID、Span ID, 以及其他任何需要向下游service传递的trace信息。

在这基础上,社区为了实现各个编程语言和各种框架的接口统一,发展出了OpenTracing Specification 以及 OpenTracing API。后来业界也相继推出了几款比较成熟的产品,如Zipkin、Jaeger、LightStep、DataDog等。

分布式追踪系统是如何解决跨服务调用时的问题定位的呢?对于一次客户调用,分布式追踪系统会在请求的入口处生成一个TraceID,用这个TraceID把客户请求进入每个微服务中的调用日志串联起来,形成一个时序图。如下图所示,假设A的两端表示一次客户调用的开始和结束,中间会经过类似B、 C、D、 E等后端服务。此时如果E出问题,就可以很快速地定位到,而不用同时让A、B、C、D都参与进来查问题。

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(2)

(图片来源:https://www.jaegertracing.io/img/spans-traces.png)

Biz-UI 分布式追踪系统实践

技术选型

在FreeWheel 核心业务系统微服务搭建过程中,我们深度调研了现有的分布式追踪系统解决方案,针对其中几个比较重要的选型指标做了深度的讨论。

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(3)

基于上面几个指标,我们对市面上主流的开源项目进行筛选,包括Jeager、Zipkin等,考虑到市场占有率、项目成熟度、项目契合度,我们最终选择了同为Golang开发的Jaeger。Jeager Tracing框架下主要包含三大模块:TracingAgent, Tracing Collector 和 Tracing Query。

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(4)

整体的架构如下图所示:

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(5)

(图片来源:https://www.jaegertracing.io/img/architecture-v1.png)

落地实践与优化改造

新系统实施的第一步往往是分析现有技术环境,目的是尽可能地复用已有的功能、模块,运维环境等。这样能大大减少后续的维护、运维等成本。

FreeWheel核心业务平台现有的基础环境包括如下几点:

  • 首先是现有微服务对外提供的接口协议多种多样,例如gRPC、HTTP(基于gRPC-Gateway)、HTTP(裸HTTP) 等。
  • FreeWheel现有一套ELK Kafka集群用来收集和分析系统日志。
  • 微服务的基础运行环境基于Kubernetes Istio,除了少许的特殊服务运行在物理机上以外,绝大部分业务服务都运行在K8s集群(AWS EKS)中,也就是说每个服务的实例都作为集群中的一个pod在运行。

基于以上背景,我们设计了Tracing系统实施方案,并对部分模块进行了升级改造。

首先,由于各个微服务对外提供的接口也不尽统一,现有的接口包括gRPC、gRPC-Gateway、HTTP,甚至WebSocket。我们在Jeager-client基础上做了一层封装,实现了一个Tracing client Lib,该lib可以针对不同的通讯协议对流量进行劫持,并将Trace 信息注入到请求中。还扩展性地加入了过滤器(过滤给定特征的流量)、 TraceID生成、TraceID提取,与Zipkin Header兼容等功能。这部分会随着平台的不断扩展和改造进行持续的更新和维护。

另外,为了充分利用公司现有的ElasticSearch 集群,我们决定用ElasticSearch作为追踪系统的后端存储。由于使用场景为写多读少,为了保护ElasticSerach,我们决定用Kafka作为缓冲,即对Collecor进行扩展,将数据进行处理并转换成ElasticSearch可读的json格式写入Kafka, 再通过logstash消费写入ElasticSearch中。

此外,对于Spark dependency Job,同样需要将数据转换为对应的Json格式写入Kafka,最终存储到ElasticSearch。这里对Spark Dependency Job的输出部分做了扩展,让其支持向Kafka中导入数据。最后,由于微服务系统内部部署环境的差异,我们提供了兼容K8s sidecar, K8s Daemonset, On-perm daeom process 等部署方式。

新设计的架构如下图所示:

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(6)

数据采集层

中间件

对于基于Golang开发的微服务,Trace信息在服务内部传播主要依赖context.Context。FreeWheel核心业务系统中一般来讲支持两种通讯协议:HTTP and GRPC,其中HTTP接口主要依赖GRPC-Gateway自动生成。当然也有一部分服务不涉及GRPC, 直接对外暴露HTTP接口。这里HTTP主要面向的调用方是OpenAPI或者前端UI。同时,服务与服务之间一般采用GRPC方式通讯。对于这类场景,Tracinglib提供了必要的组件供业务微服务使用。其传播过程如下图所示:

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(7)

针对入口流量,Tracing Client Lib封装了HTTP中间件、 GRPC中间件,以及与GRPC-Gateway这一层的兼容。针对出口流量,Tracing Client Lib 封装了GRPC-Client 中间件。这里的“封装”不单单指对Jaeger client lib提供方法的简单wrapper,还包括诸如Tracing状态监测、请求过滤等功能。比较典型的像 "/check_alive", "/metrics"这类没有必要trace的请求可以通过请求过滤的功能过滤掉从而不记录Trace。

Istio集成

了解Istio的同学应该知道,Istio本身支持Jaeger Tracing集成。对于跨服务的请求,Istio可以劫持诸如GRPC/HTTP等类型的流量,生成对应的Trace信息。因此如果能将业务代码中的Trace信息与Istio进行集成,就能够监控到整个调用网络与业务内部Trace的完整信息,方便查看Istio sidecar到服务这个调用过程的网络情况。

问题在于,Istio集成Tracing时采取了Zipkin B3 Header标准,其格式如下:

X-B3-TraceId: {TraceID} X-B3-ParentSpanId: {ParentSpanID} X-B3-SpanId: {SpanID} X-B3-Sampled: {SampleFlag} X-B3-TraceId: {TraceID} X-B3-ParentSpanId: {ParentSpanID} X-B3-SpanId: {SpanID} X-B3-Sampled: {SampleFlag}

而FreeWheel核心业务系统内部所采用的TracerHeader格式为:

FW-Trace-ID: {TraceID}:{SpanID}:{ParentSpanID}:{SampleFlag}

并且FW Trace Header被广泛地应用在业务代码中,集成了诸如log, change_history等服务,一时间难以被完全替换。针对这个问题,我们重写了Jaeger Client中的将Injector和Extractor,其接口定义如下:

//Span body { "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] } }

新实现的Injector和Extractor同时兼容B3 Header和Freewheel Trace Header。服务接收到请求时会优先查看有没有B3 Header,在生成新Span的时候同时插入FreeWheel Trace Header。即FreeWheel Trace Header继续在服务内部使用,跨服务之间的调用以B3 Header为主。

X-B3-TraceId: {TraceID} X-B3-ParentSpanId: {ParentSpanID} X-B3-SpanId: {SpanID} X-B3-Sampled: {SampleFlag} FW-Trace-ID: {TraceID}:{SpanID}:{ParentSpanID}:{SampleFlag}

数据缓冲与中转层

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(8)

上文提到数据存储选用ElasticSearch, 数据的采集与存储是一个典型的写多读少的业务场景。对这类场景,我们引入Kafka作为数据的缓冲与中转层。基于这个思路我们对Collector进行了改造,加入了Collector Kafka Producer组件,在Collector上将span信息转为json发给Kafka,然后由Logstash作为Consumer存储到ElasticSearch。对于Trace信息,ElasticSearch存储主要分为两大部分:服务/操作索引和Span 索引。服务/操作索引主要用来为query ui提供快速检索服务(Service Name)和操作(Operation Name), 结构如下:

//Span body { "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] } }

Span结构体由Tracing客户端生成,主要一下几大部分:

  • 基础trace信息,如 traceID, spanID, parentID, operationName,duration。
  • Tags,这部分主要包含业务逻辑相关的信息如request method, url, response code等。
  • References,主要用来表示Span的父子从属关系。
  • Process,服务的基本信息。
  • Logs,用于给业务代码扩展使用。

//Span body { "traceID": "5082be69746ed84a", "spanID": "5082be69746ed84a", "operationName": "HTTP GET", "startTime": ..., "duration": 616, "references": [ { "refType": "CHILD_OF", "spanID": "14a9e000a96a2671", "traceID": "259f404f8409a4d7" } ], "tags": [ { "key": "http.url", "type": "string", "value": "/services/v3/**.xml" }, { "key": "http.status_code", "type": "int64", "value": "500" }, //... ], "logs": [], "process": { "serviceName": "your_service_name", "tags": [ { "key": "hostname", "type": "string", "value": "xx-mac" }, //... ] } }

存储与计算层

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(9)

这一层主要用于对Trace数据进行持久化和离线分析。利用ElasticSearch会对数据进行分片,分index的存储,防止历史数据丢失,方便对历史问题进行回溯。不过既然提到持久化就难免要考虑数据规模的问题,持续大量的历史数据写入到ElasticSeach会不断增加其负担,而且对于过于久远的历史数据,被检索到的频率也相对较小。这里我们采取定期归档的策略,对于超过30天的数据进行归档,转存到ES之外以备不时之需。ElasticSearch只对相对较“热”的数据提供检索服务。

离线分析主要用于对EalsticSearch中的Span数据进行分析,上文我们提到一个Span数据结构包含其自身的TraceID和它父节点的TraceID,每一个节点都包含自身从属与哪个服务。

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(10)

这里我们只关心跨服务之间的调用关系,例如上图,离线分析时只考虑A, B, C, E这几个节点,由于D节点与C, E节点都在服务3内部,所以将其忽略。分析出来的结果如图所示

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(11)

展示层

展示层主要指Query-UI, 功能是从ElacticSearch中查询数据,对具有相同TraceID的数据进行聚合,并在前端进行渲染。从QueryUI中可以清晰的看到一条请求经历了几个不同的服务(以不同颜色标注),在每个服务中的到达时间和结束时间,整个请求总共经历的时间等。

分布式微服务架构原理与实战(微服务中台技术解析之全链路分布式追踪系统实践)(12)

未来展望

随着FreeWheel核心业务平台不断地扩充和演进,分布式追踪系统也需要进行不断升级改造以适配业务需求。例如部分业务代码正在尝试Serverless的方式,也就要求Tracing系统支持诸如AWS Lambda等使用场景。对于这种形式的需求,我们将紧跟业务,持续调研,以期服务更多的场景。此外,现有服务调用拓扑网络是基于离线数据生成的,我们也期望未来能找到一些在线处理的解决方案,如Flink、Spark Streaming等,做到实时的调用关系统计。

参考阅读

  • Jaeger Tracing https://www.jaegertracing.io/
  • OpenTracingSpecification https://github.com/opentracing/specification
  • Google Dapper paper https://research.google/pubs/pub36356/

作者介绍:

冯刚,FreeWheel Biz-UI 高级研发工程师,数年Golang后端研发经验,熟悉微服务体系架构,热衷于探索与分享新技术。目前致力于OpenAPI重构,服务化等相关实践。

延伸阅读:

一个微服务业务系统的中台构建之路-InfoQ

gRPC长连接在微服务业务系统中的实践-InfoQ

关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书,点击文末「了解更多」,即可移步InfoQ官网,获取最新资讯~

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页