刘沙河 刘沙河
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希

花开半夏,半夏花开
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希
  • rpc

  • grpc

    • 【grpc】1.初识
    • 【grpc】2.上下文 metadata
    • 【grpc】3.健康检查
    • 【grpc】 4.keepalive
    • 【grpc】 5.命名解析
    • 【grpc】 6.中间件&拦截器
    • 【grpc】 7.负载均衡
    • 【grpc】 8.身份认证
    • 【grpc】 9.超时重试
    • 【grpc】 10.链路追踪
      • 1. 版本命名策略
      • 2. OpenTracing数据模型
        • 2.1 Span间关系
      • 3. OpenTracing API
        • 3.1 Tracer
        • 创建一个新Span
        • 将SpanContext上下文Inject(注入)到 carrier
        • 将SpanContext上下文从carrier中Extract(提取)
        • 3.2 Span
        • 除了通过Span获取SpanContext
        • 复写操作名(operation name)
        • 结束Span
        • 为Span设置tag
        • Log结构化数据
        • 设置一个baggage(随行数据)元素
        • 获取一个baggage元素
        • 3.3 SpanContext
        • 遍历所有的baggage元素
        • 3.4 NoopTracer
        • 3.5 可选 API 元素
      • 1. 普通多级关联调用
      • 2. grpc 配置jaeger
      • 3. gin框架封装jaeger
    • 【grpc】11.grpc-gw将gRPC 转 RESTful api
    • 【grpc】12.grpc-gw自定义选项
  • protobuf

  • rpc+grpc
  • grpc
bigox
2023-04-27
目录

【grpc】 10.链路追踪

# 什么是链路追踪

  • 分布式链路追踪(Distributed Tracing),也叫 分布式链路跟踪,分布式跟踪,分布式追踪 等等。

  • 链路追踪是分布式系统下的一个概念,它的目的就是要解决上面所提出的问题,也就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如,各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。

    image-20230430125920828

# 技术选型

zipkin jaeger skywalking
OpenTracing兼容 是 是 是
客户端支持语言 java,c#,go,php,python等 java,c#,go,php,python等 Java, .NET Core, NodeJS ,PHP,python,go
存储 ES,mysql,Cassandra,内存 ES,kafka,Cassandra,内存 ES,H2,mysql,TIDB,sharding sphere
传输协议支持 http,MQ udp/http gRPC
ui丰富程度 低 中 中
实现方式-代码侵入性 拦截请求,侵入 拦截请求,侵入 字节码注入,无侵入
扩展性 高 高 中
trace查询 支持 支持 支持
性能损失 中 中 低

# jaeger

  1. # 安装

  • github : https://github.com/jaegertracing/jaeger/tree/master/examples/hotrod

  • Docker 简单安装

    docker run \
      --rm \
      --name jaeger \
      -p6831:6831/udp \
      -p16686:16686 \
      jaegertracing/all-in-one:latest
    
    1
    2
    3
    4
    5
    6
  1. # 架构

image-20230430130140166

  • Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。

  • Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。

  • Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。

  • Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。

  • Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。

分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示

# OpenTracing

  • 这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念。

# 1. 版本命名策略

  • OpenTracing标准使用Major.Minor版本命名策略(即:大版本.小版本),但不包含.Patch版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型。(如果你想知道更多关于制定此版本政策的原因,可参考specification#2 (opens new window))

# 2. OpenTracing数据模型

  • Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。

  • Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span.在此译本中,为了便于理解,Span和其他标准内声明的词汇,全部不做名词翻译。

    • 例如:下面的示例Trace就是由8个Span组成:

      单个Trace中,span间的因果关系
      [Span A]  ←←←(the root span)
                  |
           +------+------+
           |             |
      [Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
           |             |
       [Span D]      +---+-------+
                      |           |
       [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                              ↑
                                              ↑
                                              ↑
                                (Span G 在 Span F 后被调用, FollowsFrom)
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
  • 有些时候,使用下面这种,基于时间轴的时序图可以更好的展现Trace(调用链):

    单个Trace中,span间的时间关系
    
    ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
    
    [Span A···················································]
    [Span B··············································]
    [Span D··········································]
    [Span C········································]
    [Span E·······]        [Span F··] [Span G··] [Span H··]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 每个Span包含以下的状态:(译者注:由于这些状态会反映在OpenTracing API中,所以会保留部分英文说明)

    • An operation name,操作名称

    • A start timestamp,起始时间

    • A finish timestamp,结束时间

    • Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型。

    • Span Log,一组span的日志集合。 每次log操作包含一个键值对,以及一个时间戳。 键值对中,键必须为string,值可以是任意类型。 但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型。

    • SpanContext,Span上下文对象 (下面会详细说明)

    • References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)

  • 每一个SpanContext包含以下状态:

    • 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输

    • Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输

# 2.1 Span间关系

  • 一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。

  • ChildOf 引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:

    • 一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系

    • 一个sql insert操作的span,和ORM的save方法的span构成ChildOf关系

    • 很多span可以并行工作(或者分布式工作)都可能是一个父级的span的子项,他会合并所有子span的执行结果,并在指定期限内返回

  • FollowsFrom 引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子span和父span之间是"FollowsFrom"的因果关系。"FollowsFrom"关系可以被分为很多不同的子类型,未来版本的OpenTracing中将正式的区分这些类型

# 3. OpenTracing API

  • OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer, Span 和 SpanContext。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法。

  • 当我们讨论“可选”参数时,需要强调的是,不同的语言针对可选参数有不同理解,概念和实现方式 。例如,在Go中,我们习惯使用"functional Options",而在Java中,我们可能使用builder模式。

# 3.1 Tracer

  • Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。它具有如下官方能力:
  1. # 创建一个新Span

  • 必填参数

    operation name, 操作名, 一个具有可读性的字符串,代表这个span所做的工作(例如:RPC方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,"get_user" 作为操作名,比 "get_user/314159"更好。

    例如,假设一个获取账户信息的span会有如下可能的名称:

    操作名 指导意见
    get 太抽象
    get_account/792 太明确
    get_account 正确的操作名,关于account_id=792的信息应该使用Tag (opens new window)操作
  • 可选参数

    • 零个或者多个关联(references)的SpanContext,如果可能,同时快速指定关系类型,ChildOf 还是 FollowsFrom。

    • 一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。

    • 零个或者多个tag。

  • 返回值,返回一个已经启动Span实例(已启动,但未结束。译者注:英语上started和finished理解容易混淆)

  1. # 将SpanContext上下文Inject(注入)到 carrier

  • 必填参数

    • **SpanContext**实例

    • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何对SpanContext进行编码放入到carrier中。

    • carrier,根据format确定。Tracer实现根据format声明的格式,将SpanContext序列化到carrier对象中。

  1. # 将SpanContext上下文从carrier中Extract(提取)

  • 必填参数

    • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何从carrier中解码SpanContext。

    • carrier,根据format确定。Tracer实现根据format声明的格式,从carrier中解码SpanContext。

  • 返回值,返回一个SpanContext实例,可以使用这个SpanContext实例,通过Tracer创建新的Span。

注意,对于Inject(注入)和Extract(提取),format是必须的。

  • Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext是如何编码的。所有的Tracer实现,都必须支持下面的format。

    • Text Map: 基于字符串:字符串的map,对于key和value不约束字符集。

    • HTTP Headers: 适合作为HTTP头信息的,基于字符串:字符串的map。(RFC 7230 (opens new window).在工程实践中,如何处理HTTP头具有多样性,强烈建议tracer的使用者谨慎使用HTTP头的键值空间和转义符)

    • Binary: 一个简单的二进制大对象,记录SpanContext的信息。

# 3.2 Span

  • 当Span结束后(span.finish()),除了通过Span获取SpanContext外,下列其他所有方法都不允许被调用。
  1. # 除了通过Span获取SpanContext

  • 不需要任何参数。

  • 返回值,Span构建时传入的SpanContext。这个返回值在Span结束后(span.finish()),依然可以使用。

  1. # 复写操作名(operation name)

  • 必填参数
    • 新的操作名operation name,覆盖构建Span时,传入的操作名。
  1. # 结束Span

可选参数

  • 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。
  1. # 为Span设置tag

必填参数

  • tag key,必须是string类型
  • tag value,类型为字符串,布尔或者数字

注意,OpenTracing标准包含**"standard tags,标准Tag" (opens new window)**,此文档中定义了Tag的标准含义。

  1. # Log结构化数据

必填参数

  • 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些OpenTracing实现,可能支持更多的log值类型。

可选参数

  • 一个明确的时间戳。如果指定时间戳,那么它必须在span的开始和结束时间之内。

注意,OpenTracing标准包含**"standard log keys,标准log的键" (opens new window)**,此文档中定义了这些键的标准含义。

  1. # 设置一个baggage(随行数据)元素

Baggage元素是一个键值对集合,将这些值设置给给定的Span,Span的SpanContext,以及所有和此Span有直接或者间接关系的本地Span。 也就是说,baggage元素随trace一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递)

Baggage元素为OpenTracing的实现全栈集成,提供了强大的功能 (例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统。由于它如此强大的功能,他也会产生巨大的开销,请小心使用此特性。

再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。

必填参数

  • baggage key, 字符串类型
  • baggage value, 字符串类型
  1. # 获取一个baggage元素

必填参数

  • baggage key, 字符串类型

返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如Null)。

# 3.3 SpanContext

  • 相对于OpenTracing中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。 OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContext和references (opens new window),

  • OpenTracing要求,SpanContext是不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。

  1. # 遍历所有的baggage元素

  • 遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext实例,高效的遍历所有的baggage元素

# 3.4 NoopTracer

所有的OpenTracing API实现,必须提供某种方式的NoopTracer实现。NoopTracer可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer在他自己的模块中。

# 3.5 可选 API 元素

有些语言的OpenTracing实现,为了在串行处理中,传递活跃的Span或SpanContext,提供了一些工具类。例如,opentracing-go中,通过context.Context机制,可以设置和获取活跃的Span。

# Go 链路追踪

  • 一库: https://github.com/jaegertracing/jaeger-client-go

# 1. 普通多级关联调用

image-20211118220208887

/*
 * @date: 2021/11/18
 * @desc: ...
 */

package main

import (
	"context"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	jaegerlog "github.com/uber/jaeger-client-go/log"
	"log"
	"time"
)

func main() {
	cfg := jaegercfg.Configuration{
		ServiceName: "goJaegerTest2",
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:           true,
			LocalAgentHostPort: "127.0.0.1:6831",
		},
	}
	// Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
	// and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
	// frameworks.
	jLogger := jaegerlog.StdLogger
	//jMetricsFactory := metrics.NullFactory

	// Initialize tracer with a logger and a metrics factory
	tracer, closer, err := cfg.NewTracer(
		jaegercfg.Logger(jLogger),
		//jaegercfg.Metrics(jMetricsFactory),
	)
	if err != nil {
		log.Printf("Could not initialize jaeger tracer: %s", err.Error())
		return
	}
	opentracing.SetGlobalTracer(tracer)
	defer closer.Close()
	span := opentracing.StartSpan("gpSpanTest3")
	ctx := opentracing.ContextWithSpan(context.Background(),span)
	span2,_ := opentracing.StartSpanFromContext(ctx,"span2")
	time.Sleep(2 * time.Second)
	defer span2.Finish()
	defer span.Finish()

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 2. grpc 配置jaeger

  • 一库: https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc

  • server

    package main
    
    import (
    	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
    	"github.com/opentracing/opentracing-go"
    	"github.com/uber/jaeger-client-go"
    	jaegercfg "github.com/uber/jaeger-client-go/config"
    	jaegerlog "github.com/uber/jaeger-client-go/log"
    	"golang.org/x/net/context"
    	"google.golang.org/grpc"
    	"log"
    	"net"
    	"picturePro/grpcTest/proto"
    )
    
    type Server struct {
    }
    
    func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {
    	return &proto.HelloReply{
    		Message: "Hello " + request.Name,
    	}, nil
    }
    
    func main() {
    	cfg := jaegercfg.Configuration{
    		ServiceName: "goJaegerTest-http-server",
    		Sampler: &jaegercfg.SamplerConfig{
    			Type:  jaeger.SamplerTypeConst,
    			Param: 1,
    		},
    		Reporter: &jaegercfg.ReporterConfig{
    			LogSpans:           true,
    			LocalAgentHostPort: "127.0.0.1:6831",
    		},
    	}
    	jLogger := jaegerlog.StdLogger
    	//jMetricsFactory := metrics.NullFactory
    	tracer, closer, err := cfg.NewTracer(
    		jaegercfg.Logger(jLogger),
    		//jaegercfg.Metrics(jMetricsFactory),
    	)
    	if err != nil {
    		log.Printf("Could not initialize jaeger tracer: %s", err.Error())
    		return
    	}
    	defer closer.Close()
    
    	opentracing.SetGlobalTracer(tracer)
    	var serverOptions []grpc.ServerOption
    	serverOptions = append(serverOptions, grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer)))
    	g := grpc.NewServer(serverOptions...)
    	proto.RegisterGreeterServer(g, &Server{})
    	listen, err := net.Listen("tcp", ":50051")
    	if err != nil {
    		panic(err.Error())
    	}
    	err = g.Serve(listen)
    	if err != nil {
    		panic(err.Error())
    	}
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
  • client

    package main
    
    import (
    	"context"
    	"fmt"
    	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
    	"github.com/opentracing/opentracing-go"
    	"github.com/uber/jaeger-client-go"
    	jaegercfg "github.com/uber/jaeger-client-go/config"
    	jaegerlog "github.com/uber/jaeger-client-go/log"
    	"google.golang.org/grpc"
    	"log"
    	"picturePro/grpcTest/proto"
    )
    
    func main() {
    	cfg := jaegercfg.Configuration{
    		ServiceName: "goJaegerTest-http",
    		Sampler: &jaegercfg.SamplerConfig{
    			Type:  jaeger.SamplerTypeConst,
    			Param: 1,
    		},
    		Reporter: &jaegercfg.ReporterConfig{
    			LogSpans:           true,
    			LocalAgentHostPort: "127.0.0.1:6831",
    		},
    	}
    	// Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
    	// and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
    	// frameworks.
    	jLogger := jaegerlog.StdLogger
    	//jMetricsFactory := metrics.NullFactory
    
    	// Initialize tracer with a logger and a metrics factory
    	tracer, closer, err := cfg.NewTracer(
    		jaegercfg.Logger(jLogger),
    		//jaegercfg.Metrics(jMetricsFactory),
    	)
    	if err != nil {
    		log.Printf("Could not initialize jaeger tracer: %s", err.Error())
    		return
    	}
    	defer closer.Close()
    
    	opentracing.SetGlobalTracer(tracer)
    	conn, err := grpc.Dial("127.0.0.1:50051",
    		grpc.WithInsecure(),
    		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
    	)
    	if err != nil {
    		panic(err)
    	}
    	defer conn.Close()
    	c := proto.NewGreeterClient(conn)
    	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(r.Message)
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

# 3. gin框架封装jaeger

进一步封装修改 otgrpc 源码, 负载均衡模式下 方便同一个业务块中多个子业务模块的链路区分

  1. 创建拦截器tracing middleware

    • tracing.go

      package middlewares
      
      import (
      	"fmt"
      	"github.com/gin-gonic/gin"
      	"github.com/opentracing/opentracing-go"
      	"github.com/uber/jaeger-client-go"
      	jaegercfg "github.com/uber/jaeger-client-go/config"
      	jaegerlog "github.com/uber/jaeger-client-go/log"
      	"mxshop-api/goods-web/global"
      )
      
      func Trace() gin.HandlerFunc {
      	return func(ctx *gin.Context) {
      		cfg := jaegercfg.Configuration{
      			ServiceName: global.ServerConfig.JaegerInfo.Name,
      			Sampler: &jaegercfg.SamplerConfig{
      				Type:  jaeger.SamplerTypeConst,
      				Param: 1,
      			},
      			Reporter: &jaegercfg.ReporterConfig{
      				LogSpans:           true,
      				LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.JaegerInfo.Host, global.ServerConfig.JaegerInfo.Port),
      			},
      		}
      		logger := jaegerlog.StdLogger
      		tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(logger))
      		if err != nil {
      			panic(err)
      		}
      		opentracing.SetGlobalTracer(tracer)
      		defer closer.Close()
      
      		startSpan := tracer.StartSpan(ctx.Request.URL.Path)
      		defer startSpan.Finish()
      		ctx.Set("tracer", tracer)
      		ctx.Set("parentSpan", startSpan)
      		ctx.Next()
      
      	}
      
      }
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
  2. 在初始化负载均衡的srv连接器userConn时添加 注入opentrace

    grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), // 注入链路追踪

    • srvConn.go

      package initialize
      
      import (
      	"fmt"
      	"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
      	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
      	"github.com/opentracing/opentracing-go"
      	"go.uber.org/zap"
      	"google.golang.org/grpc"
      	"mxshop-api/goods-web/global"
      	"mxshop-api/goods-web/proto"
      )
      
      func InitSrvConnWithLoadBLance() {
      	// 从注册中心获取user-srv用户服务信息 - 负载均衡版
      	consulInfo := global.ServerConfig.ConsulInfo
      	userConn, err := grpc.Dial(
      		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
      		grpc.WithInsecure(),
      		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
      		/* 注入链路追踪
      		问题: 全局使用同一个GlobalTracer, 多个子业务板块的链路混乱
      		*/
      		grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), // 注入链路追踪
      	)
      	if err != nil {
      		zap.S().Fatalf("[InitSrvConnWithLoadBLance] 连接失败:%s", err.Error())
      	}
      	goodsSrvClient := proto.NewGoodsClient(userConn)
      	global.GoodsSrvClient = goodsSrvClient
      }
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
  3. 更改otgrpc包源码,实现子业务trance 隔离

    • 文件路径~/otgrpc/client.go,在OpenTracingClientInterceptor方法的返回func中,加入以下注释包含的代码块:

      		...
      		var err error
      		var parentCtx opentracing.SpanContext
      
      		if parent := opentracing.SpanFromContext(ctx); parent != nil {
      			parentCtx = parent.Context()
      		}
      /* start
      		判断ctx 是否 使用户自己传的(ginContext) , 如果是, 就使用自己的tracer和parentCtx
      		负载均衡模式下 方便同一个业务块中多个子业务模块的链路区分
      		*/
      		ginContext := ctx.Value("ginContext")
      		switch ginContext.(type) {
      		case *gin.Context:
      			if itracer, ok := ginContext.(*gin.Context).Get("tracer");ok{
      				tracer = itracer.(opentracing.Tracer)
      			}
      			if parentSpan, ok := ginContext.(*gin.Context).Get("parentSpan");ok{
      				parentCtx = parentSpan.(*jaegerClient.Span).Context()
      			}
      		}
      		/*end*/
      		if otgrpcOpts.inclusionFunc != nil &&
      ...
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
    • 修改完的部分代码

  4. 在子业务的handler发送rpc请求时, 将context中设置值ginContext

    customCtx := context.WithValue(context.Background(), "ginContext", ctx) r, err := global.GoodsSrvClient.GoodsList(customCtx, request)

    • goods.go

      ...
      
      func List(ctx *gin.Context) {
      	fmt.Println("商品列表")
      	////商品的列表 pmin=abc, spring cloud, go-micro
      	request := &proto.GoodsFilterRequest{}
      
      	priceMin := ctx.DefaultQuery("pmin", "0")
      	priceMinInt, _ := strconv.Atoi(priceMin)
      	request.PriceMin = int32(priceMinInt)
      	priceMax := ctx.DefaultQuery("pmax", "0")
      	priceMaxInt, _ := strconv.Atoi(priceMax)
      	request.PriceMax = int32(priceMaxInt)
      	isHot := ctx.DefaultQuery("ih", "0")
      	if isHot == "1" {
      		request.IsHot = true
      	}
      	isNew := ctx.DefaultQuery("in", "0")
      	if isNew == "1" {
      		request.IsNew = true
      	}
      
      	isTab := ctx.DefaultQuery("it", "0")
      	if isTab == "1" {
      		request.IsTab = true
      	}
      
      	categoryId := ctx.DefaultQuery("c", "0")
      	categoryIdInt, _ := strconv.Atoi(categoryId)
      	request.TopCategory = int32(categoryIdInt)
      
      	pages := ctx.DefaultQuery("p", "0")
      	pagesInt, _ := strconv.Atoi(pages)
      	request.Pages = int32(pagesInt)
      
      	perNums := ctx.DefaultQuery("pnum", "0")
      	perNumsInt, _ := strconv.Atoi(perNums)
      	request.PagePerNums = int32(perNumsInt)
      
      	keywords := ctx.DefaultQuery("q", "")
      	request.KeyWords = keywords
      
      	brandId := ctx.DefaultQuery("b", "0")
      	brandIdInt, _ := strconv.Atoi(brandId)
      	request.Brand = int32(brandIdInt)
      
      	// 请求商品service服务
      	// 负载均衡使用 jaeger 的span关联
      	//parentSpan,_:= ctx.Get("parentSpan")
      	//opentracing.ContextWithSpan(context.Background(), parentSpan.(opentracing.Span))
      	customCtx := context.WithValue(context.Background(), "ginContext", ctx)
      	r, err := global.GoodsSrvClient.GoodsList(customCtx, request)
      	if err != nil {
      		zap.S().Errorw("[list 查询商品列表失败]")
      		api.HandleGrpcErrorToHttp(ctx, err)
      		return
      	}
      	reMap := map[string]interface{}{
      		"total": r.Total,
      	}
      	goodsList := make([]interface{}, 0)
      	for _, value := range r.Data {
      		goodsList = append(goodsList, map[string]interface{}{
      			"id":          value.Id,
      			"name":        value.Name,
      			"goods_brief": value.GoodsBrief,
      			"desc":        value.GoodsDesc,
      			"ship_free":   value.ShipFree,
      			"images":      value.Images,
      			"desc_images": value.DescImages,
      			"front_image": value.GoodsFrontImage,
      			"shop_price":  value.ShopPrice,
      			"ctegory": map[string]interface{}{
      				"id":   value.Category.Id,
      				"name": value.Category.Name,
      			},
      			"brand": map[string]interface{}{
      				"id":   value.Brand.Id,
      				"name": value.Brand.Name,
      				"logo": value.Brand.Logo,
      			},
      			"is_hot":  value.IsHot,
      			"is_new":  value.IsNew,
      			"on_sale": value.OnSale,
      		})
      	}
      	reMap["data"] = goodsList
      	ctx.JSON(http.StatusOK, reMap)
      
      }
      ...
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
#grpc
上次更新: 2023/05/04, 15:30:48
【grpc】 9.超时重试
【grpc】11.grpc-gw将gRPC 转 RESTful api

← 【grpc】 9.超时重试 【grpc】11.grpc-gw将gRPC 转 RESTful api→

最近更新
01
go与http代理
05-24
02
自制申威架构k8s-reloader
12-06
03
Docker Buildx 教程
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2024 小刘扎扎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式