HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

微服务中台技术解析之全链路分布式追踪系统实践

  • 2020-11-23
  • 本文字数:5469 字

    阅读完需:约 18 分钟

微服务中台技术解析之全链路分布式追踪系统实践

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

微服务-捉虫记

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


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


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


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


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


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


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



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


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 都参与进来查问题。



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

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

技术选型


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


指标

描述

是否支持Go Client?

FreeWheel核心业务系统大量用到Go作为开发语言,这就使得我们更倾向于提供便利的Go 客户端的解决方案。

是否支持gRPC?

各个业务模块之间主要通过gRPC进行通讯,是否能支持gRPC协议是一个重要的评价指标。

Trace数据如何存储?

业务代码接入Trace以后必然会产生大量的数据,这些Trace数据如何存储?如何分析?如何简洁清晰地将Trace信息展示给开发人员?这也是选型过程中需要考虑的一个重要指标。

扩展性

随着基础架构的升级改造,不断有新的组件被引入。评估解决方案是否支持诸如Istio等组件也将是需要考虑的问题。


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


服务

描述

Agent

作为宿主机上的一个daemon进程用来监听从服务端发出的trace数据包,然后批量地发往CollectorAgent会被部署到所有的宿主机上,同时也实现了对于Collector的路由和负载均衡等功能,以免所有请求都发到同一个Collector 实体上。

Collector

Collector用来收集从Agent发来的trace数据包,并进行一系列处理,包括trace数据包校验、index、格式转换等,最终存储到对应的数据仓库。Collector可以对接像Cassandra、ElasticSearch这类的存储服务。在Jaeger后续的版本里也加入了对Kafka的支持,同时提供了一个Injester(也就是一个Kafka consumer)用来消费Kafka中的数据。

Query UI

Query UI提供可视化的查询服务,从数据仓库中检索查询对应的Trace ID,并进行可视化的加工展示。

Spark Job

Query UI不仅可以展示单个trace中各个Span的从属关系与时序关系,还能提供了展示各微服务之间调用关系的功能。Spark JobElasticSearch读取原始的数据,并以离线的方式进行流处理,分析出服务之间的调用关系,调用次数等,生成的调用关系数据可以由Quey UI进行展示。

客户端

用来与业务代码进行集成。


整体的架构如下图所示:



(图片来源: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 等部署方式。


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


数据采集层

中间件

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



针对入口流量,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}
复制代码


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


FW-Trace-ID: {TraceID}:{SpanID}:{ParentSpanID}:{SampleFlag}
复制代码


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

// Injector接口的主要作用是将Trace Header数据按照既定的逻辑插入到上下文中。type Injector interface {    // Inject 将 `SpanContext` and 注入到 `carrier`中,这里carrier主要是指上下文Context    //    Inject(ctx SpanContext, carrier interface{}) error} // Extractor接口的主要作用是将上下文中的Trace数据抽取出来type Extractor interface {    // Extract 将上下文作为carrier, 提取其header中的Trace信息,并返回一个SpanContext对象。    Extract(carrier interface{}) (SpanContext, error)}
复制代码


新实现的 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}
复制代码

数据缓冲与中转层


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

//索引结构{    "serviceName": "v3_adaptor",    "operationName": "HTTP GET"}
复制代码


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"            },            //...        ]    }}
复制代码

存储与计算层



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


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



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


展示层

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


未来展望


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

参考


作者介绍:

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


相关文章:

《一个微服务业务系统的中台构建之路》

《gRPC长连接在微服务业务系统中的实践》

《Freewheel核心业务团队混沌工程实践之路》

2020-11-23 16:285104

评论

发布
暂无评论
发现更多内容

计算机原理学习笔记 Day6

穿过生命散发芬芳

计算机原理 4月日更

Java 常见 bean mapper 的性能及原理分析

Java小咖秀

Java bean Copier

中国SaaS的终局:神仙打架,小鬼遭殃

ToB行业头条

专访中寰卫星导航项目管理部负责人卜钢:如何演绎人生之路

打工人!

采访 调查采访能力考核

安于现状的人,不值得同情

小天同学

深度思考 个人感悟 4月日更 突破现状

GraphX图计算组件最短路算法实战

小舰

4月日更

维度数据模型建模过程(Kimball)

大数据技术指南

数据仓库 维度建模 4月日更

征服耶鲁教授的算法大神程序媛,是如何践行“以人为本”开发智慧社区大脑的?

华为云开发者联盟

算法 音视频 智慧社区 华为智慧园区数字平台 数字平台

建议收藏!看完全面掌握,最详细的Redis总结(2021最新版)

民工哥

运维 后端 redis cluster NoSQL数据库

合约量化交易APP开发|合约量化交易系统软件开发

系统开发

磁盘快照服务USnap:公有云连续数据保护(CDP)系统升级改造实践

UCloud技术

借助 Serverless 容器服务Cube,筷子科技轻松打造 10 万+ 爆款短视频

UCloud技术

合约跟单系统开发|合约跟单APP软件开发

Java-技术专题-Stream.foreach和foreach

洛神灬殇

Java stream collection

流计算:流式处理框架

正向成长

流式计算框架

LeetCode题解:17. 电话号码的字母组合,回溯,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

【有奖征文】WEB前端大作战,走在技术最前端!

华为云开发者联盟

node.js Vue 大前端 Web Web框架

一周信创舆情观察(4.5~4.11)

统小信uos

很坑的Could not transfer artifact报错

01Running

maven Mac IDEA

聪明人的训练(十六)

Changing Lin

4月日更

const与指针交集的那些事

Bob

c++ 编程语言 4月日更

Golang Slice 数组和切片

escray

学习 极客时间 Go 语言 4月日更

Python OpenCV 图像2D直方图,取经之旅第 27 天

梦想橡皮擦

Python OpenCV 4月日更

构建智慧金融新引擎|DataPipeline与巨杉数据库完成产品兼容互认证

DataPipeline数见科技

在有道 | L同学:一位十五年有道人的成长故事

有道技术团队

分享 访谈录 阅读 网易有道

拍乐云入选 2021 爱分析·产业数字化厂商全景报告

拍乐云Pano

RTC

Markdown 文档可折叠化展示

耳东@Erdong

4月日更

车行易携手睿象云:告警管理体系全升级

睿象云

使用Python映射,过滤和缩减函数:所有您需要知道的

华为云开发者联盟

Python 函数 映射 内置函数

Python基础之:struct和格式化字符

程序那些事

Python 数据分析 程序那些事

使用transform制作书本翻页效果

空城机

JavaScript 大前端 4月日更 书本翻页

微服务中台技术解析之全链路分布式追踪系统实践_语言 & 开发_冯刚_InfoQ精选文章