简要剖析xxljob的使用和原理

28 阅读12分钟

简要剖析xxljob的使用和原理

一、使用场景

XXL-JOB是什么 XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

为什么要叫 XXL 呢?答:是因为他的作者的名字叫许雪里,使用了名字的缩写

分布式任务调度平台是什么呢?答:一个定时任务实现方

优点是什么呢:(大家都懂,免费,简单,能满足需求)

1.xxljob是分布式的,可以满足大部分现在的分布式业务需求。

2.文档比较全,对开发人员友善,咱们都知道文档的重要性,少走很多弯路。

3.有界面。操作可视化,简单明了。

4.最重要就是部署简单,方便快捷。

二、服务搭建

我们使用目前主流的docker安装的方式(主要是省事)

1.使用mysql创建需要的数据库表,结构如下:

2BC07EF5-EB42-4273-8D45-8AEB3428A56E.png

2.搭建admin服务

1.拉取镜像 docker pull xuxueli/xxl-job-admin:2.3.0

2.执行dockerfile文件或者直接命令行启动,方便起见直接后者

docker run -d --name xxl-job-admin -p 4090:8080 \
-e PARAMS="\
--spring.datasource.url=jdbc:mysql://ip地址:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \
--spring.datasource.username=xxl \
--spring.datasource.password=123456" \
-v /usr/local/xxl-job/admin/logs:/data/applogs \
--privileged=true \
--name xxl-job \
xuxueli/xxl-job-admin:2.3.0

3.查看容器状态, docker ps 如果正常就说明安装好了

注意事项:

​ 服务器部署之后要开放端口号。建议开放端口号别关防火墙。基本防护意识要有,即使是测试或者自己服务器;

# 1、开启防火墙
systemctl start firewalld
# 2、开放指定端口(比如4090端口)
firewall-cmd --zone=public --add-port=4090/tcp --permanent
# 3、重启防火墙
firewall-cmd --reload
# 4、查看端口号
netstat -ntlp   #查看当前所有tcp端口·

三、基本使用和demo案例

一般就是引入使用一个中间件的四件套

1.引依赖
        <!-- xxl-job-core -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>2.3.1</version>
        </dependency>

2.写yaml

你的properties建议写nacos上方便后期热更新

# web port
server.port=8081
# no web
#spring.main.web-environment=false

# log config
logging.config=classpath:logback.xml


### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

### xxl-job, access token
xxl.job.accessToken=default_token

### xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30

3.创建配置类
@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
4.写任务
package com.xxl.job.executor.service.jobhandler;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * XxlJob开发示例(Bean模式)
 *
 * 开发步骤:
 *      1、任务开发:在Spring Bean实例中,开发Job方法;
 *      2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
 *      3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
 *      4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
 *
 * @author xuxueli 2019-12-11 21:52:51
 */
@Component
public class SampleXxlJob {
    private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);


    /**
     * 1、简单任务示例(Bean模式)
     */
    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        XxlJobHelper.log("XXL-JOB, Hello World.");

        for (int i = 0; i < 5; i++) {
            XxlJobHelper.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        // default success
    }


    /**
     * 2、分片广播任务
     */
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler() throws Exception {

        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);

        // 业务逻辑
        for (int i = 0; i < shardTotal; i++) {
            if (i == shardIndex) {
                XxlJobHelper.log("第 {} 片, 命中分片开始处理", i);
            } else {
                XxlJobHelper.log("第 {} 片, 忽略", i);
            }
        }

    }


    /**
     * 3、命令行任务
     */
    @XxlJob("commandJobHandler")
    public void commandJobHandler() throws Exception {
        String command = XxlJobHelper.getJobParam();
        int exitValue = -1;

        BufferedReader bufferedReader = null;
        try {
            // command process
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.command(command);
            processBuilder.redirectErrorStream(true);

            Process process = processBuilder.start();
            //Process process = Runtime.getRuntime().exec(command);

            BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));

            // command log
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                XxlJobHelper.log(line);
            }

            // command exit
            process.waitFor();
            exitValue = process.exitValue();
        } catch (Exception e) {
            XxlJobHelper.log(e);
        } finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }

        if (exitValue == 0) {
            // default success
        } else {
            XxlJobHelper.handleFail("command exit value("+exitValue+") is failed");
        }

    }


    /**
     * 4、跨平台Http任务
     *  参数示例:
     *      "url: http://www.baidu.com\n" +
     *      "method: get\n" +
     *      "data: content\n";
     */
    @XxlJob("httpJobHandler")
    public void httpJobHandler() throws Exception {

        // param parse
        String param = XxlJobHelper.getJobParam();
        if (param==null || param.trim().length()==0) {
            XxlJobHelper.log("param["+ param +"] invalid.");

            XxlJobHelper.handleFail();
            return;
        }

        String[] httpParams = param.split("\n");
        String url = null;
        String method = null;
        String data = null;
        for (String httpParam: httpParams) {
            if (httpParam.startsWith("url:")) {
                url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
            }
            if (httpParam.startsWith("method:")) {
                method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
            }
            if (httpParam.startsWith("data:")) {
                data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
            }
        }

        // param valid
        if (url==null || url.trim().length()==0) {
            XxlJobHelper.log("url["+ url +"] invalid.");

            XxlJobHelper.handleFail();
            return;
        }
        if (method==null || !Arrays.asList("GET", "POST").contains(method)) {
            XxlJobHelper.log("method["+ method +"] invalid.");

            XxlJobHelper.handleFail();
            return;
        }
        boolean isPostMethod = method.equals("POST");

        // request
        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;
        try {
            // connection
            URL realUrl = new URL(url);
            connection = (HttpURLConnection) realUrl.openConnection();

            // connection setting
            connection.setRequestMethod(method);
            connection.setDoOutput(isPostMethod);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setReadTimeout(5 * 1000);
            connection.setConnectTimeout(3 * 1000);
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");

            // do connection
            connection.connect();

            // data
            if (isPostMethod && data!=null && data.trim().length()>0) {
                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
                dataOutputStream.write(data.getBytes("UTF-8"));
                dataOutputStream.flush();
                dataOutputStream.close();
            }

            // valid StatusCode
            int statusCode = connection.getResponseCode();
            if (statusCode != 200) {
                throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
            }

            // result
            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
            String responseMsg = result.toString();

            XxlJobHelper.log(responseMsg);

            return;
        } catch (Exception e) {
            XxlJobHelper.log(e);

            XxlJobHelper.handleFail();
            return;
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                XxlJobHelper.log(e2);
            }
        }

    }

    /**
     * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
     */
    @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
    public void demoJobHandler2() throws Exception {
        XxlJobHelper.log("XXL-JOB, Hello World.");
    }
    public void init(){
        logger.info("init");
    }
    public void destroy(){
        logger.info("destroy");
    }


}

四、原理分析

一、注解分析
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface XxlJob {
    String value();

    String init() default "";

    String destroy() default "";
}
@XxlJob注解中的init参数和destroy参数

init参数

  • 用途:指定在任务执行之前需要执行的方法名称。
  • 典型用例:初始化任务所需的资源、配置、环境等。
  • 执行时机:在任务实际执行之前调用。

destroy参数

  • 用途:指定在任务执行之后需要执行的方法名称。

  • 典型用例:清理任务执行过程中使用的资源、释放占用的资源、记录日志等。

  • 执行时机:在任务实际执行完毕之后调用。

简单demo
import com.xxl.job.core.handler.annotation.XxlJob;

public class MyJob {

    // 任务方法
    @XxlJob(value = "myJobHandler", init = "initMethod", destroy = "destroyMethod")
    public void execute() {
        // 任务执行的具体逻辑
        System.out.println("Executing job...");
    }

    // 初始化方法
    public void initMethod() {
        // 初始化逻辑,例如资源准备
        System.out.println("Initializing resources...");
    }

    // 销毁方法
    public void destroyMethod() {
        // 清理逻辑,例如资源释放
        System.out.println("Releasing resources...");
    }
}

注解中的这些方法,会在注册任务执行器的时候添加到任务执行器中;

在XxlJobExecutor类中会有这样一个方法:protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod)

executeMethod.setAccessible(true);
                Method initMethod = null;
                Method destroyMethod = null;
                if (xxlJob.init().trim().length() > 0) {
                    try {
                        initMethod = clazz.getDeclaredMethod(xxlJob.init());
                        initMethod.setAccessible(true);
                    } catch (NoSuchMethodException var11) {
                        throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
                    }
                }

                if (xxlJob.destroy().trim().length() > 0) {
                    try {
                        destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
                        destroyMethod.setAccessible(true);
                    } catch (NoSuchMethodException var10) {
                        throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
                    }
                }
//注册任务执行器,传入一个方法执行器,传入的参数包括初始化和注销方法,作用是将任务执行器存放到一个ConcurrentHashMap中,方便后面进行执行
                registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

MethodJobHandler类如下:

public class MethodJobHandler extends IJobHandler {
    private final Object target;
    private final Method method;
    private Method initMethod;
    private Method destroyMethod;

    public MethodJobHandler(Object target, Method method, Method initMethod, Method destroyMethod) {
        this.target = target;
        this.method = method;
        this.initMethod = initMethod;
        this.destroyMethod = destroyMethod;
    }}
二、调用链路分析

在XxlJobConfig中会注入一个XxlJobSpringExecutor的bean对象,用来初始化执行器的配置。

这个XxlJobSpringExecutor对象打开源码我们会看到是这样一个类:

public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean{}

我们可以看到这个类总共实现了三个接口,分别是

ApplicationContextAware:对应用程序上下文的引用,实现 ApplicationContextAware 接口的 Bean 可以获取对 Spring 应用程序上下文的引用,从而能够访问应用程序上下文中的其他 Bean、配置信息、资源等;也可以访问ioc容器中的其他bean;

SmartInitializingSingletonSmartInitializingSingleton 是 Spring 框架中的一个接口,它只有一个方法 afterSingletonsInstantiated。这个方法在 Spring 容器中所有单例 Bean 都初始化完成之后会被调用。在执行器中主要担任的作用就是获取到所有需要执行的xxljob任务;

DisposableBean:主要就是销毁后执行的方法。

在这里我们先知道这些接口的作用,等一会进行链路分析的时候碰到了再详细讲解:

这个类中自己本身的方法主要有两个我们来看一下

  • 实现SmartInitializingSingleton 接口的afterSingletonsInstantiated方法

    public void afterSingletonsInstantiated() {
    //进行初始化任务方法注册器
            this.initJobHandlerMethodRepository(applicationContext);
            GlueFactory.refreshInstance(1);
    
            try {
            //父类的开始方法
                super.start();
            } catch (Exception var2) {
                throw new RuntimeException(var2);
            }
        }
    
    • initJobHandlerMethodRepository方法

      private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
              if (applicationContext != null) {
                //获取所有bena的名字
                  String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
                  String[] var3 = beanDefinitionNames;
                  int var4 = beanDefinitionNames.length;
      
                  for(int var5 = 0; var5 < var4; ++var5) {
                      String beanDefinitionName = var3[var5];
                      Object bean = null;
                   //获取是否是懒加载的类
                      Lazy onBean = (Lazy)applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);
                      if (onBean != null) {
                          logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName);
                      } else {
                        //获取对应的bean
                          bean = applicationContext.getBean(beanDefinitionName);
                          Map<Method, XxlJob> annotatedMethods = null;
      
                          try {
                            //获取指定bean中有xxljob注解的方法
                              annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), new MethodIntrospector.MetadataLookup<XxlJob>() {
                                  public XxlJob inspect(Method method) {
                                      return (XxlJob)AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                                  }
                              });
                          } catch (Throwable var14) {
                              logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", var14);
                          }
      										//遍历方法集合
                          if (annotatedMethods != null && !annotatedMethods.isEmpty()) {
                              Iterator var10 = annotatedMethods.entrySet().iterator();
      
                              while(var10.hasNext()) {
                                  Map.Entry<Method, XxlJob> methodXxlJobEntry = (Map.Entry)var10.next();
                                  Method executeMethod = (Method)methodXxlJobEntry.getKey();
                                  XxlJob xxlJob = (XxlJob)methodXxlJobEntry.getValue();
                                
                                //注册任务执行器(这个方法是父类中的方法)
                                  this.registJobHandler(xxlJob, bean, executeMethod);
                              }
                          }
                      }
                  }
      
              }
          }
      

      父类XxlJobExecutor中的protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod)方法

      protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod) {
      //获取注解中的方法名和init方法和destroy方法
              if (xxlJob != null) {
                  String name = xxlJob.value();
                  Class<?> clazz = bean.getClass();
                  String methodName = executeMethod.getName();
                  if (name.trim().length() == 0) {
                      throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
                  } else if (loadJobHandler(name) != null) {
                      throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                  } else {
                      executeMethod.setAccessible(true);
                      Method initMethod = null;
                      Method destroyMethod = null;
                      if (xxlJob.init().trim().length() > 0) {
                          try {
                              initMethod = clazz.getDeclaredMethod(xxlJob.init());
                              initMethod.setAccessible(true);
                          } catch (NoSuchMethodException var11) {
                              throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
                          }
                      }
      
                      if (xxlJob.destroy().trim().length() > 0) {
                          try {
                              destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
                              destroyMethod.setAccessible(true);
                          } catch (NoSuchMethodException var10) {
                              throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
                          }
                      }
      //注册任务,将其传入到jobHandlerRepository任务集合中,key为name,值为方法任务执行器
      //方法为jobHandlerRepository.put(name, jobHandler);
                      registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
                  }
              }
          }
      
      
    • super.start()方法:个人认为这就是为啥能够实现热更新定时任务的原因。实时监听admin服务器的接口信息

      //实现父类中的方法
      public void start() throws Exception {
      //前面都是设置一些配置信息
              XxlJobFileAppender.initLogPath(this.logPath);
              this.initAdminBizList(this.adminAddresses, this.accessToken);
              JobLogFileCleanThread.getInstance().start((long)this.logRetentionDays);
              TriggerCallbackThread.getInstance().start();
              //这一步是初始化嵌入服务器也就是为啥
              this.initEmbedServer(this.address, this.ip, this.port, this.appname, this.accessToken);
          }
      
      • initEmbedServer的方法主要用途是用于启动一个嵌入式的HTTP服务器。这个服务器使用了Netty框架来处理网络通信。

            private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
                port = port > 0 ? port : NetUtil.findAvailablePort(9999);
                ip = ip != null && ip.trim().length() > 0 ? ip : IpUtil.getIp();
                if (address == null || address.trim().length() == 0) {
                    String ip_port_address = IpUtil.getIpPort(ip, port);
                    address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
                }
        
                if (accessToken == null || accessToken.trim().length() == 0) {
                    logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
                }
        
                this.embedServer = new EmbedServer();
                //开始执行服务器
                this.embedServer.start(address, port, appname, accessToken);
            }
        

        start()执行服务器

        public void start(final String address, final int port, final String appname, final String accessToken) {
                this.executorBiz = new ExecutorBizImpl();
                //构建线程
                this.thread = new Thread(new Runnable() {
                    public void run() {
                    //用于接受连接的线程组
                        EventLoopGroup bossGroup = new NioEventLoopGroup();
                        //用于处理已经被接受连接的线程组。
                        EventLoopGroup workerGroup = new NioEventLoopGroup();
                        //自定义线程池
                        final ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(0, 200, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), new ThreadFactory() {
                        //自定义线程工厂返回线程名字
                            public Thread newThread(Runnable r) {
                                return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
                            }
                        }, new RejectedExecutionHandler() {
                        //处理任务拒绝策略,当线程池无法接受新的任务时抛出异常。
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                            }
                        });
                        
        //ServerBootstrap:Netty服务器的启动辅助类。
        bossGroup和workerGroup:设置boss和worker线程组。
        NioServerSocketChannel:指定使用NIO传输类型的通道。
        childHandler:设置处理每个新连接的处理器。
        IdleStateHandler:处理空闲状态的检测。
        HttpServerCodec:HTTP请求解码器和响应编码器。
        HttpObjectAggregator:将HTTP消息聚合成完整的HTTP请求或响应。
        EmbedHttpServerHandler:自定义的业务处理器。
        childOption(ChannelOption.SO_KEEPALIVE, true):启用TCP的SO_KEEPALIVE选项,保持长连接。
        
                        try {
                            ServerBootstrap bootstrap = new ServerBootstrap();
                            ((ServerBootstrap)bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() {
                                public void initChannel(SocketChannel channel) throws Exception {
                                    channel.pipeline().addLast(new ChannelHandler[]{new IdleStateHandler(0L, 0L, 90L, TimeUnit.SECONDS)}).addLast(new ChannelHandler[]{new HttpServerCodec()}).addLast(new ChannelHandler[]{new HttpObjectAggregator(5242880)}).addLast(new ChannelHandler[]{new EmbedHttpServerHandler(EmbedServer.this.executorBiz, accessToken, bizThreadPool)});
                                }
                            }).childOption(ChannelOption.SO_KEEPALIVE, true);
                            //保存端口启动服务器
                            ChannelFuture future = bootstrap.bind(port).sync();
                            EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
                            EmbedServer.this.startRegistry(appname, address);
                            future.channel().closeFuture().sync();
                        } catch (InterruptedException var16) {
                            EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                        } catch (Exception var17) {
                            EmbedServer.logger.error(">>>>>>>>>>> xxl-job remoting server error.", var17);
                        } finally {
                        //优雅的释放资源记录日志
                            try {
                                workerGroup.shutdownGracefully();
                                bossGroup.shutdownGracefully();
                            } catch (Exception var15) {
                                EmbedServer.logger.error(var15.getMessage(), var15);
                            }
        
                        }
        
                    }
                });
                //守护线程
                this.thread.setDaemon(true);
                this.thread.start();
            }
        
  • 实现DisposableBeandestroy()方法

    public void destroy() {
    //执行父类的方法
            super.destroy();
        }
        //父类中的方法
      public void destroy() {
      //停止嵌入式的执行器,也就是之前提到的netty框架的服务器
            this.stopEmbedServer();
            //如果有执行的任务就遍历停止
            if (jobThreadRepository.size() > 0) {
                Iterator var1 = jobThreadRepository.entrySet().iterator();
    
                while(var1.hasNext()) {
                    Map.Entry<Integer, JobThread> item = (Map.Entry)var1.next();
                    //移除任务线程,并传递销毁原因
                    JobThread oldJobThread = removeJobThread((Integer)item.getKey(), "web container destroy and kill the job.");
                    if (oldJobThread != null) {
                        try {
                        //等待当前任务执行完毕后再进行操作,确保正在执行的任务能够执行完毕
                            oldJobThread.join();
                        } catch (InterruptedException var5) {
                            logger.error(">>>>>>>>>>> xxl-job, JobThread destroy(join) error, jobId:{}", item.getKey(), var5);
                        }
                    }
                }
    					//释放资源
                jobThreadRepository.clear();
            }
    				//清除执行器 
            jobHandlerRepository.clear();
            // 停止日志清理线程和回调线程,确保它们不再运行
            JobLogFileCleanThread.getInstance().toStop();
            TriggerCallbackThread.getInstance().toStop();
        }
    

    为什么使用join方法?

    使用join方法的目的是确保在执行后续清理操作之前,所有任务线程都已经完全执行完毕。这有几个好处: 保证线程安全:确保所有任务线程安全地终止,避免资源冲突或不一致状态。 清理资源:确保任务线程执行完毕后,再进行资源清理和释放操作。 逻辑完整性:确保任务执行的完整性,不会因为主线程过早继续执行而导致任务线程未执行完毕。

三、写在结尾

自己真正意义上的第一篇文档输出。也是在上个星期和掘友们讨论的:想要提高个人技术水平,除了扩展知识的广度外也要注意知识的深度。除了知道如何用还要知其所以然。 有时候可能知道自己并不能很好的坚持下去,但是有时候有个好的开始已经够了。

我最喜欢的一句话就是,对过程努力,对结果佛系。 春有百花秋有月,夏有蝉鸣冬有雪。有遗憾有不足才是再正常不过的事情了。乐观点看,能开始本身就是极好的,这也是我写这篇文章的原因和内在动力。