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


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

    客户端源码

    1. 客户端开启的时候会向服务中心进行注册,其实现用的是jetty连接,且每隔半分钟会发送一次心跳,来告诉服务中心该执行器是否正常
    2. 查看源码可以从配置文件入手
    @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方法,进入其中
    3. 可以看到以下代码,英文注释我斗胆翻译下~

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

    国人编写的代码就是有国人自己的风格,至少比国外的开源代码好看懂点
    4. 这里主要深入执行器部分吧,其他还是容易看懂的,继续深入

    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
    }
    
    

    继续深入

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

    继续深入

    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();
    }
    

    可以看出这里主要关注

    ExecutorRegistryThread.getInstance().start(port, ip, appName);
    TriggerCallbackThread.getInstance().start();
    

    这两个方法
    继续深入

    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();
    }
    

    继续

    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();
    }
    
    
    可以看到此方法放入任务
    public static void pushCallBack(HandleCallbackParam callback){
        getInstance().callBackQueue.add(callback);
        logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
    }
    

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

    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服务类中已经加入

    // Set a handler
    HandlerCollection handlerc =new HandlerCollection();
    handlerc.setHandlers(new Handler[]{new JettyServerHandler()});
    server.setHandler(handlerc);
    
  • 相关阅读:
    A Year Of Books
    Spring Boot 之 RESRful API 权限控制
    Git回滚远程版本
    初探设计:Java接口和抽象类何时用?怎么用?
    深入浅出: Java回调机制(异步)
    深入浅出: 大小端模式
    Java IO 之 FileInputStream & FileOutputStream源码分析
    Java IO 之 OutputStream源码
    软件测试--安装软件
    Mybatis 中$与#的区别
  • 原文地址:https://www.cnblogs.com/sky-chen/p/9667340.html
Copyright © 2020-2023  润新知