一、链路追踪

微服务架构是将单个应用程序被划分成各种小而连接的服务,每一个服务完成一个单一的业务功能,相互之间保持独立和解耦,每个服务都可以独立演进。相对于传统的单体服务,微服务具有隔离性、技术异构性、可扩展性以及简化部署等优点。

​ 同样的,微服务架构在带来诸多益处的同时,也为系统增加了不少复杂性。它作为一种分布式服务,通常部署于由不同的数据中心、不同的服务器组成的集群上。而且,同一个微服务系统可能是由不同的团队、不同的语言开发而成。通常一个应用由多个微服务组成,微服务之间的数据交互需要通过远过程调用的方式完成,所以在一个由众多微服务构成的系统中,请求需要在各服务之间流转,调用链路错综复杂,一旦出现问题,是很难进行问题定位和追查异常的。

​ 链路追踪系统就是为解决上述问题而产生的,它用来追踪每一个请求的完整调用链路,记录从请求开始到请求结束期间调用的任务名称、耗时、标签数据以及日志信息,并通过可视化的界面进行分析和展示,来帮助技术人员准确地定位异常服务、发现性能瓶颈、梳理调用链路以及预估系统容量。

​ 链路追踪系统的理论模型几乎都借鉴了 Google 的一篇论文”Dapper, a Large-Scale Distributed Systems Tracing Infrastructure”,典型产品有Uber jaeger、Twitter zipkin、淘宝鹰眼等。这些产品的实现方式虽然不尽相同,但核心步骤一般都有三个:数据采集、数据存储和查询展示

​ 链路追踪系统第一步,也是最基本的工作就是数据采集。在这个过程中,链路追踪系统需要侵入用户代码进行埋点,用于收集追踪数据。但是由于不同的链路追踪系统的API互不兼容,所以埋点代码写法各异,导致用户在切换不同链路追踪产品时需要做很大的改动。为了解决这类问题,于是诞生了OpenTracing规范,旨在统一链路追踪系统的API。

二、OpenTracing规范

​ OpenTracing 是一套分布式追踪协议,与平台和语言无关,具有统一的接口规范,方便接入不同的分布式追踪系统。

​ OpenTracing语义规范详见:https://github.com/opentracing/specification/blob/master/specification.md

2.1 数据模型(Data Model)

​ OpenTracing语义规范中定义的数据模型有 Trace、Sapn以及Reference。

2.1.1 Trace

​ Trace表示一条完整的追踪链路,例如:一个事务或者一个流程的执行过程。一个 Trace 是由一个或者多个 Span 组成的有向无环图(DAG)。

下图表示一个由8个Span组成的Trace:

[Span A]  ←←←(the root span)|+------+------+|             |[Span B]      [Span C] ←←←(Span C is a `ChildOf` Span A)|             |[Span D]      +---+-------+|           |[Span E]    [Span F] >>> [Span G] >>> [Span H]↑↑↑(Span G `FollowsFrom` Span F)

按照时间轴方式更为直观地展现该Trace:

––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time[Span A···················································][Span B··············································][Span D··········································][Span C········································][Span E·······]        [Span F··] [Span G··] [Span H··]

2.1.2 Span

​ Span表示一个独立的工作单元,它是一条追踪链路的基本组成要素。例如:一次RPC调用、一次函数调用或者一次Http请求。

每个Span封装了如下状态:

  • 操作名称用于表示该Span的任务名称。 例如:一个 RPC方法名, 一个函数名,或者大型任务中的子任务名称。
  • 开始时间戳任务开始时间。
  • 结束时间戳。任务结束时间。通过Span的结束时间戳和开始时间戳,就能够计算出该Span的整体耗时。
  • 一组Span标签每一个Span标签是一个键值对。键必须是字符串,值可以是字符串、布尔或数值类型。

常见标签键可参考:https://github.com/opentracing/specification/blob/master/semantic_conventions.md

一组Span日志每一条Span日志由一个键值对和一个相应的时间戳组成。键必须是字符串,值可以是任何类型。

常见日志键参考:https://github.com/opentracing/specification/blob/master/semantic_conventions.md

2.1.3 Reference

​ 一个Span可以与一个或者多个Span存在因果关系,这种关系称为Reference。OpenTracing目前定义了两种关系:ChildOf(父子)关系 和 FollowsFrom(跟随)关系。

  • ChildOf关系父Span的执行依赖子Span的执行结果,此时子Span对父Span的Reference关系是ChildOf。比如对于一次RPC调用,服务端的Span(子Span)与客户端调用的Span(父Span)就是ChildOf关系。
  • FollowsFrom关系父Span的执行不依赖子Span的执行结果,此时子Span对父Span的Reference关系是FollowFrom。FollowFrom常用于表示异步调用,例如消息队列中Consumer Span与Producer Span之间的关系。

2.2 应用接口(API)

2.2.1 Tracer

​ Tracer接口用于创建Span、跨进程注入数据和提取数据。通常具有以下功能:

  • Start a new span
    创建并启动一个新的Span。
  • Inject
    将SpanContext注入载体(Carrier)。
  • Extract
    从载体(Carrier)中提取SpanContext。

2.2.2 Span

  • Retrieve a SpanContext
    返回Span对应的SpanContext。
  • Overwrite the operation name
    更新操作名称。
  • Set a span tag
    设置Span标签数据。
  • Log structured data
    记录结构化数据。
  • Set a baggage item
    baggage item是字符串型的键值对,它对应于某个 Span,随Trace一起传播。由于每个键值都会被拷贝到每一个本地及远程的子Span,这可能导致巨大的网络和CPU开销。
  • Get a baggage item
    获取baggage item的值。
  • Finish
    结束一个Span。

2.2.3 Span Context

​ 用于携带跨越服务边界的数据,包括trace ID、Span ID以及需要传播到下游Span的baggage数据。在OpenTracing中,强制要求SpanContext实例不可变,以避免在Span完成和引用时出现复杂的生命周期问题。

2.2.4 NoopTracer

​ 所有对OpenTracing API的实现,必须提供某种形式的NoopTracer,用于标记控制OpenTracing或注入对测试无害的东西。

三、Jaeger

​ Jaeger是Uber开源的分布式追踪系统,它的应用接口完全遵循OpenTracing规范。jaeger本身采用go语言编写,具有跨平台跨语言的特性,提供了各种语言的客户端调用接口,例如c++、java、go、python、ruby、php、nodejs等。

项目地址:https://github.com/jaegertracing

3.1 Jaeger组件

  • jaeger-clientjaeger的客户端代码库,它实现了OpenTracing协议。当我们的应用程序将其装配后,负责收集数据,并发送到jaeger-agent。这是我们唯一需要编写代码的地方
  • jaeger-agent负责接收从jaeger-client发来的Trace/Span信息,并批量上传到jaeger-collector。
  • jaeger-collector负责接收从jaeger-agent发来的Trace/Span信息,并经过校验、索引等处理,然后写入到后端存储。
  • data store负责数据存储。Jaeger的数据存储是一个可插拔的组件,目前支持Cassandra、ElasticSearch和Kafka。
  • jaeger-query & ui负责数据查询,并通过前端界面展示查询结果。

3.2 快速入门

​ Jaeger官方提供了all-in-one镜像,方便快速进行测试:

# 拉取镜像
$docker pull jaegertracing/all-in-one:latest# 运行镜像
$docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 14268:14268 -p 9411:9411 -p 16686:16686 jaegertracing/all-in-one:latest

​ 通过all-in-one镜像启动,我们发现Jaeger占据了很多端口。以下是端口使用说明:

​ 启动后,我们可以访问 http://localhost:16686 ,在浏览器中查看和查询收集的数据。

​ 由于通过all-in-one镜像方式收集的数据都存储在docker中,无法持久保存,所以只能用于开发或者测试环境,无法用于生产环境。生产环境中需要依据实际情况,分别部署各个组件。

四、Jaeger在业务代码中的应用

​ 系统中使用Jaeger非常简单,只需要在原有程序中插入少量代码。以下代码模拟了一个查询用户账户余额,执行扣款的业务场景:

4.1 初始化jaeger函数

​ 主要是按照实际需要配置有关参数,例如服务名称、采样模式、采样比例等等。

func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 构造配置信息cfg := &config.Configuration{// 设置服务名称ServiceName: "ServiceAmount",// 设置采样参数Sampler: &config.SamplerConfig{Type:  "const", // 全采样模式Param: 1,       // 开启状态},}// 生成一条新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 设置tracer为全局单例对象opentracing.SetGlobalTracer(tracer)}return
}

4.2 检测用户余额函数

​ 用于检测用户余额,模拟一个子任务Span。

func CheckBalance(request string, ctx context.Context) {// 创建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "CheckBalance")// 模拟系统进行一系列的操作,耗时1/3秒time.Sleep(time.Second / 3)// 示例:将需要追踪的信息放入tagspan.SetTag("request", request)span.SetTag("reply", "CheckBalance reply")// 结束当前spanspan.Finish()log.Println("CheckBalance is done")
}

4.3 从用户账户扣款函数

​ 从用户账户扣款,模拟一个子任务span。

func Reduction(request string, ctx context.Context) {// 创建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "Reduction")// 模拟系统进行一系列的操作,耗时1/2秒time.Sleep(time.Second / 2)// 示例:将需要追踪的信息放入tagspan.SetTag("request", request)span.SetTag("reply", "Reduction reply")// 结束当前spanspan.Finish()log.Println("Reduction is done")
}

4.4 主函数

​ 初始化jaeger环境,生成tracer,创建父span,以及调用查询余额和扣款两个子任务span。

package mainimport ("context""fmt""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""io""log""time"
)func main() {// 初始化jaeger,创建一条新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()// 创建一个新span,作为父span,开始计费过程span := tracer.StartSpan("CalculateFee")// 生成父span的contextctx := opentracing.ContextWithSpan(context.Background(), span)// 示例:设置一个span标签信息span.SetTag("db.instance", "customers")// 示例:输出一条span日志信息span.LogKV("event", "timed out")// 将父span的context作为参数,调用检测用户余额函数CheckBalance("CheckBalance request", ctx)// 将父span的context作为参数,调用扣款函数Reduction("Reduction request", ctx)// 结束父spanspan.Finish()
}

五、Jaeger在gRPC微服务中的应用

​ 我们依然模拟了一个查询用户账户余额,执行扣款的业务场景,并把查询用户账户余额和执行扣款功能改造为gRPC微服务:

5.1 gRPC Server端代码

main.go:

​ 代码使用了第三方依赖库http://github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing,该依赖库将OpenTracing封装为通用的gRPC中间件,并通过gRPC拦截器无缝嵌入gRPC服务中。

package mainimport ("fmt""github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""google.golang.org/grpc""google.golang.org/grpc/reflection""grpc-jaeger-server/account""io""log""net"
)// 初始化jaeger
func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 构造配置信息cfg := &config.Configuration{// 设置服务名称ServiceName: "ServiceAmount",// 设置采样参数Sampler: &config.SamplerConfig{Type:  "const", // 全采样模式Param: 1,       // 开启全采样模式},}// 生成一条新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 设置tracer为全局单例对象opentracing.SetGlobalTracer(tracer)}return
}func main() {// 初始化jaeger,创建一条新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()log.Println("succeed to init jaeger")// 注册gRPC account服务server := grpc.NewServer(grpc.UnaryInterceptor(grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer))))account.RegisterAccountServer(server, &AccountServer{})reflection.Register(server)log.Println("succeed to register account service")// 监听gRPC account服务端口listener, err := net.Listen("tcp", ":8080")if err != nil {log.Println(err)return}log.Println("starting register account service")// 开启gRpc account服务if err := server.Serve(listener); err != nil {log.Println(err)return}
}

计费微服务 accountsever.go:

package mainimport ("github.com/opentracing/opentracing-go""golang.org/x/net/context""grpc-jaeger-server/account""time"
)// 计费服务
type AccountServer struct{}// 检测用户余额微服务,模拟子span任务
func (s *AccountServer) CheckBalance(ctx context.Context, request *account.CheckBalanceRequest) (response *account.CheckBalanceResponse, err error) {response = &account.CheckBalanceResponse{Reply: "CheckBalance Reply", // 处理结果}// 创建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "CheckBalance")// 模拟系统进行一系列的操作,耗时1/3秒time.Sleep(time.Second / 3)// 将需要追踪的信息放入tagspan.SetTag("request", request)span.SetTag("reply", response)// 结束当前spanspan.Finish()return response, err
}// 从用户账户扣款微服务,模拟子span任务
func (s *AccountServer) Reduction(ctx context.Context, request *account.ReductionRequest) (response *account.ReductionResponse, err error) {response = &account.ReductionResponse{Reply: "Reduction Reply", // 处理结果}// 创建子spanspan, _ := opentracing.StartSpanFromContext(ctx, "Reduction")// 模拟系统进行一系列的操作,耗时1/3秒time.Sleep(time.Second / 3)// 将需要追踪的信息放入tagspan.SetTag("request", request)span.SetTag("reply", response)// 结束当前spanspan.Finish()return response, err
}

5.2 gRPC Client端代码main.go:

package mainimport ("context""fmt""github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing""github.com/opentracing/opentracing-go""github.com/uber/jaeger-client-go/config""google.golang.org/grpc""grpc-jaeger-client/account""io""log"
)// 初始化jaeger
func initJaeger() (tracer opentracing.Tracer, closer io.Closer, err error) {// 构造配置信息cfg := &config.Configuration{// 设置服务名称ServiceName: "ServiceAmount",// 设置采样参数Sampler: &config.SamplerConfig{Type:  "const", // 全采样模式Param: 1,       // 开启全采样模式},}// 生成一条新tracertracer, closer, err = cfg.NewTracer()if err == nil {// 设置tracer为全局单例对象opentracing.SetGlobalTracer(tracer)}return
}func main() {// 初始化jaeger,创建一条新tracertracer, closer, err := initJaeger()if err != nil {panic(fmt.Sprintf("ERROR: cannot init Jaeger: %vn", err))}defer closer.Close()log.Println("succeed to init jaeger")// 创建一个新span,作为父spanspan := tracer.StartSpan("CalculateFee")// 函数返回时关闭spandefer span.Finish()// 生成span的contextctx := opentracing.ContextWithSpan(context.Background(), span)// 连接gRPC serverconn, err := grpc.Dial("localhost:8080",grpc.WithInsecure(),grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(tracer),)))if err != nil {log.Println(err)return}// 创建gRPC计费服务客户端client := account.NewAccountClient(conn)// 将父span的context作为参数,调用检测用户余额的gRPC微服务checkBalanceResponse, err := client.CheckBalance(ctx,&account.CheckBalanceRequest{Account: "user account",})if err != nil {log.Println(err)return}log.Println(checkBalanceResponse)// 将父span的context作为参数,调用扣款的gRPC微服务reductionResponse, err := client.Reduction(ctx,&account.ReductionRequest{Account: "user account",Amount: 1,})if err != nil {log.Println(err)return}log.Println(reductionResponse)
}

注:
本文全部源代码位于:https://github.com/wangshizebin/micro-service
本文时候用的开发工具为:goland
原文链接:https://www.cnblogs.com/wanghao72214/p/13932810.html

如果觉得本文对你有帮助,可以点赞关注支持一下,也可以点进我主页关注我公众号,上面有更多技术干货文章以及相关资料共享,大家一起学习进步!

ajax请求是宏任务还是微任务_微服务-如何解决链路追踪问题相关推荐

  1. ajax请求是宏任务还是微任务_ASP.NET Web API基础(04)---异步编程和跨域请求 - 高原秃鹫...

    异步编程 .1 线程回顾 说到异步编程,离不开多线程.在前面的课程中我们学习过多线程.回顾一下我们之前的例子. public static void DoWork() { (1000); (" ...

  2. ajax请求成功后打开新开窗口(window.open())被拦截的解决方法

    问题:今天在做项目时需要在ajax请求成功后打开一个新的窗口,此时遇到浏览拦截了新窗口的问题,尝试在ajax 回调函数中模拟执行 click 或者 submit 等用户行为(trigger('clic ...

  3. qiankun 微前端_微前端方案 qiankun(实践及总结)

    ❝ 作者:沉末_ 链接:https://juejin.im/post/5ed73b73e51d4578724e3fa4 ❞ 什么是微前端? 我们先来看两个实际的场景: 1. 复用别的的项目页面 通常, ...

  4. 微服务下的链路追踪(Sleuth+Zipkin)

    目录 Sleuth简介 相关术语 使用Sleuth 引入依赖 创建服务 product-service order-service 启动&测试 Zipkin 使用Zipkin 参考文章 Sle ...

  5. springboot 之 微服务调用 之 链路追踪

    说明:本文来自 本篇主要内容 一.为什么要用链路追踪? 1.1 因:拆分服务单元 微服务架构其实是一个分布式的架构,按照业务划分成了多个服务单元. 由于服务单元的数量是很多的,有可能几千个,而且业务也 ...

  6. ajax请求是宏任务还是微任务_好程序员web前端学习路线分享了解AJAX是什么

    好程序员web前端学习路线分享了解AJAX是什么首先是服务器 什么是服务器:咱们的页面来源于服务器:实例(在phpnwo上面存放一个页面), 咱们把页面放在互联网的服务器上,就有了自己的网站了. 1. ...

  7. ajax请求是宏任务还是微任务_微服务编排引擎Cadence简介

    原文来源:https://cadenceworkflow.io/ 1.概述(Overview)2.使用案例(Use cases)2.1.定时轮询(aka Distributed Cron)2.2.微服 ...

  8. ajax请求是宏任务还是微任务_声望系统详细了解,千万要把每周任务做完,不然损失很多声望经验...

    原神这款游戏在最近也是终于来了一次大更新,整个游戏当中多出了不少东西,让很多玩家都没了解透,虽然在之前看了官方的直播,对更新中的声望系统有了一些了解,但是在更新之后还是有一些陌生,一些独特的机制是完全 ...

  9. ajax和flash,flask ajax请求后flash方法(消息闪现)无效问题的解决方法

    一.问题描述 一个好的应用和用户界面需要良好的反馈,flask的消息闪现系统提供了一个良好的反馈方式.闪现系统的基本工作方式是:在且只在下一个请求中访问上一个请求结束时记录的消息. 正常使用flash ...

最新文章

  1. 关于Linux中权限列中的加号及点的深度探索
  2. CCNA重点难点:生成树配置
  3. Golang的匿名函数和闭包
  4. python字符串三种常用的方法或函数_python中字符串常用的函数
  5. Morpheus - DNS Spoofing
  6. 【Python3网络爬虫开发实战】4-解析库的使用-3 使用pyquery
  7. Python“文件操作”Excel篇(上)
  8. python多个dataframe_python对分组在多个列上的dataframe进行条件和运算
  9. 【Python笔记】正则表达式
  10. C++11 double转化为string
  11. inline函数的好处与缺点
  12. python可视化图表生成(一)
  13. endnote参考文献排版_基于国家标准的 EndNote 输出样式模板
  14. 超分算法之SRCNN
  15. python pygame实现简单的网游 1
  16. codesys工控机_CODESYS在系统集成项目中的运用案例
  17. 镜头评价指标及测试方法(一)
  18. 弯管机编程软件电脑版_花了一年时间开发的弯管机YBC编程软件
  19. 用Excel做了7天报表,这个领导喜欢的可视化工具,只用了7小时
  20. 2022-2023 通信工程专业毕业设计题目选题推荐 - 100例

热门文章

  1. Numpy基础(part2)--ndarray数组
  2. keras模型保存和加载
  3. SAP Spartacus 产品明细页面的 url 设计和数据源
  4. ExpressionChangedAfterItHasBeenCheckedError - Expression has changed after it was checked
  5. 手术期间重读《倚天屠龙记》
  6. SAP Spartacus里如何查看HTTP请求的状态
  7. Angular HTML template的解析位置
  8. SAP Fiori图标(icon)设计原理:一个可以查看 SAP UI5 所有可用图标的工具
  9. SAP Fiori应用里出现http request错误的原因分析
  10. Spring 中的如何自定义事件处理(Custom Event)