xxl-job原理分析

xxl-job学习

文章目录

设计思想

  • 调度中心:将调度行为抽象形成"调度中心"公共平台,而平台自身并不承担业务逻辑,"调度中心"负责发起调度请求。
  • 执行器将任务抽象成分散的JobHandler,交由"执行器"统一管理,"执行器"负责接收调度请求并执行对应的JobHandler中业务逻辑。
  • 因此,"调度"和"任务"两部分可以相互解耦,提高系统整体稳定性和扩展性;

简单总结

xxl-lob分布式的任务调度框架

核心思想是:任务的调度和任务的执行分离,任务的调度中心化统一调度、任务的执行分布式执行

系统模块:调度中心分为执行器管理、任务管理(调度配置CRON、运行配置、路由配置、失败策略)

关键技术:

1.调度器和执行器如何通信,通过内嵌Netty作为http服务器,调度中心提供注册、回调等api接口、执行中心:提供心跳、心跳繁忙、运行等api接口

2.调度器如何调度:间隔5秒扫描任务表xxl_job_info的下次触发时间消息当前时间加5秒(trigger_next_time < now + 5),再把5秒内将要执行的任务放入时间轮等待调度

3.调度器如何保证并发调度:基于mysql悲观锁机制xxl_job_lock表加锁

4.执行器如何注册到调度中心:执行器30秒定时注册

5.执行器如何自动扫描:扫描所有xxlJob注解的方法并维护,XxlJobSpringExecutor.afterSingletonsInstantiated()

调度平台源码分析

  • 调度管理平台如何发现需要调度的任务:通过xxl_job_lock表加锁并且间隔5秒扫描任务表xxl_job_info的下次触发时间消息当前时间加5秒(trigger_next_time < now + 5),再把5秒内将要执行的任务放入时间轮等待调度,这里直接回答解决了问题1、2、3,核心代码逻辑JobScheduleHelper#start
java 复制代码
 //定时调度线程
		scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                }

                // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
                int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;

                while (!scheduleThreadToStop) {
                    // Scan Job
                    long start = System.currentTimeMillis();

                    boolean preReadSuc = true;
                    try {

                        conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
                        connAutoCommit = conn.getAutoCommit();
                        conn.setAutoCommit(false);
						//加锁避免分布式并发执行
                        preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
                        preparedStatement.execute();
                        long nowTime = System.currentTimeMillis();
						
						//扫描下次触发时间小于当前时间加5秒的任务
                        List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
                        if (scheduleList!=null && scheduleList.size()>0) {
                            for (XxlJobInfo jobInfo: scheduleList) {

                                //下次触发时间可能已过期5秒,可能是调度平台宕机一直没有刷新下次触发时间
                                if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
                                    //刷新下次触发时间
                                    refreshNextValidTime(jobInfo, new Date());

                                } else if (nowTime > jobInfo.getTriggerNextTime()) {
                                    //下次触发时间过期但在5秒以内,立即触发执行
                                    JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);

                                    //刷新下次触发时间
                                    refreshNextValidTime(jobInfo, new Date());

                                    //下次触发时间如果将在5秒以内执行的任务,放入时间轮等待触发,这样是因为上面刷新了下次触发时间所以增加判断
                                    if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
                                        // 1、计算时间轮的秒
                                        int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
                                        // 2、放入时间轮
                                        pushTimeRing(ringSecond, jobInfo.getId());
                                        // 3、刷新下次触发时间
                                        refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));

                                    }

                                } else {
                                    // 下次触发时间如果将在5秒以内执行的任务
                                    // 1、make ring second
                                    int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
                                    // 2、push time ring
                                    pushTimeRing(ringSecond, jobInfo.getId());
                                    // 3、fresh next
                                    refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));

                                }

                            }

                            // 3、更新任务信息,主要是更新任务的下次触发时间
                            for (XxlJobInfo jobInfo: scheduleList) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
                            }

                        } else {
                            preReadSuc = false;
                        }


                    } catch (Exception e) {
                    } finally {
                        // 省略
                    }
                    long cost = System.currentTimeMillis()-start;

                    if (cost < 1000) {  // scan-overtime, not wait
                        try {
                            // 如果扫描到任务每秒执行, 没有扫描到则间隔一个扫描周期5秒执行
                            TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
                        } catch (InterruptedException e) {
                        }
                    }

                }

                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
            }
        });
        scheduleThread.setDaemon(true);
        scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
        scheduleThread.start();

		//时间轮执行线程
		ringThread = new Thread(new Runnable() {
            @Override
            public void run() {

                while (!ringThreadToStop) {

                    // align second
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
                    } catch (InterruptedException e) {
                    }

                    try {
                        List<Integer> ringItemData = new ArrayList<>();
                        int nowSecond = Calendar.getInstance().get(Calendar.SECOND); 
						// 避免处理耗时太长,跨过刻度,向前校验一个刻度; 因为这里是从时间轮移除所以不会重复执行
                        for (int i = 0; i < 2; i++) {
							//从时间轮获取当前的秒 对应的 任务列表,rangData类型:Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
                            List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
                            if (tmpData != null) {
                                ringItemData.addAll(tmpData);
                            }
                        }
                        if (ringItemData.size() > 0) {
                            for (int jobId: ringItemData) {
                                //触发执行
                                JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
                            }
                            // clear
                            ringItemData.clear();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        });
        ringThread.setDaemon(true);
        ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
        ringThread.start();
    }

执行器源码分析(业务端)

  • 扫描所有xxlJob注册的方法,XxlJobSpringExecutor#afterSingletonsInstantiated
java 复制代码
public void afterSingletonsInstantiated() {
        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(applicationContext);
    }

private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init job handler from method
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = applicationContext.getBean(beanDefinitionName);

            Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                            }
                        });
            } catch (Throwable ex) {
            }
            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
                continue;
            }

            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                // regist
                registJobHandler(xxlJob, bean, executeMethod);
            }
        }
    }


protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
        if (xxlJob == null) {
            return;
        }
        String name = xxlJob.value();
        Class<?> clazz = bean.getClass();
        String methodName = executeMethod.getName();

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

    }

public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
  		//执行任务的name和处理器映射
        return jobHandlerRepository.put(name, jobHandler);
    }

参考文档

相关推荐
pyniu12 分钟前
Spring Boot车辆管理系统实战开发
java·spring boot·后端
虾说羊13 分钟前
ssm项目本地部署
java·tomcat
资生算法程序员_畅想家_剑魔13 分钟前
Kotlin常见技术分享-01-相对于Java 的核心优势-空安全
java·安全·kotlin
gelald20 分钟前
AQS 解析:从原理到实战
java·后端
2301_7806698620 分钟前
集合框架(Collection单列集合(常用功能,三种遍历方式及通过并发修改异常认识他们的区别)、Map双列集合)
java
进阶小白猿20 分钟前
Java技术八股学习Day14
java·数据库·学习
super_lzb22 分钟前
mybatis拦截器ResultSetHandler详解
java·spring·mybatis·springboot
代码or搬砖26 分钟前
JVM垃圾回收器
java·jvm·算法
客卿12328 分钟前
C语言刷题--合并有序数组
java·c语言·算法