当前位置: 首页 > news >正文

Dubbo分布式日志跟踪实现

前言

随着越来越多的应用逐渐微服务化后,分布式服务之间的RPC调用使得异常排查的难度骤增,最明显的一个问题,就是整个调用链路的日志不在一台机器上,往往定位问题就要花费大量时间。如何在一个分布式网络中把单次请求的整个调用日志给串起来,变得刻不容缓。

笔者基于 Dubbo 框架的 Filter 扩展点实现了一个分布式日志跟踪工具 dubbo-tracing,源码地址:https://github.com/panchanghe/dubbo-tracing

实现思路

Dubbo 作为国内最热门的 RPC 框架之一,对外提供了丰富的功能扩展点,日志跟踪就需要用到其中org.apache.dubbo.rpc.Filter扩展点。

Filter 扩展点可以在 Consumer 发起 RPC 调用前和 Provider 处理请求前发起拦截,执行我们特定的业务逻辑来对 Dubbo 做增强。另外,Dubbo RPC 调用除了方法入参,还额外提供了 Map 类型的 attachments 来隐式的传递参数。

有了这些前提,要实现分布式日志跟踪就简单了。通过实现 Filter 扩展点拦截 RPC 调用,最早的 Consumer 端生成一个唯一的 TraceId 进行透传,TraceId 在整个调用链路里保持一致,TraceId 会被写到日志上下文 MDC 中,最终和业务日志一起打印到日志文件里,这样通过 TraceId 检索就能获取整个调用链路的所有日志。一个完整的 RPC 调用链路是一个树状结构,最早发起调用的节点是根节点,一直向下延伸,为了把整个链路的日志构造成树状结构展示,我们还需要一个 SpanId,它代表了当前日志在整个调用链路中的层级。有了这些日志数据,再搭配日志检索服务 + 图形化展示,分布式问题的排查就会简单很多。

TraceId和SpanId生成规则

这里借鉴一下阿里的做法。

TraceId一般由接受请求的第一个服务器产生,具有唯一性,且在整个调用链路中保持不变。

TraceId的生成规则是:服务器IP + 时间戳 + 自增序列 + 进程号,比如:

c0a861711731309291125100068524

前8位c0a86171是生成TraceId的服务器IP,它被编码为十六进制,每2位代表IP地址中的一段,转换成十进制结果就是192.168.97.113,可以根据该号段快速定位到生成TraceId的服务器。

后面的13位1731309291125是生成TraceId的毫秒级时间戳;之后的4位1000是一个自增的序列,从 1000 开始,涨到 9999 后又会回到 1000;最后的部分68524是当前进程的ID,主要是为了防止单机多进程间产生的TraceId发生冲突。

SpanId 代表本次调用在整个调用链路树中的位置。

假设一个 Web 系统 A 接收了一次用户请求,那么在这个系统的 MVC 日志中,记录下的 SpanId 是 0,代表是整个调用的根节点,如果 A 系统处理这次请求,需要通过 RPC 依次调用 B、C、D 三个系统,那么在 A 系统的 RPC 客户端日志中,SpanId 分别是 0.1,0.2 和 0.3,在 B、C、D 三个系统的 RPC 服务端日志中,SpanId 也分别是 0.1,0.2 和 0.3;如果 C 系统在处理请求的时候又调用了 E,F 两个系统,那么 C 系统中对应的 RPC 客户端日志是 0.2.1 和 0.2.2,E、F 两个系统对应的 RPC 服务端日志也是 0.2.1 和 0.2.2。

根据上面的描述可以知道,如果把一次调用中所有的 SpanId 收集起来,可以组成一棵完整的链路树。

假设一次分布式调用中产生的 TraceId 是 0a1234(实际不会这么短),那么根据上文 SpanId 的产生过程,如下图所示:

具体实现

1、首先是实现一个根据 机器IP、时间戳、自增序列、进程ID 生成 TraceId 的方案:

public class IdUtils {private static final String PROCESS_ID;private static final String IP_HEX_CODE;private static final AtomicInteger COUNTER;private static final int COUNT_INIT_VALUE = 1000;private static final int COUNT_MAX_VALUE = 9999;private static long lastTimestamp = 0L;static {PROCESS_ID = ProcessIdUtil.getProcessId();IP_HEX_CODE = getIpHexCode();COUNTER = new AtomicInteger(COUNT_INIT_VALUE);}/*** 8位         13位            4位* 服务器 IP + ID 产生的时间 + 自增序列 + 当前进程号** @return*/public static synchronized String newTraceId() {final long timestamp = System.currentTimeMillis();long count;if (timestamp > lastTimestamp) {COUNTER.set(COUNT_INIT_VALUE);count = COUNT_INIT_VALUE;lastTimestamp = timestamp;} else {count = COUNTER.incrementAndGet();if (count == COUNT_MAX_VALUE) {COUNTER.set(COUNT_INIT_VALUE - 1);}}return IP_HEX_CODE + timestamp + count + PROCESS_ID;}private static String getIpHexCode() {final StringBuilder builder = new StringBuilder();String host = NetUtils.getLocalHost();String[] split = host.split("\\.");for (String s : split) {String hex = Integer.toHexString(Integer.valueOf(s));if (hex.length() == 1) {hex = "0" + hex;}builder.append(hex);}return builder.toString();}
}

2、为了方便本地透传 TraceId 等信息,必然要用到 ThreadLocal 来记录,所以我们创建一个 TraceContext 类来读写当前线程的 Trace 信息。

public class TraceContext {private static final ThreadLocal<Map<String, Object>> TRACE_THREAD_LOCAL = new ThreadLocal() {@Overrideprotected Object initialValue() {return new HashMap<>();}};public static boolean isStarted() {return !get().isEmpty();}public static void start(String traceId) {start(traceId, "0");}public static void start(String traceId, String spanId) {get().put(TracingConstant.TRACE_ID, traceId);get().put(TracingConstant.SPAN_ID, spanId);get().put(TracingConstant.LOGIC_ID, new AtomicInteger(0));}public static String getTraceId() {return (String) get().get(TracingConstant.TRACE_ID);}public static String getSpanId() {String s = (String) get().get(TracingConstant.SPAN_ID);return s;}public static int nextLogicId() {return ((AtomicInteger) get().get(TracingConstant.LOGIC_ID)).incrementAndGet();}private static Map<String, Object> get() {return TRACE_THREAD_LOCAL.get();}public static void clear() {TRACE_THREAD_LOCAL.remove();}
}

3、Consumer 端的 Filter 扩展,判断当前线程是否已经生成 TraceId,如果没有则生成新的 TraceId 和 SpanId 写入到 ThreadLocal 同时通过 attachments 透传到 Provider。

@Activate(group = {"consumer"})
public class ConsumerTraceFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {if (!TraceContext.isStarted()) {TraceContext.start(getTraceId());}ThreadContext.put(TracingConstant.TRACE_ID, TraceContext.getTraceId());ThreadContext.put(TracingConstant.SPAN_ID, TraceContext.getSpanId());invocation.setAttachment(TracingConstant.DUBBO_TRACE_ID, TraceContext.getTraceId());invocation.setAttachment(TracingConstant.DUBBO_SPAN_ID, TraceContext.getSpanId() + "." + TraceContext.nextLogicId());return invoker.invoke(invocation);}private String getTraceId() {String traceId = ThreadContext.get(TracingConstant.TRACE_ID);if (StringUtils.isEmpty(traceId)) {traceId = IdUtils.newTraceId();}return traceId;}
}

4、Provider 端的 Filter 扩展,读取 attachments 透传过来的 TraceId 和 SpanId,如果能读到,就将它们写入本地 ThreadLocal 里,开启 TraceContext,后续如果自己再发起下游的 RPC 调用,则会以它们为基础数据,发给下游节点,整个链路就能串起来了。

@Activate(group = {"provider"})
public class ProviderTraceFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {final String traceId = RpcContext.getServerAttachment().getAttachment(TracingConstant.DUBBO_TRACE_ID);final String spanId = RpcContext.getServerAttachment().getAttachment(TracingConstant.DUBBO_SPAN_ID);if (StringUtils.isAnyEmpty(traceId, spanId)) {return invoker.invoke(invocation);}TraceContext.start(traceId, spanId);ThreadContext.put(TracingConstant.TRACE_ID, TraceContext.getTraceId());ThreadContext.put(TracingConstant.SPAN_ID, TraceContext.getSpanId());try {return invoker.invoke(invocation);} catch (Throwable e) {throw e;} finally {TraceContext.clear();ThreadContext.remove(TracingConstant.TRACE_ID);ThreadContext.remove(TracingConstant.SPAN_ID);}}
}

5、为了让我们自定义的 Filter 能被 Dubbo 加载并执行,还需要在 META-INF/dubbo/org.apache.dubbo.rpc.Filter文件里配置一下:

ProviderTraceFilter=top.javap.dubbo.tracing.ProviderTraceFilter
ConsumerTraceFilter=top.javap.dubbo.tracing.ConsumerTraceFilter

相关文章:

Dubbo分布式日志跟踪实现

前言 随着越来越多的应用逐渐微服务化后&#xff0c;分布式服务之间的RPC调用使得异常排查的难度骤增&#xff0c;最明显的一个问题&#xff0c;就是整个调用链路的日志不在一台机器上&#xff0c;往往定位问题就要花费大量时间。如何在一个分布式网络中把单次请求的整个调用日…...

EPSON机械手与第三方相机的校准功能设计By python

EPSON机械手与第三方相机的校准功能设计By python 使用Python来实现EPSON机械手与第三方相机的校准功能是一个复杂但可行的任务。这通常涉及以下几个步骤:硬件接口通信、图像处理、标定算法实现和控制逻辑编写。 1. 环境准备 首先,库 pip install numpy opencv-python pyse…...

探索 Java 23:新时代的编程利器

一、引言 随着技术的不断发展&#xff0c;Java 作为一种广泛应用的编程语言也在不断演进。Java 23 的推出带来了许多令人兴奋的新特性和改进&#xff0c;为开发者提供了更多的工具和功能&#xff0c;以应对日益复杂的软件开发挑战。本文将深入介绍 Java 23 的各个方面。 二、J…...

CSS3_3D变换(七)

1、CSS3_3D变换 1.1 3D空间与景深 3D空间&#xff1a;在父元素中将属性transform-style设置为preserve-3d开启3D空间&#xff0c;默认值为flat&#xff08;开启2D空间&#xff09;&#xff1b; 景深&#xff1a;人眼与平面的距离&#xff0c;产生透视效果&#xff0c;使得效果…...

Mesh网格

Mesh(网格) 定义&#xff1a;Mesh 是一个包含顶点、三角形、顶点法线、UV坐标、颜色和骨骼权重等数据的对象。它定义了3D模型的几何形状。 功能&#xff1a; 顶点&#xff08;Vertices&#xff09;&#xff1a;构成3D模型的点。 三角形&#xff08;Triangles&#xff09;&…...

LeetCode 509.斐波那契数

动态规划思想 五步骤&#xff1a; 1.确定dp[i]含义 2.递推公式 3.初始化 4.遍历顺序 5.打印dp数组 利用状态压缩&#xff0c;简化空间复杂度。在原代码中&#xff0c;dp 数组保存了所有状态&#xff0c;但实际上斐波那契数列的计算只需要前两个状态。因此&#xff0c;我们…...

SQL Server 数据太多如何优化

大家好&#xff0c;我是 V 哥。讲了很多数据库&#xff0c;有小伙伴说&#xff0c;SQL Server 也讲一讲啊&#xff0c;好吧&#xff0c;V 哥做个听话的门童&#xff0c;今天要聊一聊 SQL Server。 在 SQL Server 中&#xff0c;当数据量增大时&#xff0c;数据库的性能可能会受…...

关于word 页眉页脚的一些小问题

去掉页眉底纹&#xff1a; 对文档的段落边框和底纹进行设置&#xff0c;也是页眉横线怎么删除的一种解决方式&#xff0c;具体操作如下&#xff1a; 选中页眉中的横线文本&#xff1b; 点击【开始】选项卡&#xff0c;在【段落】组中点击【边框】按钮的下拉箭头&#xff1b; …...

【高等数学学习记录】连续函数的运算与初等函数的连续性

一、知识点 &#xff08;一&#xff09;连续函数的和、差、积、商的连续性 定理1 设函数 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 在点 x 0 x_0 x0​ 连续&#xff0c;则它们的和&#xff08;差&#xff09; f g f\pm g fg、积 f ⋅ g f\cdot g f⋅g 及商 f g \frac{f…...

【抖音直播间弹幕】protobuf协议分析

将Uint8Array变成 PushFrame格式&#xff0c;里面的payload就存放着弹幕消息 点进去就可以看到其定义的proto结构 headers是一个自定义类型 将测试数据保存一下&#xff0c;等下做对比 先将PushFrame的 payload 内容进行gzip解压 然后再解析为响应 可以看到里面有对应的消…...

Swift 开发教程系列 - 第11章:内存管理和 ARC(Automatic Reference Counting)

在 Swift 中&#xff0c;内存管理由 ARC&#xff08;自动引用计数&#xff09;机制自动处理。ARC 通过追踪和管理对象的引用计数来确保分配的内存得到有效释放。尽管 ARC 在大多数情况下能够高效地管理内存&#xff0c;但理解其工作原理仍然十分重要&#xff0c;因为不当的引用…...

C#中 layout的用法

在C#中&#xff0c;layout并不是一个直接用于C#语言本身的关键字或特性。然而&#xff0c;layout在与C#紧密相关的某些上下文中确实有其用途&#xff0c;特别是在涉及用户界面&#xff08;UI&#xff09;设计和数据展示时。以下是几个常见的与layout相关的用法场景&#xff1a;…...

【编程概念基础知识】

、编程基础 一、面向对象的三大特性 1、封装&#xff1a; 盒子、零件、按钮 隐藏对象 的内部状态&#xff0c;并且只通过对象的方法来访问数据 想象你有一个小盒子&#xff08;这个盒子就是一个类&#xff09;&#xff0c;里面装着一些零件&#xff08;这些零件就是数据&a…...

【React】深入理解 JSX语法

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 深入理解 JSX语法1. JSX 简介2. JSX 的基本语法2.1 基本结构2.2 与普通 JavaScr…...

【Linux】从零开始使用多路转接IO --- 理解EPOLL的 LT水平触发模式 与 ET边缘触发模式

当你偶尔发现语言变得无力时&#xff0c; 不妨安静下来&#xff0c; 让沉默替你发声。 --- 里则林 --- 从零开始认识多路转接 1 EPOLL优缺点2 EPOLL工作模式 1 EPOLL优缺点 poll 的优点(和 select 的缺点对应) 接口使用方便&#xff1a;虽然拆分成了三个函数&#xff0c;…...

QtLua

描述 QtLua 库旨在使用 Lua 脚本语言使 Qt4/Qt5 应用程序可编写脚本。它是 QtScript 模块的替代品。 QtLua 不会为 Qt 生成或使用生成的绑定代码。相反&#xff0c;它提供了有用的 C 包装器类&#xff0c;使 C 和 lua 对象都可以从 lua 和 C 访问。它利用 Qt 元对象系统将 QOb…...

c++-有关计数、双变量累加、半衰、阶乘、变量值互换的基础知识

C是一种非常强大和灵活的编程语言&#xff0c;它包含了许多重要的概念和技巧。在本文中&#xff0c;我们将重点讨论五个主题&#xff1a;计数、双变量累加、半衰、阶乘和变量值的互换。我们将介绍这些概念的定义、用法、题目、答案和解释&#xff0c;以帮助读者更好地理解和运用…...

MyBatis3-获取参数值的方式、查询功能及特殊SQL执行

目录 准备工作 获取参数值的方式&#xff08;重点&#xff09; 查询功能 查询一个实体类对象 查询一个list集合 查询单个数据 查询一条数据为map集合 查询多条数据为map集合 特殊SQL执行 模糊查询 批量删除 动态设置表名 添加功能获取自增的主键 准备工作 模块My…...

web——[SUCTF 2019]EasySQL1——堆叠注入

这个题主要是讲述了堆叠注入的用法&#xff0c;来复现一下 什么是堆叠注入 堆叠注入&#xff1a;将多条SQL语句放在一起&#xff0c;并用分号;隔开。 1.查看数据库的名称 查看数据库名称 1;show databases; 发现有名称为ctftraining的数据库 2.对表进行查询 1;show tabl…...

【Ubuntu学习】Ubuntu无法使用vim命令编辑

问题 在VMware首次安装Ubuntu&#xff0c;使用vi指令对文件进行编辑&#xff0c;按i键后无法更改文件内容。 原因 由于Ubuntu中预装的是vim-tiny&#xff0c;平时开发中需要使用vim-full。 解决方案 卸载预装vim sudo apt-get remove vim-common安装vim-full sudo apt-get …...

UniAPP u-popup 禁止背景滑动

增加class .NoScroll {overflow: hidden;position: fixed; }在外层div上增加该class判断条件...

F5全新报告揭示AI时代API安全面临严峻挑战

F5 《2024年应用策略现状报告:API安全》揭示了 API 保护中的漏洞以及对全面安全措施的迫切需求 西雅图,2024年11月11日 – F5(NASDAQ: FFIV)日前发布《2024年应用策略现状报告:API 安全》(以下简称为“报告”),揭示了跨行业API安全面临的严峻现状。该报告强调了企业API保护方面…...

使用C语言进行信号处理:从理论到实践的全面指南

1. 引言 在现代操作系统中&#xff0c;信号是一种进程间通信机制&#xff0c;它允许操作系统或其他进程向一个进程发送消息。信号可以用来通知进程发生了一些重要事件&#xff0c;如用户请求终止进程、硬件异常、定时器超时等。掌握信号处理技术对于开发健壮、高效的系统程序至…...

什么是工单管理系统?全面认识指南

在现代企业中&#xff0c;客户服务和支持是业务成功的关键因素之一。为了有效地管理客户请求和问题&#xff0c;许多公司采用了工单管理系统。本文将深入探讨工单管理系统的定义、功能、优势。 一、工单管理系统的定义 工单管理系统是一种软件工具&#xff0c;旨在帮助企业管…...

集群化消息服务解决方案

目录 集群化消息服务解决方案项目概述架构图使用说明服务端通过API接口推送消息给客户端调用方式 请求参数返回参数 客户端推送消息连接websocket或发送消息 接收消息项目地址作者信息 集群化消息服务解决方案 项目概述 集群化消息服务解决方案是一种用于处理大量消息的高可用…...

python数据结构操作与可视化的应用

Python具有丰富的数据结构操作和可视化库&#xff0c;可以进行各种数据结构的创建、编辑和分析&#xff0c;并将结果可视化。以下是几个常见的Python数据结构操作和可视化的应用示例&#xff1a; 1. 列表&#xff08;List&#xff09;操作和可视化&#xff1a; - 创建列表&a…...

【基于轻量型架构的WEB开发】课程 作业4 AOP

一. 单选题&#xff08;共7题&#xff0c;38.5分&#xff09; 1 (单选题)下列选项中&#xff0c;用于通知/增强处理的是&#xff08; &#xff09;。 A. Joinpoint B. Pointcut C. Aspect D. Advice 正确答案&#xff1a;D 答案解析&#xff1a;在面向切面编程&#xff…...

跨境独立站新手,如何用DuoPlus云手机破局海外社媒引流?

独立站作为电商领域的一个重要组成部分&#xff0c;其发展在最近几年里确实令人瞩目&#xff0c;对于想要进入跨境赛道的新手卖家来说&#xff0c;手上握着有优势的货源&#xff0c;建立小型的DTC独立站确实会比入驻第三方平台具有更大的灵活性。本文将给跨境卖家们总结独立站和…...

【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏

Android 标题栏 参考 Android Studio版本 配置gradle镜像 阿里云 Android使用 android:theme 显示标题栏 添加依赖 dependencies {implementation("androidx.appcompat:appcompat:1.6.1")implementation("com.google.android.material:material:1.9.0")…...

信息安全工程师(83)Windows操作系统安全分析与防护

一、Windows操作系统安全分析 系统漏洞&#xff1a; Windows操作系统由于其复杂性和广泛使用&#xff0c;可能存在一些已知或未知的漏洞。这些漏洞可能会被黑客利用&#xff0c;进行恶意攻击。微软会定期发布系统更新和补丁&#xff0c;以修复这些漏洞&#xff0c;提高系统的安…...

网站做系统下载/黄山seo公司

实验环境&#xff1a;博科光纤交换机&#xff1a;Silkworm3250 &#xff08;默认设置&#xff09;FTP服务器&#xff1a;win7_64位客户端&#xff1a;Hyper terminal注意&#xff1a;请先自行百度资料设置win7_64为FTP服务器&#xff0c;站点设置目录为&#xff1a;c:/upload1.…...

wordpress画廊怎么用/平台推广是什么意思

下载jadClipse地址&#xff1a; 链接: http://pan.baidu.com/s/1kTN4TPd 提取码: 3fvd 将net.sf.jadclipse_3.3.0.jar拷贝到eclipse的plugins目录下&#xff1b; 删除eclipse的configuration目录下org.eclipse.update &#xff08;记录Eclipse feature ID 、相对路径、版本号…...

营销网站做推广公司/搜狗推广管家

安装 一. Apache 安装 yum install -y httpd启动 /etc/init.d/httpd start备注&#xff1a;Apache启动之后会提示错误&#xff1a; 正在启动httpd:httpd: Could not reliably determine the server’s fully qualif domain name, using ::1 for ServerName解决办法&#xff1a;…...

广州番禺服装网站建设/电商的运营模式有几种

今天在看hibernate视频时&#xff0c;看到视频上人家的hibernate.cfg.xml配置文件在配置hibernate方言时&#xff0c;发现视频上是这样写的<property name"dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>&#xff0c;结果我也是那样写&a…...

免费代理服务器网站/网上推广的平台有哪些

1.users的数据表设计&#xff1a; 1.重建用户表&#xff1a; 1.在settings中配置&#xff1a; AUTH_USER_MODELusers.UserProfile 2.apps/users/models.py中建立UserProfile表&#xff1a; class UserProfile(AbstractUser):"""用户表"""nickNam…...

资源型网站建设 需要多大硬盘/企业获客方式

valign是html,只能在支持的标签内&#xff0c;如tdvertical-align是css 属性&#xff0c;支持的选择器更多&#xff0c;很多block属性的都可以用。...