原理篇-- 定时任务xxl-job-执行器项目启动过程--执行器注册&任务执行(4)


前言

本文对initEmbedServer 内容进行介绍。


一、initEmbedServer 作用

initEmbedServer 将执行器的地址项目服务端进行注册,并开启nety 服务,用于接收来自服务端的请求;

二、initEmbedServer 源码内容:

2.1 initEmbedServer 执行器注册

 private EmbedServer embedServer = null;

/**
 address 服务端地址 通过 xxl.job.executor.address 配置
 ip 执行器ip 通过 xxl.job.executor.ip 配置
* port  执行器端口通过 xxl.job.executor.port 配置
* appname 执行器名称通过 xxl.job.executor.appname 配置
*  accessToken 与服务端的访问令牌通过 xxl.job.accessToken 配置
**/
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {

   // fill ip 如果 端口大于0 则取配置的端口,如果小于等于0 则获取可用的端口
    port = port>0?port: NetUtil.findAvailablePort(9999);
    // 如果没有配置ip 则获取一个ip
    ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();

    // generate address  执行器地址是空的 则使用ip 和port 进行拼接
    if (address==null || address.trim().length()==0) {
        String ip_port_address = IpUtil.getIpPort(ip, port);   // registry-address:default use address to registry , otherwise use ip:port if address is null
        address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
    }

    // accessToken
    if (accessToken==null || accessToken.trim().length()==0) {
        logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
    }

    // start 实例化 EmbedServer 对象
    embedServer = new EmbedServer();
    // start 初始化
    embedServer.start(address, port, appname, accessToken);
}

2.2 start 初始化工作:

private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);

// 声明 ExecutorBiz  对象
private ExecutorBiz executorBiz;
// 声明执行器新城
private Thread thread;

public void start(final String address, final int port, final String appname, final String accessToken) {
	// 实例化 ExecutorBizImpl
    executorBiz = new ExecutorBizImpl();
    thread = new Thread(new Runnable() {

        @Override
        public void run() {

            // param  netty 服务声明
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            // 接收服务端请求的线程池 创建
            ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                    0,
                    200,
                    60L,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>(2000),
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
                        }
                    },
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                        }
                    });


            try {
                // start server
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel channel) throws Exception {
                                channel.pipeline()
                                		// 注册空闲状态监测IdleStateHandler
                                        .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                                        // 对数据进行编解码
                                        .addLast(new HttpServerCodec())
                                        // 将 HTTP 消息的多个部分合并成一个完整的消息体
                                        .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                                        // 对服务端请求处理
                                        .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                            }
                        })
                        .childOption(ChannelOption.SO_KEEPALIVE, true);

                // bind
                ChannelFuture future = bootstrap.bind(port).sync();

                logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

                // start registry 注册执行器
                startRegistry(appname, address);

                // wait util stop 启动netty
                future.channel().closeFuture().sync();

            } catch (InterruptedException e) {
                if (e instanceof InterruptedException) {
                    logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                } else {
                    logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
                }
            } finally {
                // stop
                try {
                	// 关闭netty 线程
                    workerGroup.shutdownGracefully();
                    bossGroup.shutdownGracefully();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }

        }

    });
    thread.setDaemon(true);	// daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
    thread.start();
}

2.3 startRegistry 执行器的注册:

2.3.1 注册和移除执行器:

public void startRegistry(final String appname, final String address) {
    // start registry
    ExecutorRegistryThread.getInstance().start(appname, address);
}

ExecutorRegistryThread 类及方法介绍:

public class ExecutorRegistryThread {
	// 实例化log 对象
    private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
	// ExecutorRegistryThread  实例化
    private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
    public static ExecutorRegistryThread getInstance(){
        return instance;
    }
	// 声明注册线程
    private Thread registryThread;
    private volatile boolean toStop = false;
    public void start(final String appname, final String address){

        // valid 执行器名称 为空 不进行注册
        if (appname==null || appname.trim().length()==0) {
            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, appname is null.");
            return;
        }
        // 服务端的地址为空,不进行注册
        if (XxlJobExecutor.getAdminBizList() == null) {
            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
            return;
        }

        registryThread = new Thread(new Runnable() {
            @Override
            public void run() {

                // registry
                while (!toStop) {
                    try {
                    	// 执行器对象RegistryParam  封装 ,EXECUTOR 为自动注册
                    	// appname 执行器名称
                    	// address 执行器地址
                        RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                        	// 遍历 向每个服务端都发送注册信息
                            try {
                            	// 注册并得到结果
                                ReturnT<String> registryResult = adminBiz.registry(registryParam);
                                if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                	// 注册成功
                                    registryResult = ReturnT.SUCCESS;
                                    logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                    break;
                                } else {
                                	// 注册失败
                                    logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                }
                            } catch (Exception e) {
                                logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
                            }

                        }
                    } catch (Exception e) {
                        if (!toStop) {
                        	// 容器停止 日志输出
                            logger.error(e.getMessage(), e);
                        }

                    }

                    try {
                        if (!toStop) {
                        	// 睡 30 s
                            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                        }
                    } catch (InterruptedException e) {
                        if (!toStop) {
                            logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
                        }
                    }
                }

                // registry remove 执行器容器停止向服务端发送移除 执行器请求
                try {
                    RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                        try {
                            ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
                            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                registryResult = ReturnT.SUCCESS;
                                logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                break;
                            } else {
                                logger.info(">>>>>>>>>>> xxl-job registry-remove fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                            }
                        } catch (Exception e) {
                            if (!toStop) {
                                logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
                            }

                        }

                    }
                } catch (Exception e) {
                    if (!toStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, executor registry thread destroy.");

            }
        });
        // 线程 启动
        registryThread.setDaemon(true);
        registryThread.setName("xxl-job, executor ExecutorRegistryThread");
        registryThread.start();
    }
	// 容器停止,则释放资源
    public void toStop() {
    	// 重置标识使得 registryThread 跳出 循环
        toStop = true;

        // interrupt and wait
        if (registryThread != null) {
            registryThread.interrupt();
            try {
            	// 等待registryThread 线程任务完成
                registryThread.join();
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
        }

    }

}

2.3.2 注册和移除执行器请求发送:

1) 注册执行器:

@Override
public ReturnT<String> registry(RegistryParam registryParam) {
    return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);
}

2) 移除执行器:

@Override
public ReturnT<String> registryRemove(RegistryParam registryParam) {
   return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class);
}

2.4 EmbedHttpServerHandler 任务执行:

2.4.1 channelRead0 接收服务端请求:

 @Override
 protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {

      // request parse
      //final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());    // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
      // 请求信息获取
      String requestData = msg.content().toString(CharsetUtil.UTF_8);
      String uri = msg.uri();
      HttpMethod httpMethod = msg.method();
      boolean keepAlive = HttpUtil.isKeepAlive(msg);
      // 请求头访问令牌获取
      String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);

      // invoke  ThreadPoolExecutor bizThreadPool  线程池进行任务处理
      bizThreadPool.execute(new Runnable() {
          @Override
          public void run() {
              // do invoke 处理请求并得到结果
              Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);

              // to json 将结果转为json 字符串
              String responseJson = GsonTool.toJson(responseObj);

              // write response 通过netty 写回信息
              writeResponse(ctx, keepAlive, responseJson);
          }
      });
  }

2.4.2 process 处理请求:

private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {

    // valid 请求方式检验
    if (HttpMethod.POST != httpMethod) {
        return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
    }
    // 请求地址
    if (uri==null || uri.trim().length()==0) {
        return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
    }
    // token 校验
    if (accessToken!=null
            && accessToken.trim().length()>0
            && !accessToken.equals(accessTokenReq)) {
        return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
    }

    // services mapping
    try {
    	// 如果是服务端向执行器发送心态信息 则返回SUCCESS_CODE = 200;
        if ("/beat".equals(uri)) {
            return executorBiz.beat();
        } else if ("/idleBeat".equals(uri)) {
        	// 如果是当前任务 的执行器队列是否有任务请求,则返回改任务队列的空闲情况,SUCCESS_CODE = 200; 
        	// 否则返回FAIL_CODE = 500
            IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
            return executorBiz.idleBeat(idleBeatParam);
        } else if ("/run".equals(uri)) {
        	// 如果是触发任务执行 则调用run 方法去执行任务
            TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
            return executorBiz.run(triggerParam);
        } else if ("/kill".equals(uri)) {
        	// 如果是杀死任务则调用  kill 方法
            KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
            return executorBiz.kill(killParam);
        } else if ("/log".equals(uri)) {
        	// 如果是任务的执行器情况则调用log 请求
            LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
            return executorBiz.log(logParam);
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
        return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
    }
}
2.4.2.1 心跳 beat():
 @Override
public ReturnT<String> beat() {
	// code  = 200; content 为null
    return ReturnT.SUCCESS;
}
2.4.2.2 任务队列空闲idleBeat:
@Override
public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam) {

    // isRunningOrHasQueue
    boolean isRunningOrHasQueue = false;
    // 通过任务id 得到改任务执行的线程
    JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId());
    // 判断线程是否存在及是否有任务
    if (jobThread != null && jobThread.isRunningOrHasQueue()) {
        isRunningOrHasQueue = true;
    }

    if (isRunningOrHasQueue) {
     // JobThread  线程队列有任务存在 则返回 code  = 500  content 为job thread is running or has trigger queue
        return new ReturnT<String>(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.");
    }
    // 任务空闲 返回code  = 200; content 为null
    return ReturnT.SUCCESS;
}
2.4.2.3 任务执行run 方法:

由于任务执行的内容较多,放入下面的文章进行介绍:

2.4.2.4 杀掉任务kill 方法:
@Override
public ReturnT<String> kill(KillParam killParam) {
     // kill handlerThread, and create new one
     // 通过任务id 获取到执行任务的 JobThread  线程
     JobThread jobThread = XxlJobExecutor.loadJobThread(killParam.getJobId());
     if (jobThread != null) {
     	//  线程不为空 
     	// 从ConcurrentMap<Integer, JobThread> jobThreadRepository  注册的线程移除
         XxlJobExecutor.removeJobThread(killParam.getJobId(), "scheduling center kill job.");
         return ReturnT.SUCCESS;
     }

     return new ReturnT<String>(ReturnT.SUCCESS_CODE, "job thread already killed.");
 }

removeJobThread:

 public static JobThread removeJobThread(int jobId, String removeOldReason){
  JobThread oldJobThread = jobThreadRepository.remove(jobId);
    if (oldJobThread != null) {
        oldJobThread.toStop(removeOldReason);
        oldJobThread.interrupt();

        return oldJobThread;
    }
    return null;
}
2.4.2.5 任务的执行器情况log 方法:
@Override
public ReturnT<LogResult> log(LogParam logParam) {
     // log filename: logPath/yyyy-MM-dd/9999.log 
     // 获取log 文件路径
     String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId());
	// 读取log 日志为LogResult 对象
     LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum());
     // 返回日志结果
     return new ReturnT<LogResult>(logResult);
 }

三、扩展:

3.1 IdleStateHandler 作用:

IdleStateHandler 是 Netty 中提供的一个用于处理空闲状态的处理器,主要用于在连接空闲时发送特定的事件和处理相应的逻辑。它是 Netty 的一个组件,通常用于网络通信中实现心跳检测、断线重连等功能。

具体来说,IdleStateHandler 的作用包括以下几个方面:

  1. 空闲状态检测IdleStateHandler 可以检测连接的空闲状态,包括读空闲(即在一定时间内未收到数据)、写空闲(即在一定时间内未发送数据)、读写空闲(即在一定时间内未收到或发送数据)等状态。

  2. 发生空闲状态时触发事件:当连接进入空闲状态时,IdleStateHandler 会触发特定的事件,可以通过重写相应的方法来处理这些事件,例如发送心跳包、关闭连接等。

  3. 设置空闲状态检测时间IdleStateHandler 可以设置空闲检测的时间间隔,即在多长时间内判断连接是否处于空闲状态,默认为 0 表示不会进行空闲检测。

  4. 心跳检测机制IdleStateHandler 常用于实现心跳检测机制,当连接进入空闲状态时发送心跳包,以保持连接的活跃状态,防止连接被关闭。

  5. 断线重连:通过在空闲状态下判断连接是否断开,可以触发断线重连逻辑,重新建立与服务器的连接。

总的来说,IdleStateHandler 在 Netty 中的作用是用于处理连接的空闲状态,触发特定事件以执行相应的业务逻辑,如心跳检测、连接维持等功能。使用IdleStateHandler可以帮助实现网络通信中的稳定性和可靠性,确保连接的正常运行和数据传输的可靠性。

3.2 HttpServerCodec作用:

HttpServerCodec 是 Netty 中提供的一个组合编解码器(codec),用于在 HTTP 通信中处理 HTTP 请求和响应的编解码工作。它是一个组合了HttpRequestDecoderHttpResponseEncoder的编解码器,用于处理 HTTP 协议的编解码。

具体来说,HttpServerCodec 的作用包括以下几个方面:

  1. HTTP 请求解码HttpServerCodec 负责将从客户端接收到的 HTTP 请求进行解码,将原始的字节数据解析为FullHttpRequest对象,方便后续业务逻辑处理。

  2. HTTP 响应编码HttpServerCodec 负责将服务器端的 HTTP 响应信息进行编码,将FullHttpResponse对象编码为字节数据后发送给客户端。

  3. 处理 HTTP 消息的完整性HttpServerCodec 能够保证接收到的 HTTP 消息的完整性,包括报文头部和消息体的完整性。

  4. 支持 HTTP 协议的解析HttpServerCodec 内部会处理 HTTP 协议相关的头部解析、消息体解析、不同 HTTP 方法的支持(GET、POST等)等。

  5. 简化 HTTP 服务器的开发:通过使用HttpServerCodec,开发人员无需关心具体的 HTTP 请求和响应的编解码细节,能够更加专注于业务逻辑的处理。

总的来说,HttpServerCodec 在 Netty 中的作用是处理 HTTP 请求和响应的编解码工作,简化了开发人员在构建 HTTP 服务器时的编解码处理,同时保证了 HTTP 消息的完整性和正确性。通过使用HttpServerCodec,可以更轻松地实现基于 HTTP 协议的通信。

3.2 HttpObjectAggregator作用:

HttpObjectAggregator 是 Netty 中提供的一个聚合器(Aggregator),用于将 HTTP 消息的多个部分合并成一个完整的消息体。它通常在处理 HTTP 请求过程中的编解码器链中使用,主要用于处理 HTTP 请求和响应中的分段消息。

具体来说,HttpObjectAggregator 的作用包括以下几个方面:

  1. 聚合 HTTP 消息HttpObjectAggregator 负责将 HTTP 消息的多个部分(如消息头、消息体等)聚合成一个完整的FullHttpMessage对象,以便后续处理器对整个消息进行处理。

  2. 处理分块传输编码:当客户端发送的请求使用分块传输编码(Chunked Transfer Encoding)时,HttpObjectAggregator 能够将多个分块消息合并成一个完整的消息体,方便后续处理器处理。

  3. 限制消息大小HttpObjectAggregator 可以设置最大消息大小限制,防止因为过大的消息导致内存溢出或其他问题。

  4. 增加性能:通过聚合 HTTP 消息,减少了在处理 HTTP 请求和响应中的编解码过程中的处理次数,提升了性能。

  5. 简化处理逻辑:使用HttpObjectAggregator简化了开发人员对分段消息的处理逻辑,避免了对消息的拆分和合并处理。

总的来说,HttpObjectAggregator 在 Netty 中的作用是将 HTTP 消息的多个部分聚合成一个完整的消息体,方便后续处理器处理。通过使用HttpObjectAggregator,可以提升 HTTP 请求处理的效率,简化开发过程,并确保消息的完整性和正确性。


总结

本文对执行器注册&任务执行 的源码内容进行介绍。

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值