XxlJob添加Sleuth链路追踪

解决cloud-sleuth在xxl-job中无法传递traceId的问题 本文章主要介绍sleuth与xxl-job的传递traceId问题,以及提出临时解决办法 阅读详情
XxlJob是是一个轻量级分布式任务调度平台。对于此可以通过官网进行学习使用。

我们在拥有Sleuth的环境下使用xxjob的时候,就会发现一个问题就是通过xxjob中调用的代码中并不存在链路,当我在XxlJob中出现异常时,需要跨系统查询日志等操作后,得到结论,该操作耗时耗力。所以我们不如向feign,rpc调用一样在xxjob中添加链路。

为了不变更xxjob的源码结构,这里我通过spring aop的方式对@XxlJob注解进行切面编程,而注解@Xxljob就是一个切点。代码如下所示:

将Tracing注入到Aspect切面类中

@EnableAspectJAutoProxy
@Configuration
public class AOPConfiguration {


    @Bean
    public TraceXXJobAspect traceXXJobAspect(Tracing tracing){
        return new TraceXXJobAspect(tracing);
    }

}

TraceXXJobAspect :


@Aspect
@Slf4j
public class TraceXXJobAspect {

    private Tracing tracing;

    public TraceXXJobAspect(Tracing tracing) {
        this.tracing = tracing;
    }

    private static final String CLASS_KEY = "class";

    private static final String METHOD_KEY = "method";


    @Around("execution (@com.xxl.job.core.handler.annotation.XxlJob  * *.*(..))")
    public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {

        Tracer tracer = this.tracing.tracer();
        //清除scop中原span
        tracer.withSpanInScope(null);
        String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName());
        Span span = tracer.newTrace().name(spanName);
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span.start())) {
            span.tag(CLASS_KEY, pjp.getTarget().getClass().getSimpleName());
            span.tag(METHOD_KEY, pjp.getSignature().getName());
            return pjp.proceed();
        }catch (Throwable ex) {
            String message = ex.getMessage() == null ? ex.getClass().getSimpleName()
                    : ex.getMessage();
            span.tag("error", message);
          
            throw ex;
        }
        finally {
            span.finish();
        }
    }

}

这里解释下XxlJob初始化的过程,我们在使用xxljob的时候需要自己创建XxlJobSpringExecutor类实例对象,并注入到sping中(xxljob依赖于spring),查看下XxlJobSpringExecutor源码:

public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);


    // start
    @Override
    public void afterSingletonsInstantiated() {

        // init JobHandler Repository  在2.2之后@JobHandler方式被xxljob给取消了
        /*initJobHandlerRepository(applicationContext);*/

        // 扫描xxljob注解并为每一个被xxljob生成 IJobHandler对象
        initJobHandlerMethodRepository(applicationContext);

        // refresh GlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // destroy
    @Override
    public void destroy() {
        super.destroy();
    }


    /*private void initJobHandlerRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }

        // init job handler action
        Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);

        if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
            for (Object serviceBean : serviceBeanMap.values()) {
                if (serviceBean instanceof IJobHandler) {
                    String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
                    IJobHandler handler = (IJobHandler) serviceBean;
                    if (loadJobHandler(name) != null) {
                        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                    }
                    registJobHandler(name, handler);
                }
            }
        }
    }*/

    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler from method
        //这里是获取springioc容器的 bean对象
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        for (String beanDefinitionName : beanDefinitionNames) {
            //获取bean
            Object bean = applicationContext.getBean(beanDefinitionName);

            Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                //从Bean中判断是否有方法使用了@Xxljob注解
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                            }
                        });
            } catch (Throwable ex) {
                logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
            }
            //没有获取到开始下个循环
            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
                continue;
            }

            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                //获取xxljob对应的method对象
                Method method = methodXxlJobEntry.getKey();
                //获取xxljob注解对象
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                if (xxlJob == null) {
                    continue;
                }
                //对获取name
                String name = xxlJob.value();
                if (name.trim().length() == 0) {
                    throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
                }
                //验证name的唯一性 
                if (loadJobHandler(name) != null) {
                    throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                }

                // execute method
                //日志记录
                if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
                    throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                            "The correct method format like \" public ReturnT<String> execute(String param) \" .");
                }
                if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
                    throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                            "The correct method format like \" public ReturnT<String> execute(String param) \" .");
                }
                method.setAccessible(true);

                // init and destory
                Method initMethod = null;
                Method destroyMethod = null;
                //根据init属性判断是否存在对应init方法
                if (xxlJob.init().trim().length() > 0) {
                    try {
                        initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
                        initMethod.setAccessible(true);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
                    }
                }
                //根据init属性判断是否存在对应destroy方法

                if (xxlJob.destroy().trim().length() > 0) {
                    try {
                        destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
                        destroyMethod.setAccessible(true);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
                    }
                }

                // registry jobhandler
                registJobHandler(name, new MethodJobHandler(bean, method, initMethod, destroyMethod));
            }
        }

    }

    // ---------------------- applicationContext ----------------------
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}

 注册jobhandler,实际上写入到本地缓存中jobHandlerRepository,如下所示:

  // ---------------------- job handler repository ----------------------
    private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
    public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
        logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
        return jobHandlerRepository.put(name, jobHandler);
    }

xxljob的调用时基于Netty的,客户端在初始化的时候会本地构建一个Netty服务端(详细可参考NettyHttpServer),并与admin保持一个长连接,在Admin来被定时调度的触发后通过Netty发送调度的信息给客户端(详细参考NettyServerHandler与XxlRpcProviderFactory),客户端收到信息后,拿着信息去jobHandlerRepository中找到对应的IjobHandler对象,执行对应的方法

【全链路追踪】XXL-JOB添加TraceID 其实就是使用ThreadLocal来存储,而由于请求到Java后端服务时,Tomcat会分配一个线程,直至请求结束,这样就会保证我们在入口添加的TraceID,会传递到整条链路。走统一网关入口的请求不用担心,在网关那边加了TraceID,但是XXL-JOB由于是自动注册,且部署环境是在K8S内,XXL-JOB获取到的是Pod的IP,网关并未拦截到。由于项目的逻辑较为复杂,XXL-JOB的调度任务属于其中比较重要的一块,对于前期开发的调试以及后期问题的确认,加上TraceID是非常有必要的。 阅读详情

相关推荐

【链路追踪】Spring AOP赋能xxl-job:实现定时任务日志traceId无缝集成

本文详细介绍了如何利用Spring AOP为xxl-job定时任务注入traceId,实现分布式系统中的链路追踪。通过无侵入式的切面编程,解决定时任务日志难以追踪的问题,提升排查效率。文章包含完整实现代码、配置建议和优化方案,帮助开发者快速集成traceId功能到xxl-job任务中。

weixin_42556197的博客 174

Sleuth 搭建链路监控步骤

https://blog.youkuaiyun.com/qq_41211642/article/details/104926094

yanlipeng_job的博客 250

浅谈Spring-cloud 之 sleuth 服务链路跟踪

本篇文章主要介绍了浅谈Spring-cloud 之 sleuth 服务链路跟踪,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

用拦截器解决xxljob适配sleuth

项目中使用了xxljob做任务调度中心,使用过程中发现一个问题,xxljob调度的任务代码日志没有traceId和spanId等信息,为了放方便问题跟踪,所以要做适配。发现一个问题,traceId和spanId虽然都还在,但都是一个新值,老的traceId无法保留,此问题尚未解决。所以按照这个思路我写了个拦截器来为使用@XxlJob注解的方法手动添加traceId和spanId。测试了下,traceId和spanId都成功添加,然后我又测试了异步方法。网上调研,sleuth的日志增强类。

wobenqingfeng的专栏 1260

深入剖析:功率放大电路设计精髓——从理论到实践

输入信号首先被调制(通常是PWM脉宽调制)成高频开关信号,驱动功率开关管(MOSFET),再通过LC低通滤波器滤除高频分量,还原出放大的音频信号。在靠近功放板电源引脚处放置足够容量的电解电容(如100uF - 1000uF)和高频瓷片电容(如0.1uF)并联进行电源去耦,抑制低频和高频干扰,防止低频振荡(Motorboating)和高频自激。,堪称电子世界里的“能量转换器”。当输入信号在零附近(即两个器件切换导通的区域)时,由于器件的开启电压(Vbe或Vgs)不为零,输出波形会出现明显的衔接不流畅的失真。

Zihan2558的博客 1697

KG²RAG框架:知识图谱增强大模型检索生成技术解析

知识图谱(Knowledge Graph)作为结构化知识表示的重要方式,与向量检索技术结合形成了新一代检索增强生成(RAG)框架。其核心原理是通过图神经网络编码器提取子图特征,并与文本向量空间对齐,实现结构化与非结构化知识的协同检索。这种技术显著提升了专业领域问答的事实准确性,在医疗诊断、金融分析等需要高可靠性响应的场景中具有重要价值。KG²RAG框架创新性地采用双通道处理架构,结合Neo4j等图数据库与Milvus向量数据库,通过动态检索调度器智能选择最优检索路径。实测表明该方案能降低40%以上的事实错误

weixin_29306875的博客 105

解决接入sleuth链路追踪后xxl-job定时任务的日志无日志问题

随着业务规模的不断的增大, 系统的复杂度也越来越高, 公司软件架构也进入到了分布式微服务的阶段, 在这样的情况下每一次请求都有可能跨越多个项目, 传统的日志监控方式无法满足调用链路追踪, 这就导致问题定位/诊断服务变得复杂。但是使用中发现xxl-job的日志是没有traceId的,只能根据线程id来筛选日志,且跨服务后的日志查不到了。通过增加以上类,即可实现traceId注入,同时也增加了统一日志打印,不需要在每个定时任务入口都打印日志(如果不需要也可以去掉)。

fhey的博客 601

汇川机器人与PLC通信-ModbusTCP超详细案例

通过汇川机器人与PLC的ModbusTCP通信项目案例,我们可以了解通讯配置的过程,地址映射的方式,对接信号的转换,运行时序的逻辑等注意事项,帮助我们快速应用到项目中。

weixin_44166380的博客 1万+

SpringCloud Sleuth分布式请求链路跟踪

微服务的链路问题 在微服务框架中,一个由客户端发起的请求在后端系统中会经历多个不同的服务节点调用来协同产生最后的请求结果。因此,每一个请求都会产生一条复杂的分布式调用链路,链路中的任何一个环节出现高延迟或者错误都会引起整个请求的失败。而SpringCloud Sleuth提供了一整套的服务跟踪解决方案。 搭建链路监控的步骤 下载和启动zipkin 本文以zipkin 2.12.9版本为例,可以根据自己的需求进行版本选择。 zipkin下载地址 http://dl.bintray.com/openzipki

chongbaozhong的博客 423

SD--根据订单创建出库单(相关的函数列表的介绍系列篇(2))

创建订单样例代码参见:http://blog.youkuaiyun.com/CompassButton/archive/2007/05/09/1601712.aspx 可以根据一个订单或者部分订单项来创建出库单,代码如下:* Populating the item data lx_sales_item-ref_doc = l_vbeln. lx_sales_item-ref_item = c_000010

SAP BLOG--Jack Wu 6226

网口通信实战(C#完整版):基于TCP协议的工业仪表数据采集系统开发

工业现场中80%的智能仪表(压力/温度/液位/流量/数显仪表)、采集模块、测控终端均支持TCP网口通信,这类设备的TCP通信有鲜明的工业特性:基于字节流的自定义二进制协议、一问一答的主从通信模式、对数据完整性/稳定性要求极高。本次实战是纯工业级落地项目,基于C# WinForm + .NET Framework 4.8(工业上位机标配)✅纯原生无依赖:零第三方类库/控件,仅用C#原生,源码可100%复用、无引用冲突、工控机完美兼容;✅双采集模式完美适配:同时实现「单次手动采集」+「连续自动采集」

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1802

日志追踪的MDC与TraceID技术学习笔记

本文介绍了MDC(MappedDiagnosticContext)和TraceId在分布式系统日志追踪中的应用。MDC是线程级日志上下文存储机制,通过ThreadLocal保存请求上下文信息,帮助串联分布式系统中的请求日志。TraceId是唯一标识请求链路的ID,可采用UUID或Snowflake算法生成。文章详细说明Logback、Log4j等框架对MDC的实现方式,并通过SpringBoot项目演示单线程和多线程环境下MDC的使用方法。此外,还介绍了如何通过拦截器实现Web请求ID传递、线程池MDC使用

DayDayUp的博客 247

【论文复现】水下单光子激光雷达设计 Sequential Multimodal Underwater Single

针对对慢速小水下目标进行远距离、高分辨率重建的需求,单光子激光雷达(LiDAR)目标重建技术的研究正在进行。本文报告了一种基于时空序列融合的顺序多模态水下单光子 LiDAR 自适应目标重建算法,该算法具有强大的信息提取和噪声过滤能力,能够从复杂的回波光子计数和空间像素关系中重建目标的深度和反射强度信息。该方法包括三个步骤:数据预处理、序列优化极值推断滤波以及图像优化协同变分策略,以实现复杂水下环境下的高质量目标重建。

u012133341的博客 293

XXL-JOB的基本使用

子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。下边要重点说的是分片广播策略,分片是指是调度中心将集群中的执行器标上序号:0,1,2,3...,广播是指每次调度会向集群中所有执行器发送调度请求,请求中携带分片参数。覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;如下图,我们会启动多个执行器组成一个集群,去执行任务。

Relievedz的博客 5488

【DexGraspNet与多指手抓取算法详解】第三章 DexGraspNet数据集构建机理

抓取姿态描述多指手与物体的相对空间关系,包含手掌位姿与手指关节配置。

VectorShift的博客 498

Win10 系统下VisualStudio2022 配置点云库 PCL1.13.0

Win10 系统下VisualStudio2022 配置点云库 PCL1.13.0与Win10 系统下 VisualStudio2019 配置点云库 PCL1.11.1流程基本相同,主要区别有两点: * 从PCL1.12.0版本开始需要进行`启用增强指令集`的设置。 * 附加依赖项添加的内容不同。

点云侠的博客 7006

xxl-job添加日志traceId

在使用xxl-job框架作为项目中的定时任务管理时,JobHandler线程记录日志时没有traceId,导致查询日志时不方便。其他框架如SpringMVC、Dubbo等都提供了拦截器,可以通过拦截器手动添加traceId,或者直接使用brave.zipkin自动添加traceId,网上有很多资料可以参考,这里仅介绍一下xxl-job集成spring时添加traceId的一种方法。 方法比较简单,...

zoro777的博客 5579

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

微服务开发系列 第十一篇:XXL-JOB

本节实现目标 搭建xxl-job环境 xxl-job-admin平台创建定时任务 动态创建定时任务,实现动态创建15分钟未支付自动关闭订单的定时任务

quanzhan_King的博客 3756

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

分布式定时调度:xxl-job 万字详解

调度中心支持集群部署,提升调度系统容灾和可用性。调度中心集群部署时,几点要求和建议:DB配置保持一致;集群机器时钟保持一致(单机集群忽视);当启动多个调度器时,执行器配置调度中心部署跟地址可以用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;但是建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。xxl-job确实很强大,功能也很全,经过该文章学习相信你可以把xxl-job。

a1405的专栏 4456

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【链路追踪】xxl-job定时任务日志增加traceId

注入,在生产环境进行日志追溯时比较方便。但是在使用xxl-job进行定时任务管理时,却发现xxl-job线程打印出来的日志没有。注入,同时也增加了统一日志打印,不需要在每个定时任务入口都打印日志(如果不需要也可以去掉)。,查询日志时十分不方便,于是通过使用。通过增加以上类,即可实现。

Java全栈开发者 2492

XXL-JOB接入trace链路

一、背景 目前XXL-JOB使用自定义XxlJobHelper来做日志埋点,但是实际上很多内部框架的日志都未接入,此时定时任务出现错误,需要上执行器上查看日志信息,这时候无法查询到本次调度相关日志。 二、环境 <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.3.0</version>

yu542771572的博客 2126

log trace (3) - 仿Sleuth 支持异步LazyTraceAsyncTaskExecutor方式实现XXL-JOB的日志链路

在bean初始化阶段,通过代理方式将其适配为自定义的IJobHandler 实现对象XxlJobTraceWrapper。(ps: 需要通过代理的方式保证bean的class是一致的) 真正执行时调用invoke方法先从beanFactory中获取Tracing对象开启新的Span。 ...

chyd8899的博客 750

XXL-JOB的使用(详细教程)

概述 ​ 首先我们要知道什么是XXL-JOB? ​ 官方简介:XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用 ​ XXL-JOB的有点特性: ​ 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; ​ 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效; ​ ​ 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可

YHJ 2万+

Java分布式跟踪系统Zipkin(四):Brave源码分析-HttpTracing

所有博文均在个人独立博客http://blog.mozhu.org首发,欢迎访问! 上一篇博文中,我们分析了Tracing的相关源代码,这一篇我们来看看Brave是如何在Web项目中使用的 我们先来看看普通的servlet项目中,如何使用Brave,这对我们后面分析和理解Brave和Sprin...

440

将Sleuth添加到项目里做链路追踪的三种方式(Greenwich.SR5版)

Important To ensure that your application name is properly displayed in Zipkin, set thespring.application.nameproperty inbootstrap.yml.

ystyaoshengting的专栏 735
上一篇: springclou中Sleuth异步中trace传递
下一篇: Kafka副本剖析
F_Hello_World
博客等级 码龄11年 38粉丝 58原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值