分布式链路追踪 —— 基于Dubbo的traceId追踪传递

2023-12-21 09:45

本文主要是介绍分布式链路追踪 —— 基于Dubbo的traceId追踪传递,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • **原文链接,点击跳转**
  • RpcContext 上下文对象
  • Dubbo 过滤器(Filter)对象
  • 基于Dubbo的traceId追踪传递实现

原文链接,点击跳转

RpcContext 上下文对象

在实现 Dubbo 调用之间的链路跟踪之前,先简单了解 RpcContext 上下文对象和 Filter 过滤器对象,Dubbo 分布式链路追踪是基于这两个对象实现。

概要

  • RpcContext 是 Dubbo 框架提供的一个类,它的设计目标是提供一个 dubbo 调用的上下文对象,用于在远程过程调用(RPC)期间传递、共享请求和响应的相关信息。它可以用于存储、访问与当前 RPC 调用相关的数据,如调用方的 IP 地址、附加参数、上下文变量等。它提供了一些静态方法和属性,可以方便地获取和设置与当前线程相关的 RPC 上下文。

    public class RpcContext {private static final InternalThreadLocal<RpcContext> LOCAL = new InternalThreadLocal<RpcContext>() {protected RpcContext initialValue() {return new RpcContext();}};private static final InternalThreadLocal<RpcContext> SERVER_LOCAL = new InternalThreadLocal<RpcContext>() {protected RpcContext initialValue() {return new RpcContext();}};private final Map<String, String> attachments = new HashMap();private final Map<String, Object> values = new HashMap();// ......
    }
    

原理

  • RpcContext 是基于 ThreadLocal 实现的,做到了线程隔离。RpcContext是与线程绑定的,每个线程都有自己的一个 RpcContext 实例并使用 ThreadLocal 变量来存储,避免并发访问问题。当客户端发起一个 RPC 请求时,Dubbo 框架会创建一个新的线程来处理该请求,并且会将 RpcContext 与该线程进行绑定,这样看,对于每次 RPC 请求,RpcContext 也是唯一的。但对于同一个线程内的多个 RPC 请求,它们共享同一个 RpcContext 实例。

  • RpcContext 实例会在请求处理期间一直存在,并在请求处理完成后需要清理当前线程上的 RpcContext 实例中的数据,以确保下次使用该线程处理新的请求时,RpcContext 是一个干净的状态。

    对于服务消费方,Dubbo 框架在请求发送、响应后没有清除 RpcContext 实例中的数据;

    对于服务提供方,Dubbo 框架在收到请求并处理后,会去清除 RpcContext 实例中的数据,这个在 ContextFilter 服务提供方过滤器中可以看到。

使用场景

  • 可以在 dubbo 的拦截器、过滤器或服务提供者/消费者的代码中使用 RpcContext 来获取和设置上下文信息,以满足特定的业务需求,如日志跟踪、传递身份验证信息等。

下面是一些常用的 RpcContext 方法和属性:

  • RpcContext.getContext(): 获取当前线程的 RpcContext 实例。
  • RpcContext.isConsumerSide(): 判断当前线程是否处于消费者端。
  • RpcContext.isProviderSide(): 判断当前线程是否处于提供者端。
  • RpcContext.getRemoteAddress(): 获取远程调用的地址。
  • RpcContext.getLocalAddress(): 获取本地调用的地址。
  • RpcContext.setAttachment(String key, String value): 设置附加参数。
  • RpcContext.getAttachment(String key): 获取指定键的附件信息。
  • RpcContext.getAttachments(): 获取所有的附件信息。

Dubbo 过滤器(Filter)对象

Dubbo Filter 介绍

dubbo 的 Filter 是 dubbo 框架提供的一个功能扩展点,用于对服务提供者和消费者之间的请求和响应进行拦截过滤处理,比如认证和授权、日志跟踪、传递一些公共信息等。

如 dubbo 原生 Filter 实现类,如:ConsumerContextFilter 和 ContextFilter

  • ConsumerContextFilter 是一个服务消费方的过滤器,用于在服务消费者发起 RPC 调用之前或之后,对上下文信息进行处理和传递,用于收集和发送调用方的上下文信息到服务提供者端。

    ConsumerContextFilter 源码:通过这个过滤器可以看到在 RPC 调用之前会获取 RpcContext 对象并设置相关参数,Dubbo 框架会借助 RpcContext 对象将相关数据透传到服务提供方。

    @Activate(group = {"consumer"},order = -10000
    )
    public class ConsumerContextFilter implements Filter {public ConsumerContextFilter() {}public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {RpcContext.getContext().setInvoker(invoker).setInvocation(invocation).setLocalAddress(NetUtils.getLocalHost(), 0).setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()).setRemoteApplicationName(invoker.getUrl().getParameter("remote.application")).setAttachment("remote.application", invoker.getUrl().getParameter("application"));if (invocation instanceof RpcInvocation) {((RpcInvocation)invocation).setInvoker(invoker);}return invoker.invoke(invocation);}
    }
    

    @Activate 注解是 Dubbo 框架提供的一个扩展点激活注解,用于指定在特定条件下激活扩展点。

    在上述过滤器中,@Activate(group = Constants.CONSUMER, order = -10000) 是对一个扩展点的激活配置。具体解释如下:

    • group = {"consumer"}: 在指定的分组激活,如 consumer,表示该扩展点在消费者端被激活。
    • order = -10000: 指定激活的顺序为 -10000。在 Dubbo 框架中,扩展点的激活顺序可以通过 order 值来进行控制,值越小表示优先级越高。
  • ContextFilter 是一个服务提供方的过滤器,用于在服务提供者收到 RPC 调用请求之前或之后,对上下文信息进行处理和传递,如清除 RpcContext 实例中的数据,以确保下次使用该线程处理新的请求时,RpcContext 是一个干净的状态。

    ContextFilter 源码:在处理请求后去清除 RpcContext 中相关数据。

    @Activate(group = {"provider"},order = -10000
    )
    public class ContextFilter implements Filter, Filter.Listener {private static final String TAG_KEY = "dubbo.tag";public ContextFilter() {}public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {// ......Result var6;try {// 禁止清除RpcContext的功能。这意味着在调用invoker.invoke(invocation)之后,RpcContext中的上下文信息不能被清除。RpcContext.getContext().clearAfterEachInvoke(false);var6 = invoker.invoke(invocation);} finally {// 开启允许清除RpcContext的功能。RpcContext.getContext().clearAfterEachInvoke(true);// 显式清除RpcContext中的上下文信息。RpcContext.removeContext();RpcContext.removeServerContext();}return var6;}// ......
    }
    

自定义 Dubbo Filter

  1. 实现 org.apache.dubbo.rpc.Filter接口:

    import org.apache.dubbo.rpc.*;public class CustomFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {// 自定义过滤逻辑return invoker.invoke(invocation);}
    }
    
  2. 创建 Dubbo 的 SPI 扩展文件(META-INF/dubbo/org.apache.dubbo.rpc.Filter)中,将自定义过滤器的实现类指定为对应的扩展点:

    customFilter=com.example.CustomFilter
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 使过滤器生效:

    方式一:在自定义过滤器上使用注解:

    @Activate(group = {"consumer"})
    

    方式二:在配置文件(这里使用application.properties)中配置:

    dubbo.consumer.filter=customFilter
    

    通过以上步骤,在服务消费方指定了一个自定义过滤器,该过滤器将在服务消费者发起远程调用前后执行自定义的逻辑。

基于Dubbo的traceId追踪传递实现

要实现在 Dubbo 接口之间传递 TraceID,可以使用 Dubbo 的拦截器(Filter)机制来实现。下面是一个示例代码,演示了如何在 Dubbo 接口调用中传递 TraceID 进行追踪,其中具体 Filter 实现过程前面已讲述,这里只展示实现类代码。需要 demo 示例代码,请关注【Qin的学习营地】,回复【基于Dubbo的traceId追踪传递】。

这里使用 spring boot 整合 dubbo,详细搭建过程请参考:Dubbo 快速入门使用教程

这里通过打印日志来可视化结果,使用了 Slf4J 的 MDC,通过设置 MDC.put(key, value),并在日志配置文件中配置 key,日志打印时会将配置 key 的地方转换为 value 打印出来。

  1. 创建 Dubbo 的服务提供方拦截器类,从 RpcContext 中获取 traceid 参数,并设置到 MDC 中,请求处理完后清除 MDC 中的 traceid 参数:

    @Slf4j
    @Activate(group = {"provider"})
    public class TraceIdProviderFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = RpcContext.getContext().getAttachment("traceId");if (traceId != null) {MDC.put("traceId", traceId);}try {return invoker.invoke(invocation);} finally {MDC.remove("traceId");}}
    }
    
  2. 创建 Dubbo 的服务消费方拦截器类,向 RpcContext 中写入 traceid 参数:

    @Slf4j
    @Activate(group = {"consumer"})
    public class TraceIdConsumerFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = MDC.get("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");}RpcContext.getContext().setAttachment("traceId", traceId);MDC.put("traceId", traceId);log.info("consumer ——> provider");return invoker.invoke(invocation);}
    }
    
  3. 服务消费方调用逻辑:

    @Slf4j
    @Component
    public class ProducerService {@Reference(retries = -1, version="1.0.0", timeout = 15000)private HelloService helloService;public String consumerSayHello(String name){String traceId = UUID.randomUUID().toString().replace("-", "");MDC.put("traceId", traceId);String hello = helloService.sayHello(name);log.info("consumer receive response : "+ hello);return hello;}
    }
    
  4. 运行后看日志打印结果,可以看到服务提供方的 traceId 和服务消费方的 traceId 两者一致,服务消费方的 traceId 透传到服务提供方。

    消费方:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    提供方:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 分布式链路追踪
基于Dubbo的traceId追踪传递

本文首先介绍 Dubbo 的 RpcContext 上下文和 Filter 过滤器,然后再介绍基于Dubbo的traceId追踪传递的实现。

这篇关于分布式链路追踪 —— 基于Dubbo的traceId追踪传递的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/519492

相关文章

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

Seata之分布式事务问题及解决方案

《Seata之分布式事务问题及解决方案》:本文主要介绍Seata之分布式事务问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Seata–分布式事务解决方案简介同类产品对比环境搭建1.微服务2.SQL3.seata-server4.微服务配置事务模式1

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

异步线程traceId如何实现传递

《异步线程traceId如何实现传递》文章介绍了如何在异步请求中传递traceId,通过重写ThreadPoolTaskExecutor的方法和实现TaskDecorator接口来增强线程池,确保异步... 目录前言重写ThreadPoolTaskExecutor中方法线程池增强总结前言在日常问题排查中,

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp