golang 使用 OpenTelemetry 实现跨服务 全链路追踪

news2025/1/22 12:49:25

使用 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
grpc-server
api2/bar
api3/bar
bar
bar2
bar3

说明:

  1. 用户 请求 api1(echo server) 服务的 api1/bar
  2. api1 调用 Grpc 服务
  3. api1 调用 api2 (gin server) 服务的 api2/bar
  4. api2 调用 api3 (echo server )服务的 api3/bar
  5. 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 为例

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 进行链路追踪 比如

ctx, span := tracing.Start(ctx, "service.bar")
defer span.End()

但如果是跨服务进行调用,比如 http server之间的调用,需要:

  1. 对于 http client: 请求server的时候,将ctx(上下文) 注入到 请求头中(req header) 中
  2. 对于 http server: 在获取http请求时,解析 出请求头 中的 parent trace 信息 这样就可以实现跨服务链路追踪

启动 http服务开启链路追踪

服务传输过程中,诸如tracex信息到请求头: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 启动时 通过解析 请求头 中的 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. 调用服务时,需要将上下文(ctx)传入到 CallAPI 方法

调用服务,查看链路关系

实战代码演示

跨服务 链路追踪 大概说完 下面是运行实战代码,分为普通运行和docker 一键运行

普通运行

  1. 示例文件:moda_tracing下 有四个目录,分别是 api1_http,api2_http,api3_http,grpc 分别对应三个api服务 一个grpc服务
  2. 分别启动三个服务,进入目录 go run ./ -c ./conf.toml 即可启动服务

docker 运行

  1. 进入moda_tracing目录
  2. 执行 make deploy,会同时启动 jaeger,api1,api2,api3,grpc(mac 和 linux经过试验可行,win如不行可使用第一种)

查看jaeger 链路

  1. 根据上面链路关系,调用api1 等待调用完成: curl localhost:8081/api1/bar
  2. 打开 jaeger 面板,查看链路关系图,http://localhost:16686/

在这里插入图片描述

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

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

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

相关文章

力扣总结,深度优先题

LCP 67. 装饰树 –链接– 中等 10 相关企业 力扣嘉年华上的 DIY 手工展位准备了一棵缩小版的 二叉 装饰树 root 和灯饰,你需要将灯饰逐一插入装饰树中,要求如下: 完成装饰的二叉树根结点与 root 的根结点值相同 若一个节点拥有父节点&#x…

用Java开发的建站神器JPress

什么是 JPress ? JPress 是一个使用 Java 开发的、类似 WordPress 的产品,支持多站点、多语种自动切换等。(JPress 始于2015 年) 目前已经有 10w 网站使用 JPress 进行驱动,其中包括多个政府机构,200 上市公…

HashMap 扰动函数、负载因子、扩容链表拆分

文章目录 1.扰动函数2.初始容量3.负载因子4.扩容链表拆分 1.扰动函数 在jdk8中,hashmap有这样一段代码,他叫扰动函数,目的是优化散列效果 static final int hash(Object key) {int h;return (key null) ? 0 : (h key.hashCode()) ^ (h &…

初识Java多线程编程

文章目录 一、线程的状态二、线程的常见属性三、多线程编程Thread类常用构造方法1.继承Thread类2.实现Runnable接口3.匿名内部类实现4.lambda 表达式创建 Runnable 子类对象 四、线程的常见方法 一、线程的状态 //线程的状态是一个枚举类型 Thread.State public class ThreadS…

【C语言】结构体——基础篇

结构体 为什么需要结构体?结构体类型的定义结构体变量结构体变量的定义结构体变量的引用结构体变量的初始化 结构体嵌套 为什么需要结构体? 💫首先,来举一个例子看一下为什么需要结构体。   在学籍管理系统中,要存储…

驱动开发:内核远程堆分配与销毁

在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了ZwAllocateVirtualMemory这个函数用于专门分配虚拟空间,而与之相对应…

MATLAB绘制动画(二)擦除动画

如果我们在绘制图形之后将原有的图形擦除,并重新绘制,看上去就像动画了 示例: t 0; m [sin(t);cos(t)]; p plot(t,m,EraseMode,background,MarkerSize,5); x -1.5*pi; axis([x x2*pi -1.5 1.5]); grid onfor i 1:100t [t 0.1*i];m [m [sin(0.1*i…

【LeetCode: 97. 交错字符串 | 暴力递归=>记忆化搜索=>动态规划 | 位置对应】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

校园安全,一键报警主机助力保障

校园安全,一键报警主机助力保障 随着社会发展和科技进步,校园安全问题日益受到重视。如何保障师生们的安全成为了学校一项重要任务。而校园可视一键报警主机就是一种非常有效的安保设备。 这种报警主机集合了视频监控、安全防范、数据处理等多个功能&a…

asm 加盘 udev 重启 导致网络异常

Network interface going down when dynamically adding disks to storage using udev in RHEL 6 (Doc ID 1569028.1)正在上传…重新上传取消To Bottom In this Document APPLIES TO: Oracle Database - Enterprise Edition - Version 11.2.0.3 and later Oracle Net Servi…

阿里国际、Lazada、eBay如何提高转化率?测评养号优化方式是什么

转化率是卖家在分析复盘时非常关键的因素,转化率的高低直接影响着卖家目前的关键词listing或者商品描述是否符合,消费者的满意度。 1.调查获客,明确分析市场需求 这是在产品上架之前必须明确的重要环节。如果市场上对于一个产品接受率低&am…

ChatGPT有中文版吗?

2023年最热的技术话题是什么?毫无疑问是人工智能,特别是AIGC领域。其中又以ChatGPT为最热门产品。ChatGPT是一个革命性的人工智能产品,能对我们的生产生活产生巨大的影响。然而,有网友担心ChatGPT是西方开发的AI工具,能…

若依管理系统RuoYi-Vue:登录和鉴权的实现

文章目录 摘要spring-boot-starter-security验证码生成是否开启验证码配置验证码类型CaptchaController的getCode来生成验证码 用户登录SysLoginController的login验证登录是否正确用户名密码是否正确Spring Security 的用户名密码验证机制在SecurityConfig中配置PasswordEncod…

聚观早报|恒大公告许家印成被执行人;特斯拉回应召回超百万辆车

今日要闻:恒大公告:许家印成被执行人;特斯拉回应召回超百万辆车;ChatGPT联网插件下周开放票;天翼物联发布首个3AZ亿级物联网平台;苹果MR头显功能预计远超竞争对手 恒大公告:许家印成被执行人 12…

【沐风老师】3DMAX一键种草插件GrassScatter使用方法详解

GrassScatter for 3dMax一键种草插件使用教程 3DMAX一键种草插件GrassScatter,用于控制草的创建和散布,快速生成草坪! 【版本要求】 3dMax2012及更高版本 【安装方法】 方法一:本插件无需安装,使用时直接拖动插件脚…

什么是独享数据库(Database per Microservice)?解决了什么问题?

独享数据库(Database per Microservice)是一种微服务架构模式,涉及为每个微服务创建单独的数据库。在这种模式下,每个微服务都有自己的数据库,这允许更大的可扩展性、灵活性和自治性。 使用这种模式,每个微…

考考驾照(安卓)

软件安装好后,不用注册登录直接就可以用,软件里主要包含有科目一,科目二,科目三,科目四等等学习的知识内容和一些技巧。 像科目一和科目四有考前须知,考试技巧,和学车流程,还有考试一…

JavaScript学习-DOM事件基础

DOM事件基础 事件监听(绑定)事件监听案例:关闭广告随机点名案例事件监听版本 事件类型类型鼠标经过与鼠标离开轮播图完整版焦点事件键盘事件与文本事件评论字数统计 事件对象获取事件对象事件对象常用属性案例:评论回车发布 环境对象回调函数综合案例&am…

MPLS格式和802.1q帧格式,ISL格式

一.MPLS IETF开发的多协议标记交换(MPLS)把第2层的链路状态信息(带宽、延迟、利用率等)集成到第3层的协议数据单元中,从而简化和改进了第3层分组的交换过程 。理论上,MPLS支持任何第2层和第3层协议。MPLS包头的位置界…

常见电子元器件

目录 常见电子元器件NTC(负温度系数热敏电阻)压敏电阻X2电容(抑制电源电磁干扰用电容器)泄放电阻共模电压共模电感整流桥滤波电容RCD吸收二极管Y电容整流器的原理输出整流肖特基二极管 功率晶体管(GTR,三极管)双极型晶体管(BJT,三极管)MOSFET…