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

各种AI的调试日记——辅助工作篇

记录使用各种AI(语言大模型)的心得和总结

m0_73056986的博客 1391

Stm32L0 STM32CUBE中UART和使用LPUART1以及串口不进中断问题解决

/* 开启串口1中断 */ LL_USART_EnableIT_RXNE(LPUART1); /* 开启串口2中断 */ LL_USART_EnableIT_RXNE(USART2);

シ❤゛甜虾的个人博客 7687

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

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

fhey的博客 601

国产安全芯片在密码算法国密体系下的性能适配策略

国密体系即国家商用密码体系,是保障信息安全的核心技术体系。自20世纪90年代末起步,国密体系经历了从无到有、从弱到强的发展历程。1999年,国家密码管理局成立,标志着国密体系的正式建立。2000年,国密SM1算法首次发布,开启了国密算法的标准化进程。此后,SM2、SM3、SM4等算法陆续推出,形成了完整的对称与非对称密码算法体系。:2010年,国密标准正式纳入国家标准体系,国密算法在金融、政务、通信等关键领域的应用得到明确规范。

qq_40552871的博客 1356

SpringCloud Sleuth分布式请求链路跟踪

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

chongbaozhong的博客 423

MaxEnt物种分布预测中的常见误区与优化技巧:如何提高模型准确率

本文深入探讨了MaxEnt物种分布预测模型从基础应用到精准预测的进阶实践。针对模型准确率提升,文章系统分析了环境变量筛选、模型参数调优、结果批判性解读等核心环节的常见误区,并提供了基于生态位模型原理的深度优化技巧,帮助研究者产出更可靠、可解释的预测结果。

weixin_29219539的博客 594

Windows下用Caddy一键搞定LiveKit本地HTTPS部署(含自签名证书避坑指南)

本文详细介绍了在Windows本地开发环境中,如何利用Caddy服务器一键部署支持HTTPS和WSS的LiveKit实时通信服务。文章重点解决了自签名证书的生成与配置难题,提供了完整的避坑指南和防火墙设置,帮助开发者快速搭建安全的WebRTC测试环境,实现局域网内的实时音视频互动。

weixin_29274969的博客 583

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

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

DayDayUp的博客 247

你一直忽略的神器:离你最近的超强语音输入法(微信语音输入全攻略,支持全局混用)

你一直忽略的语音输入工具🎤 ,其实就在身边!无需额外软件,即可实现全局语音转文字⚡ 支持中英文混输,还能与其他输入法混合使用,聊天、办公都能高效完成,让你的输入效率轻松翻倍🚀

XiaoqiangClub的博客 907

XXL-JOB的基本使用

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

Relievedz的博客 5488

ChatTutor:让AI老师“会写会画”,打造可视化、互动式智能教育新范式

ChatTutor是一款突破性的开源AI教学助手,通过可视化电子白板实现"边讲边写"的沉浸式教学体验。该项目支持数学公式手写输入、动态函数绘图、自动生成思维导图等功能,采用多智能体架构实现专业协同。未来将扩展至编程、物理等STEM教育领域,适用于学生自学、教师备课和教育平台集成。作为MIT协议开源项目,ChatTutor通过赋予AI"教学肢体语言",推动教育从信息传递转向可视化互动建构,重新定义了AI助教的可能性。

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 423

aic8800出现insmod: can‘t insert ‘/lib/modules/4.9.191/aic8800_fdrv.ko‘: No such device

2、如果单独编译wifi驱动,则把CONFIG_PLATFORM_UBUNTU?= y都改为CONFIG_PLATFORM_ALLWINNER?1、如果把wifi驱动嵌入到内核编译,则在makefile中加入这一句。后面再修改对应平台的交叉编译链和内核吗路径等。

mantouyouyou的博客 511

xxl-job添加日志traceId

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

zoro777的博客 5579

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

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

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

quanzhan_King的博客 3756

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

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

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

a1405的专栏 4456

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

【链路追踪】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、付费专栏及课程。

余额充值