[Artículos del código fuente de xxl-job 01] Interpretación del código fuente de xxl-job de la interpretación del proceso de activación de la rueda mágica del tiempo

guía

xxl-job es una plataforma de programación de tareas distribuidas, que es muy apreciada por los programadores de la industria. Este capítulo lo llevará a comprender el código fuente de xxl-job y comprender su lógica operativa.

Leer el código fuente de xxl-job mejorará su comprensión y aplicación de subprocesos múltiples y la sublimación de ideas de programación.

Este capítulo lo llevará a analizar un poco la lógica subyacente del diseño de xxl-job, para que realmente pueda comprender cada módulo en la figura a continuación y le permita saber qué es y por qué lo es.

Introduce una descripción de la imagen

estructura del proyecto

Despliegue el código, primero observamos la estructura del proyecto, xxl-job es un proyecto principal-secundario estándar, vemos que hay tres subproyectos

imagen-20220412144123632

  • servidor xxl-job-admin
  • El servidor y el cliente harán referencia al paquete principal xxl-job-core
  • xxl-job-executor-samples algunas demostraciones

Interpretación del código fuente - Temporizador

bien, ahora necesitamos saber qué hace el servidor xxl-job cuando se inicia, primero ubique esta claseXxlJobAdminConfig

Se puede ver que la función central de xxl-job se ejecuta a lo largo del ciclo de vida del bean

@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
    
    
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
        adminConfig = this;
        xxlJobScheduler = new XxlJobScheduler();
      	// 初始化
        xxlJobScheduler.init();
    }
    @Override
    public void destroy() throws Exception {
    
    
        xxlJobScheduler.destroy();
    }
}

sigue el códigocom.xxl.job.admin.core.scheduler.XxlJobScheduler#init

Se puede ver que aquí se inicializan varios ayudantes centrales, y cada ayudante usa el modo singleton hambriento, por lo que no habrá creación repetida.

public void init() throws Exception {
    
    
    // 触发池
    JobTriggerPoolHelper.toStart();
    // 服务监听器
    JobRegistryHelper.getInstance().start();
    // 失败告警
    JobFailMonitorHelper.getInstance().start();
    // 回调监听器
    JobCompleteHelper.getInstance().start();
    // 日志回调
    JobLogReportHelper.getInstance().start();
    // 定时器
    JobScheduleHelper.getInstance().start();
}

Primero localizacom.xxl.job.admin.core.thread.JobScheduleHelper#start

El ayudante es un temporizador 心脏, y su función es la temporización y el predisparo.

Comenzó dos subprocesos de daemon

  1. hilo de análisis de tareas de ScheduleThread

    Durante el ciclo, siga escaneando las próximas tareas, use mysql for update实现排他锁, evite que otros servicios escaneen en paralelo y almacene en caché las próximas tareas 时间轮en

  2. subproceso de ejecución ringThread

    时间轮tarea que se desencadena

rueda del tiempo

Echemos un vistazo a cómo se realiza la rueda del tiempo, que suena alta.

De la siguiente manera, la esencia es uno concurrentHashMap, la clave es el segundo de ejecución, el valor es la identificación del trabajo que se ejecutará y scheduleThreadel subproceso colocará la tarea en la lista de la rueda de tiempo con 5 a 10 segundos de anticipación.

private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
// ringSecond 下一次执行的时间
private void pushTimeRing(int ringSecond, int jobId){
    
         
    // push async ring                                    
    List<Integer> ringItemData = ringData.get(ringSecond);
    if (ringItemData == null) {
    
                               
        ringItemData = new ArrayList<Integer>();          
        ringData.put(ringSecond, ringItemData);           
    }                                                     
    ringItemData.add(jobId);                              
}

Activar tareas en la rueda del tiempo

ringThread = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        while (!ringThreadToStop) {
    
    
            try {
    
    
              // 时间对其
                TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
            } catch (InterruptedException e) {
    
    
                if (!ringThreadToStop) {
    
    
                    logger.error(e.getMessage(), e);
                }
            }
            try {
    
    
                // 时间轮数据处理
                List<Integer> ringItemData = new ArrayList<>();
                int nowSecond = Calendar.getInstance().get(Calendar.SECOND);   // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
                for (int i = 0; i < 2; i++) {
    
    
                    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);
                    }
                    ringItemData.clear();
                }
            } catch (Exception e) {
    
    
                if (!ringThreadToStop) {
    
    
                    logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                }
            }
        }
    }
});

archivo sin titulo

Como se muestra arriba, la duración total de la rueda del tiempo es 60, que corresponde a cada segundo en un minuto. Como se mencionó anteriormente, scheduleThread pondrá las tareas en la rueda del tiempo con 5 a 10 segundos de anticipación, y ringThread transferirá las tareas para ser ejecutada desde la rueda del tiempo Quitada, es decir, no hay operación concurrente de acceso. Debido al negocio especial de las tareas programadas, la concurrencia de la rueda del tiempo también es muy baja.

preguntas de entrevista

¿Por qué usar dos hilos?

Aísle el subproceso de exploración del subproceso de ejecución, ya que el subproceso de exploración necesita interactuar con la base de datos y utiliza un bloqueo exclusivo, lo que da como resultado un rendimiento lento.

El subproceso de ejecución no interactúa con el middleware y escanea directamente la rueda del tiempo, que tiene un alto rendimiento y se utiliza principalmente para garantizar la activación precisa de las tareas.

¿Cuáles son los beneficios de la rueda del tiempo?

La lógica no tiene bloqueos, el rendimiento es eficiente y no hay conflicto de ejecución entre los dos subprocesos.

Cómo garantizar la ejecución en el momento preciso

Alinee la hora actual con el segundo completo a través del siguiente código, que puede tener una precisión de milisegundos

TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);

Interpretación del código fuente: disparador

Esta sección rastreará la tarea de trabajo xxl desde el temporizador -> disparador -> solicitud http -> tarea de disparador

com.xxl.job.admin.core.thread.JobTriggerPoolHelper#start

El desencadenador inicializa dos grupos de subprocesos para desencadenar tareas

com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTriggerCuando se llama al activador, no se programará directamente, sino que se ejecutará utilizando el grupo de subprocesos. El propósito no es bloquear el subproceso de programación.

public void addTrigger(final int jobId,
                       final TriggerTypeEnum triggerType,
                       final int failRetryCount,
                       final String executorShardingParam,
                       final String executorParam,
                       final String addressList) {
    
    

    // 默认使用fastTriggerPool
    ThreadPoolExecutor triggerPool_ = fastTriggerPool;
    AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
    // 如果发现任务一分钟内有大于10次的慢执行,换slowTriggerPool线程池
    if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) {
    
    
        triggerPool_ = slowTriggerPool;
    }

    // 线程池执行
    triggerPool_.execute(new Runnable() {
    
    
        @Override
        public void run() {
    
    
            long start = System.currentTimeMillis();
            try {
    
    
                // 触发
                XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
            } catch (Exception e) {
    
    
                logger.error(e.getMessage(), e);
            } finally {
    
    
                // 到达下一个周期则清理上一个周期数据
                long minTim_now = System.currentTimeMillis()/60000;
                if (minTim != minTim_now) {
    
    
                    minTim = minTim_now;
                    jobTimeoutCountMap.clear();
                }

                // 记录慢任务执行次数
                long cost = System.currentTimeMillis()-start;
                if (cost > 500) {
    
           // ob-timeout threshold 500ms
                    AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));
                    if (timeoutCount != null) {
    
    
                        timeoutCount.incrementAndGet();
                    }
                }

            }

        }
    });
}

Siga el enlace a continuación

com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger com.xxl.job.admin.core.trigger.XxlJobTrigger#runExecutor

Aquí se explica cómo desencadenar la tarea.

runResult = executorBiz.run(triggerParam);

Hay dos implementaciones aquí, el lado del trabajo xxl usa ExecutorBizClient, y nuestra propia aplicación tiene efecto con ExecutorBizImpl

imagen-20220412171159090

A continuación, observe la implementación de ExecutorBizImpl

Nota: En este momento, el lado del servidor se envió a nuestro propio proyecto a través de ExecutorBizClient

@Override
public ReturnT<String> run(TriggerParam triggerParam) {
    
    
    // 获得执行控制器
    JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
    IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
    String removeOldReason = null;

    GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
    if (GlueTypeEnum.BEAN == glueTypeEnum) {
    
        // bean模式触发
        IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
        if (jobThread!=null && jobHandler != newJobHandler) {
    
    
            removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
            jobThread = null;
            jobHandler = null;
        }
        if (jobHandler == null) {
    
    
            jobHandler = newJobHandler;
            if (jobHandler == null) {
    
    
                return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
            }
        }
    } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
    
      // 原生模式触发
        if (jobThread != null &&
                !(jobThread.getHandler() instanceof GlueJobHandler
                    && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
    
    
            removeOldReason = "change job source or glue type, and terminate the old job thread.";

            jobThread = null;
            jobHandler = null;
        }
        if (jobHandler == null) {
    
    
            try {
    
    
                IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
                jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
            } catch (Exception e) {
    
    
                logger.error(e.getMessage(), e);
                return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
            }
        }
    } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
    
     // 脚本触发
        if (jobThread != null &&
                !(jobThread.getHandler() instanceof ScriptJobHandler
                        && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
    
    
            removeOldReason = "change job source or glue type, and terminate the old job thread.";
            jobThread = null;
            jobHandler = null;
        }
        if (jobHandler == null) {
    
    
            jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
        }
    } else {
    
    
        return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
    }
    // 阻塞处理策略
    if (jobThread != null) {
    
    
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
        if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
    
    
            // discard when running
            if (jobThread.isRunningOrHasQueue()) {
    
    
                return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
            }
        } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
    
    
            // kill running jobThread
            if (jobThread.isRunningOrHasQueue()) {
    
    
                removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();

                jobThread = null;
            }
        } else {
    
    
            // just queue trigger
        }
    }

    if (jobThread == null) {
    
    
        // 创建执行控制器
        jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
    }

    // 将数据放入执行队列
    ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
    return pushResult;
}

Tenga en cuenta que esto no espera a que se complete la ejecución de la tarea, sino que devuelve directamente el resultado del activador después de colocarlo en la cola, y el resultado de la ejecución se notificará al lado del servidor más tarde.

Ingresarcom.xxl.job.core.thread.JobThread#pushTriggerQueue

public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {
    
    
   // 防止重复触发的set
   if (triggerLogIdSet.contains(triggerParam.getLogId())) {
    
    
      logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());
      return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());
   }
   triggerLogIdSet.add(triggerParam.getLogId());
   // 加入待执行队列
   triggerQueue.add(triggerParam);
       return ReturnT.SUCCESS;
}

Esta clase es el controlador que ejecuta el controlador. Hereda Thread. Echemos un vistazo al método de ejecución.

com.xxl.job.core.thread.JobThread#run

   @Override
public void run() {
    
    
       // init
       try {
    
    
      handler.init();
   } catch (Throwable e) {
    
    
          logger.error(e.getMessage(), e);
   }
   while(!toStop){
    
    
      running = false;
      idleTimes++; // 增加空闲的次数

           TriggerParam triggerParam = null;
           try {
    
    
         // 将队列中待执行待任务poll出来
         triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
         if (triggerParam!=null) {
    
    
            running = true;
            idleTimes = 0;
            triggerLogIdSet.remove(triggerParam.getLogId());

            // 记录上下文对象,用于数据分片,日志记录等动作
            String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId());
            XxlJobContext xxlJobContext = new XxlJobContext(
                  triggerParam.getJobId(),
                  triggerParam.getExecutorParams(),
                  logFileName,
                  triggerParam.getBroadcastIndex(),
                  triggerParam.getBroadcastTotal());
            XxlJobContext.setXxlJobContext(xxlJobContext);

            XxlJobHelper.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + xxlJobContext.getJobParam());
            if (triggerParam.getExecutorTimeout() > 0) {
    
    
               // 有设置执行时间的话通过FutureTask实现等待超时的动作
               Thread futureThread = null;
               try {
    
    
                  FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
    
    
                     @Override
                     public Boolean call() throws Exception {
    
    
                        // 记录上下文对象,用于数据分片,日志记录等动作
                        XxlJobContext.setXxlJobContext(xxlJobContext);
                        handler.execute();
                        return true;
                     }
                  });
                  // 创建并执行任务线程
                  futureThread = new Thread(futureTask);
                  futureThread.start();

                  Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
               } catch (TimeoutException e) {
    
    
                  // 执行超时处理
                  XxlJobHelper.log("<br>----------- xxl-job job execute timeout");
                  XxlJobHelper.log(e);
                  XxlJobHelper.handleTimeout("job execute timeout ");
               } finally {
    
    
                  futureThread.interrupt();
               }
            } else {
    
    
               // 如果有设置执行超时时间直接执行
               handler.execute();
            }

            // 校验执行状态
            if (XxlJobContext.getXxlJobContext().getHandleCode() <= 0) {
    
    
               XxlJobHelper.handleFail("job handle result lost.");
            } else {
    
    
               // 截取日志长度,防止过长影响性能
               String tempHandleMsg = XxlJobContext.getXxlJobContext().getHandleMsg();
               tempHandleMsg = (tempHandleMsg!=null&&tempHandleMsg.length()>50000)
                     ?tempHandleMsg.substring(0, 50000).concat("...")
                     :tempHandleMsg;
               XxlJobContext.getXxlJobContext().setHandleMsg(tempHandleMsg);
            }
            XxlJobHelper.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- Result: handleCode="
                  + XxlJobContext.getXxlJobContext().getHandleCode()
                  + ", handleMsg = "
                  + XxlJobContext.getXxlJobContext().getHandleMsg()
            );
         } else {
    
    
            if (idleTimes > 30) {
    
    
               if(triggerQueue.size() == 0) {
    
    
                  // 当空闲次数大于30次且队列中无待执行时移除控制器,释放资源
                  XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
               }
            }
         }
      } catch (Throwable e) {
    
    
         // 异常,记录错误日志
         if (toStop) {
    
    
            XxlJobHelper.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
         }

         // handle result
         StringWriter stringWriter = new StringWriter();
         e.printStackTrace(new PrintWriter(stringWriter));
         String errorMsg = stringWriter.toString();

         XxlJobHelper.handleFail(errorMsg);

         XxlJobHelper.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
      } finally {
    
    
         // 将执行结果和日志通知给xxl-job
               if(triggerParam != null) {
    
    
                   if (!toStop) {
    
    
                       TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
                              triggerParam.getLogId(),
                     triggerParam.getLogDateTime(),
                     XxlJobContext.getXxlJobContext().getHandleCode(),
                     XxlJobContext.getXxlJobContext().getHandleMsg() )
               );
                   } else {
    
    
                       TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
                              triggerParam.getLogId(),
                     triggerParam.getLogDateTime(),
                     XxlJobContext.HANDLE_CODE_FAIL,
                     stopReason + " [job running, killed]" )
               );
                   }
               }
           }
       }

   // 将队列中的任务回调标记失败处理
   while(triggerQueue !=null && triggerQueue.size()>0){
    
    
      TriggerParam triggerParam = triggerQueue.poll();
      if (triggerParam!=null) {
    
    
         TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
               triggerParam.getLogId(),
               triggerParam.getLogDateTime(),
               XxlJobContext.HANDLE_CODE_FAIL,
               stopReason + " [job not executed, in the job queue, killed.]")
         );
      }
   }
   try {
    
    
      handler.destroy();
   } catch (Throwable e) {
    
    
      logger.error(e.getMessage(), e);
   }

   logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
}

presta atención a este párrafo

XxlJobContext.setXxlJobContext(xxlJobContext);

xxl-job的日志系统Use threadlocal para mantener el objeto de contexto de xxl-job, que es muy útil, y algunos de los datos que se mantienen en él se usarán durante la ejecución de la tarea.

A través del código anterior, podemos ver que xxl-job hace que nuestra aplicación espere a que se complete la ejecución sin bloquearse. Volverá inmediatamente después de que la programación sea exitosa. El resultado de la ejecución y el registro de ejecución se notifican a xxl-job después de la solicitud. La ventaja de esto es que xxl-job puede saber rápidamente si el servidor se programó correctamente y no ocupará la cantidad de conexiones del sistema porque la tarea no se completó. xxl-job liberará recursos automáticamente cuando el subproceso de ejecución esté inactivo y no seguirá ocupando recursos del sistema.

Enlace

[xxl-job código fuente artículos 02] ¿Aplicación de RPC netty desarrollado por el centro de registro?

[Artículos 03 del código fuente de xxl-job] Interpretación del código fuente del sistema de registro de xxl-job

Supongo que te gusta

Origin blog.csdn.net/qq_21046665/article/details/124127842
Recomendado
Clasificación