分布式任务调度系统xxl-job源码探究(一、客户端)

2019-09-10 16:45:05 浏览数 (2)

前面讲了xxl-job的搭建,现在来粗略的解析下该分布式调度系统的源码,先来客户点代码

客户端源码

  1. 客户端开启的时候会向服务中心进行注册,其实现用的是jetty连接,且每隔半分钟会发送一次心跳,来告诉服务中心该执行器是否正常
  2. 查看源码可以从配置文件入手
代码语言:javascript复制
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobExecutor xxlJobExecutor() {
    logger.info(">>>>>>>>>>> xxl-jobhandler config init.");
    XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
    xxlJobExecutor.setAdminAddresses(adminAddresses);
    xxlJobExecutor.setAppName(appName);
    xxlJobExecutor.setIp(ip);
    xxlJobExecutor.setPort(port);
    xxlJobExecutor.setAccessToken(accessToken);
    xxlJobExecutor.setLogPath(logPath);
    xxlJobExecutor.setLogRetentionDays(logRetentionDays);

    return xxlJobExecutor;
}

很明显,在把配置信息注入以后,该配置执行了start方法,进入其中

  1. 可以看到以下代码,英文注释我斗胆翻译下~
代码语言:javascript复制
// ---------------------- start   stop ----------------------
public void start() throws Exception {
    // init admin-client 初始化服务中心
    initAdminBizList(adminAddresses, accessToken);

    // init executor-jobHandlerRepository 
    // 初始化jobHandler也就是继承了该类的所有定时方法,模仿spring ioc,把实例化对象都保存了起来
    initJobHandlerRepository(applicationContext);

    // init logpath 初始化日志文件,设置文件路径
    XxlJobFileAppender.initLogPath(logPath);

    // init executor-server 初始化执行器服务,看参数知道这里就是jetty连接的主要地方了
    initExecutorServer(port, ip, appName, accessToken);

    // init JobLogFileCleanThread 看名字也知道 初始化日志清理线程,应该是用来定时清理日志的
    JobLogFileCleanThread.getInstance().start(logRetentionDays);
}

国人编写的代码就是有国人自己的风格,至少比国外的开源代码好看懂点

  1. 这里主要深入执行器部分吧,其他还是容易看懂的,继续深入
代码语言:javascript复制
private void initExecutorServer(int port, String ip, String appName, String accessToken) throws Exception {
    // valid param 可以看到,我们不配置jetty端口,它默认也是9999
    port = port>0?port: NetUtil.findAvailablePort(9999);

    // start server
    NetComServerFactory.putService(ExecutorBiz.class, new ExecutorBizImpl());   // rpc-service, base on jetty
    NetComServerFactory.setAccessToken(accessToken);
    //主要就是这个方法了
    serverFactory.start(port, ip, appName); // jetty   registry
}

继续深入

代码语言:javascript复制
// ---------------------- server start ----------------------
//可以看到用到了jetty服务
JettyServer server = new JettyServer();
public void start(int port, String ip, String appName) throws Exception {
    server.start(port, ip, appName);
}

继续深入

代码语言:javascript复制
public void start(final int port, final String ip, final String appName) throws Exception {
    thread = new Thread(new Runnable() {
        @Override
        public void run() {

            // The Server
            server = new Server(new ExecutorThreadPool());  // 非阻塞

            // HTTP connector
            ServerConnector connector = new ServerConnector(server);
            if (ip!=null && ip.trim().length()>0) {
                connector.setHost(ip);  // The network interface this connector binds to as an IP address or a hostname.  If null or 0.0.0.0, then bind to all interfaces.
            }
            connector.setPort(port);
            server.setConnectors(new Connector[]{connector});

            // Set a handler
            HandlerCollection handlerc =new HandlerCollection();
            handlerc.setHandlers(new Handler[]{new JettyServerHandler()});
            server.setHandler(handlerc);

            try {
                // Start server
                server.start();
                logger.info(">>>>>>>>>>> xxl-job jetty server start success at port:{}.", port);

                // Start Registry-Server 注册到服务中心方法,单独线程执行
                ExecutorRegistryThread.getInstance().start(port, ip, appName);

                // Start Callback-Server 定时任务回调方法,单独线程执行
                TriggerCallbackThread.getInstance().start();

                server.join();  // block until thread stopped
                logger.info(">>>>>>>>>>> xxl-rpc server join success, netcon={}, port={}", JettyServer.class.getName(), port);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                //destroy();
            }
        }
    });
    thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
    thread.start();
}

可以看出这里主要关注

代码语言:javascript复制
ExecutorRegistryThread.getInstance().start(port, ip, appName);
TriggerCallbackThread.getInstance().start();

这两个方法 继续深入

代码语言:javascript复制
public void start(final int port, final String ip, final String appName){

    // 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;
    }

    // executor address (generate addredd = ip:port)
    final String executorAddress;
    if (ip != null && ip.trim().length()>0) {
        executorAddress = ip.trim().concat(":").concat(String.valueOf(port));
    } else {
        executorAddress = IpUtil.getIpPort(port);
    }

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

            // registry 此线程为守护线程,不销毁,循环执行
            while (!toStop) {
                try {
                    RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, executorAddress);
                    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                        try {
                            ReturnT<String> registryResult = adminBiz.registry(registryParam);
                            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                registryResult = ReturnT.SUCCESS;
                                logger.info(">>>>>>>>>>> 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) {
                    logger.error(e.getMessage(), e);
                }
                // 睡眠30秒
                try {
                    TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                } catch (InterruptedException e) {
                    logger.error(e.getMessage(), e);
                }
            }
            
            // registry remove 服务中心移除此任务起效果
            try {
                RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, executorAddress);
                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) {
                        logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
                    }

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

        }
    });
    registryThread.setDaemon(true);
    registryThread.start();
}

继续

代码语言:javascript复制
public void start() {

    // valid
    if (XxlJobExecutor.getAdminBizList() == null) {
        logger.warn(">>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
        return;
    }

    triggerCallbackThread = new Thread(new Runnable() {

        @Override
        public void run() {

            // normal callback
            while(!toStop){
                try {
                //这里采用了阻塞队列,可以看出,当服务中心发送任务到此队列,就会被消费
                    HandleCallbackParam callback = getInstance().callBackQueue.take();
                    if (callback != null) {

                        // callback list param
                        List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                        int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                        callbackParamList.add(callback);

                        // callback, will retry if error
                        if (callbackParamList!=null && callbackParamList.size()>0) {
                            doCallback(callbackParamList);
                        }
                    }
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }

            // last callback
            try {
                List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                if (callbackParamList!=null && callbackParamList.size()>0) {
                    doCallback(callbackParamList);
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
            logger.info(">>>>>>>>>>> xxl-job, executor callback thread destory.");

        }
    });
    triggerCallbackThread.setDaemon(true);
    triggerCallbackThread.start();
}
代码语言:javascript复制
可以看到此方法放入任务
public static void pushCallBack(HandleCallbackParam callback){
    getInstance().callBackQueue.add(callback);
    logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
}

后面可以追溯好多层级,最顶上发现

代码语言:javascript复制
private RpcResponse doInvoke(HttpServletRequest request) {
    try {
        // deserialize request
        byte[] requestBytes = HttpClientUtil.readBytes(request);
        if (requestBytes == null || requestBytes.length==0) {
            RpcResponse rpcResponse = new RpcResponse();
            rpcResponse.setError("RpcRequest byte[] is null");
            return rpcResponse;
        }
        RpcRequest rpcRequest = (RpcRequest) HessianSerializer.deserialize(requestBytes, RpcRequest.class);

        // invoke 主要就是这个调用了,调用一次会执行一次对应任务
        RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
        return rpcResponse;
    } catch (Exception e) {
        logger.error(e.getMessage(), e);

        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setError("Server-error:"   e.getMessage());
        return rpcResponse;
    }
}

而这个对象实际在JettyServer服务类中已经加入

代码语言:javascript复制
// Set a handler
HandlerCollection handlerc =new HandlerCollection();
handlerc.setHandlers(new Handler[]{new JettyServerHandler()});
server.setHandler(handlerc);

0 人点赞