Golang tracing:与 OpenTelemetry、jaeger实现 跨服务 全链路追踪

news2024/10/6 10:33:47

使用 OpenTelemetry 链路追踪说明

  1. 工作中常常会遇到需要查看服务调用关系,比如用户请求了一个接口
  2. 接口会调用其他grpc,http接口,或者内部的方法
  3. 这样的调用链路,如果出现了问题,我们需要快速的定位问题,这时候就需要一个工具来帮助我们查看调用链路
  4. OpenTelemetry就是这样一个工具
  5. 本文大概以:main 函数初始化 OpenTelemetry、启动 http server、配置httpclient 请求服务 来进行说明
  6. 完整可执行源码在:https://github.com/webws/go-moda/tree/main/example/tracing/moda_tracing
  7. 后面会补充grpc 的链路追踪

服务链路关系

关系图

用户
api1/bar
api2/bar
api3/bar
bar
bar2
bar3

说明:

  1. 用户 请求 api1(echo server) 服务的 api1/bar
  2. api1 调用 api2 (gin server) 服务的 api2/bar
  3. api2 调用 api3 (echo server )服务的 api3/bar
  4. api3 调用 内部 调用方法 bar->bar2->bar3

安装jaeger

  1. 下载jaeger:我使用的是 jaeger-all-in-one
  2. 启动 jaeger: ~/tool/jaeger-1.31.0-linux-amd64/jaeger-all-in-one
  3. 默认查看面板 地址 http://localhost:16686/
  4. tracer Batcher的地址,下面代码会体现: http://localhost:14268/api/traces

初始化 全局的 OpenTelemetry

这里openTelemetry 的exporter 以 jaeger 为例,其他的exporter 可以参考官方文档

var tracer = otel.Tracer("go-moda")
func InitJaegerProvider(jaegerUrl string, serviceName string) (func(ctx context.Context) error, error) {
	if jaegerUrl == "" {
		logger.Errorw("jaeger url is empty")
		return nil, nil
	}
	tracer = otel.Tracer(serviceName)
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerUrl)))
	if err != nil {
		return nil, err
	}
	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithResource(resource.NewSchemaless(
			semconv.ServiceNameKey.String(serviceName),
		)),
	)
	otel.SetTracerProvider(tp)
	// otel.SetTextMapPropagator(propagation.TraceContext{})
	b3Propagator := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader))
	propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, b3Propagator)
	otel.SetTextMapPropagator(propagator)
	return tp.Shutdown, nil
}

说明

  1. 上面方法的参数 jaegerUrl ,如果安装的是 jaeger-all-in-one,则地址默认为 http://localhost:14268/api/traces
  2. serviceName 是服务名称,这里我使用的是 api1,api2,api3
  3. 增加 span 可以使用 tracer.Start(ctx, “spanName”)

http服务链路追踪

上面初始化了全局的 OpenTelemetry后,在当前服务就可以使用 OpenTelemetry 的 tracer 进行链路追踪了
但如果 需要跨服务进行调用,这是不够的,比如http server之间的调用,需要:

  1. 对于 http client: httpclient 请求server的时候,将ctx(上下文) 注入到 req header 中
  2. 对于 http server: 在获取http请求时,解析req header 中的 parent trace 这样就可以在服务传输中获取到上下文,从而进行链路追踪

启动 http服务开启链路追踪

上面说的服务传输过程中, echo 和 gin 都有成熟的的中间件,我们在初始化的时候,将中间件加入到服务中即可,下面是 echo 和 gin启动服务的演示:

echo server 示例

import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
e := echo.New()
e.Server.Use(otelecho.Middleware("moda"))

gin 举例

import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
ginEngine := gin.Default()
g.GetServer().Use(otelgin.Middleware("my-server"))

http client 链路追踪

上面说到 httpserver 启动时 通过解析 req header 中的 parent trace 来进行链路追踪
那么在调用服务时,就需要将上下文注入到 req header 中
下面是我个人封装的 httpclient,可以参考:

package tracing

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

// 新增 options  http.Transport
type ClientOption struct {
	Transport *http.Transport
}

type ClientOptionFunc func(*ClientOption)

func WithClientTransport(transport *http.Transport) ClientOptionFunc {
	return func(option *ClientOption) {
		option.Transport = transport
	}
}

// CallAPI 为 http client 封装,默认使用 otelhttp.NewTransport(http.DefaultTransport)
func CallAPI(ctx context.Context, url string, method string, reqBody interface{}, option ...ClientOptionFunc) ([]byte, error) {
	clientOption := &ClientOption{}
	for _, o := range option {
		o(clientOption)
	}

	client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
	if clientOption.Transport != nil {
		client.Transport = otelhttp.NewTransport(clientOption.Transport)
	}
	var requestBody io.Reader
	if reqBody != nil {
		payload, err := json.Marshal(reqBody)
		if err != nil {
			return nil, err
		}
		requestBody = bytes.NewReader(payload)
	}
	req, err := http.NewRequestWithContext(ctx, method, url, requestBody)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	resBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return resBody, nil
}

说明

  1. 上面代码中,使用了 otelhttp.NewTransport(http.DefaultTransport) 将上下文注入到 req header 中
  2. http client 调用服务时,需要将上下文传入到 CallAPI 的 ctx 参数中

调用服务,查看链路关系

实战代码演示

http 跨服务 链路追踪 大概说完 接下来就是实战演示:

  1. 下载示例源码,启动服务,然后调用服务,查看链路关系
    源码地址:https://github.com/webws/go-moda/tree/main/example/tracing/moda_tracing
  2. 示例文件:moda_tracing下 有三个目录,分别是 api1_http,api2_http,api_http,分别对应三个服务
  3. 分别启动三个服务,进入目录 go run ./ 即可启动服务,端口分别是 8081,8082,8083
  4. 根据上面链路关系,调用api1 等待调用完成: curl localhost:8081/api1/bar
  5. 打开 jaeger 面板,查看链路关系图,http://localhost:16686/
  6. 后续示例代码启动采用 docker-compose 启动,方便演示

查看jaeger 链路

在这里插入图片描述

可以看到对应的链路,在bar,bar2,bar3 刻意sleep 加了耗时也体现了出来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/515807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sui改进提案(SIPs)及其审核流程

SIPs提供了一个清晰透明的流程,使社区可以对Sui网络提出改进建议。 Sui基金会致力于打造一个开放协作的生态,因此推出了Sui改进提案(SIPs,Sui Improvement Proposals),这是一个记录社区为去中心化未来的发…

如何利用API做好电商,接口如何凋用关键字

一.随着互联网的快速发展,电子商务成为了众多企业的首选模式,而开放API则成为了电商业务中不可或缺的部分。API(Application Programming Interface),即应用程序接口,是软件系统不同组件之间交互的约定。电…

登录校验2.0

登录校验2.0 Filter Filter详解 过滤器Filter在使用中的一些细节,主要介绍一下3个方面的细节: 过滤器的执行流程过滤器的拦截路径配置过滤器链 执行流程 过滤器当中我们拦截到了请求之后,如果希望继续访问后面的web资源,就要…

内容编排与Kubernetes

第一节 内容编排与Kubernetes 为什么要用k8s 集群环境容器部署的困境,假设我们有数十台服务器。分别部署Nginx,redis,mysql,业务服务。如何合理的分配这些资源。这里就需要用到容器编排 容器编排 在实际集群环境下&#xff0…

线性结构-队列

队列是一种先进先出First In Fisrt Out,FIFO的线性表。 与一般的数组和链表不同,队列要求所有的数据只能从一端进入,从另一端离开。 输入进入的一端叫队尾rear,数据离开的一端叫队头front。 数据只能从队尾进入队列,从队头离开队…

VSCODE配置ROS编译环境

目录 一、安装插件 二、环境配置 2.1初始化工作空间 2.2配置VSCode 2.2.1创建功能包 2.2.2配置 c_cpp_properties.json 2.2.3配置 task.json 2.2.4配置 CMakeLists.txt 三、运行程序 3.1编译程序 3.2启动ros master 3.3执行可执行文件 用VSCode编辑ROS程序时&#xf…

linux 内核内存管理

物理内存 相关数据结构 page(页) Linux 内核内存管理的实现以 page 数据结构为核心,其他的内存管理设施都基于 page 数据结构,如 VMA 管理、缺页中断、RMAP、页面分配与回收等。page 数据结构定义在 include/linux/mm_types.h …

使用 Lambda 函数将 CloudWatch Log 中的日志归档到 S3 桶中

作者:SRE运维博客 博客地址:https://www.cnsre.cn/ 文章地址:https://www.cnsre.cn/posts/221205544069/ 相关话题:https://www.cnsre.cn/tags/aws/ 躺了好久,诈尸了。因为换了工作,所以比较忙一直没有时间…

解决APP抓包问题「网络安全」

1.前言 在日常渗透过程中我们经常会遇到瓶颈无处下手,这个时候如果攻击者从APP进行突破,往往会有很多惊喜。但是目前市场上的APP都会为防止别人恶意盗取和恶意篡改进行一些保护措施,比如模拟器检测、root检测、APK加固、代码混淆、代码反调试…

挖出api接口的重要性

作为一名软件开发者,API是我们工作中不可或缺的一部分。无论是将不同系统连接起来,还是构建多组件应用程序,API都是我们的核心工具之一。在本文中,我们将深入讨论API的技术细节和实际应用。 一.首先,我们来看看什么是…

怎么把mkv格式改成mp4?不妨试试这几种方法吧!

怎么把mkv格式改成mp4?mp4是一种多媒体封装格式,不过我们通常会将它说成是视频格式,它可以在一个文件中容纳无限数量的视频、音频、图片或字幕轨道,mp4格式也是被我们每个人所熟知,因为我们每个人几乎每天都会接触或者…

Spring入门教程

目录 一、Spring最基本的使用 1.创建Maven项目(不需要模板) 2.添加Spring框架支持 3.添加启动类(没啥可说的符合规范即可) 4.创建bean对象 5.将bean对象注册到Spring中 (a)先在resources文件夹中创建一个xml文件(注意:是test文件用了.xml后缀 不是直接创建一个xml文件) (…

[ChatGPT] 从 GPT-3.5 到 GPT-5 的进化之路 | ChatGPT和程序员 : 协作 or 取代

⭐作者介绍:大二本科网络工程专业在读,持续学习Java,努力输出优质文章 ⭐作者主页:逐梦苍穹 ⭐如果觉得文章写的不错,欢迎点个关注一键三连😉有写的不好的地方也欢迎指正,一同进步😁…

FastDFS总结

目录 概述 什么是分布式文件系统 核心概念 目录结构 上传机制 下载机制 Linux中搭建FastDFS 常用指令 SpringBoot整合FastDFS FastDFS集成Nginx 概述 FastDFS是一个开源的轻量级分布式文件系统。它解决了大数据量存储和负载均衡等问题。特别适合以中小文件&#xff…

Android输入法不使用多客户端多屏适配-Android12

Android输入法不使用多客户端多屏适配-Android12 1、IME屏幕之间切换2、属性配置3、屏幕之间切换 IME 窗口 在非默认屏幕上运行的应用 1、IME屏幕之间切换 系统使用一个 IME,但可以在屏幕之间切换,以跟踪用户焦点。Android 10 默认所有第一方和第三方 IM…

【Android】试着写一个资讯界面(含不同板块)

跟着视频做的,并不能动脑子,于是自己顺一遍流程!(只阅读了部分教程,代码不完全相同)(仅为静态界面不含跳转)(在fragment上)此为视频链接 成果图: …

07-HTML-链接标签

<a> 标签定义超链接&#xff0c;用于从一张页面链接到另一张页面。<a> 元素最重要的属性是 href 属性&#xff0c;它指示链接的目标。 属性值描述downloadfilename规定被下载的超链接目标。hrefURL规定链接指向的页面的 URL。pinglist_of_URLs规定以空格分隔的 UR…

YOLOv8 人体姿态估计(关键点检测) python推理 ONNX RUNTIME C++部署

目录 1、下载权重 ​2、python 推理 3、转ONNX格式 4、ONNX RUNTIME C 部署 1、下载权重 我这里之前在做实例分割的时候&#xff0c;项目已经下载到本地&#xff0c;环境也安装好了&#xff0c;只需要下载pose的权重就可以 2、python 推理 yolo taskpose modepredict model…

ESP32设备驱动-PCA9685 LED控制器驱动

PCA9685 LED控制器驱动 文章目录 PCA9685 LED控制器驱动1、PCA9685介绍2、硬件准备3、软件准备4、驱动实现1、PCA9685介绍 PCA9685 是一款 IC 总线控制的 16 通道 LED 控制器,针对红色/绿色/蓝色/琥珀色 (RGBA) 彩色背光应用进行了优化。 每个 LED 输出都有自己的 12 位分辨率…

scala中match使用报错Scala.matchError:(of class java.lang.String)

1.遇到错误 Scala.matchError:(of class java.lang.String) 2.发现问题出在match使用中,如下写法就会报错 val partitionIndex key.toString match {case "chinese" > 0case "math" > 1case "english" > 2} 3.后来修改了写法&#xf…