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

2023-12-19 05:52

本文主要是介绍分布式链路追踪 —— 基于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/511107

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

Java中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例解析

《Java中的分布式系统开发基于Zookeeper与Dubbo的应用案例解析》本文将通过实际案例,带你走进基于Zookeeper与Dubbo的分布式系统开发,本文通过实例代码给大家介绍的非常详... 目录Java 中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例一、分布式系统中的挑战二

Redis实现分布式锁全过程

《Redis实现分布式锁全过程》文章介绍Redis实现分布式锁的方法,包括使用SETNX和EXPIRE命令确保互斥性与防死锁,Redisson客户端提供的便捷接口,以及Redlock算法通过多节点共识... 目录Redis实现分布式锁1. 分布式锁的基本原理2. 使用 Redis 实现分布式锁2.1 获取锁

Redis分布式锁中Redission底层实现方式

《Redis分布式锁中Redission底层实现方式》Redission基于Redis原子操作和Lua脚本实现分布式锁,通过SETNX命令、看门狗续期、可重入机制及异常处理,确保锁的可靠性和一致性,是... 目录Redis分布式锁中Redission底层实现一、Redission分布式锁的基本使用二、Red

redis和redission分布式锁原理及区别说明

《redis和redission分布式锁原理及区别说明》文章对比了synchronized、乐观锁、Redis分布式锁及Redission锁的原理与区别,指出在集群环境下synchronized失效,... 目录Redis和redission分布式锁原理及区别1、有的同伴想到了synchronized关键字

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

SpringBoot整合Dubbo+ZK注册失败的坑及解决

《SpringBoot整合Dubbo+ZK注册失败的坑及解决》使用Dubbo框架时,需在公共pom添加依赖,启动类加@EnableDubbo,实现类用@DubboService替代@Service,配... 目录1.先看下公共的pom(maven创建的pom工程)2.启动类上加@EnableDubbo3.实

Spring boot整合dubbo+zookeeper的详细过程

《Springboot整合dubbo+zookeeper的详细过程》本文讲解SpringBoot整合Dubbo与Zookeeper实现API、Provider、Consumer模式,包含依赖配置、... 目录Spring boot整合dubbo+zookeeper1.创建父工程2.父工程引入依赖3.创建ap

Jenkins分布式集群配置方式

《Jenkins分布式集群配置方式》:本文主要介绍Jenkins分布式集群配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装jenkins2.配置集群总结Jenkins是一个开源项目,它提供了一个容易使用的持续集成系统,并且提供了大量的plugin满