分布式任务调度平台XXL-JOB源码解析笔记

最近经过一段时间的源码系统学习,写一篇博客总结一下。本文记录并整理了学习XXL-JOB框架的全部过程,要求阅读前对XXL-JOB掌握基础的使用方法和基础原理,可以先参考博主之前的XXL-JOB搭建笔记博客:
https://blog.csdn.net/nannan7777/article/details/107337464?spm=1001.2014.3001.5501

技术栈: spring + spring security + jetty+ quartz + mysql + 自研RPC
环境: idea:2018.3win10maven: 3.6.3jdk:1.8spring cloud:Finchley.RELEASEspring boot: 2.0.8quartz-2.2.3xxl-job:2.0.1xxl-rpc: 1.4.0

XXL-JOB 是一个轻量级分布式任务调度框架,主要基于spring quartz 框架搭建,它的核心设计理念是把任务调度分为两个核心部分:调度中心(xxl-admin)执行器(executor),隔离成两个部分。这是一种中心化的设计,由调度中心来统一管理和调度各个接入的业务模块(执行器)。

接入的执行器只需要接收调度信号,然后去执行具体的业务逻辑,两者可以各自的进行扩容,采用自研RPC协议调用来实现执行器的注册和任务的调度,整体框架原理如图所示:


下面对XXL-JOB框架源码进行解析,按照使用逻辑顺序从如下三个方面进行总结:

一、 执行器注册至任务调度中心

XXL-JOB框架添加执行器到任务调度中心有两种方式:
(1)客户端执行器自动将名称和机器地址注册到任务调度中心
(2)在任务调度中心手动录入执行器名称和相关的机器地址(多个机器地址用逗号隔开)

下面对自动注册流程进行源码实现过程总结:
首先在在执行器客户端配置执行器名称和任务调度中心地址:

### 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 executor address
##任务调度器名称和机器信息
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=127.0.0.1
xxl.job.executor.port=9998

执行器注册至任务调度中心的逻辑流程如图所示:
在这里插入图片描述


STEP1 执行器注册

在执行器启动时会读取上述配置,当存在任务调度中心地址时,会依次向任务调度中心注册其地址。

XxlJobExecutor类在进行初始化时,通过XxlRpcReferenceBean构建AdminBiz的客户端,通过XxxlJobExecutor#getAdminBizList来获取AdminBiz的Client,调用任务调度中心AdMinBiz的相关方法。
具体代码如下:

private static void initAdminBizList(String adminAddresses, String accessToken) throws Exception {
   
        if (adminAddresses!=null && adminAddresses.trim().length()>0) {
   
            for (String address: adminAddresses.trim().split(",")) {
   
                if (address!=null && address.trim().length()>0) {
   
                    String addressUrl = address.concat(AdminBiz.MAPPING);
                    AdminBiz adminBiz = (AdminBiz) new NetComClientProxy(AdminBiz.class, addressUrl, accessToken).getObject();
                    if (adminBizList == null) {
   
                        adminBizList = new ArrayList<AdminBiz>();
                    }
                    adminBizList.add(adminBiz);
                }
            }
        }
    }

其中,在XxlJobExecutor类被调用时执行getObject()方法,完成向任务调度中心发送请求进行执行器注册的操作。
具体代码如下:

@Override
	public Object getObject() throws Exception {
   
		return Proxy.newProxyInstance(Thread.currentThread()
				.getContextClassLoader(), new Class[] {
    iface },
				new InvocationHandler() {
   
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
 
						// filter method like "Object.toString()"
						if (Object.class.getName().equals(method.getDeclaringClass().getName())) {
   
							logger.error(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", method.getDeclaringClass().getName(), method.getName());
							throw new RuntimeException("xxl-rpc proxy class-method not support");
						}
						
						// request
						RpcRequest request = new RpcRequest();
	                    request.setServerAddress(serverAddress);
	                    request.setCreateMillisTime(System.currentTimeMillis());
	                    request.setAccessToken(accessToken);
	                    request.setClassName(method.getDeclaringClass().getName());
	                    request.setMethodName(method.getName());
	                    request.setParameterTypes(method.getParameterTypes());
	                    request.setParameters(args);
 
	                    // send
						//向任务调度中心发送请求进行服务注册
	                    RpcResponse response = client.send(request);
	                    
	                    // valid response
						if (response == null) {
   
							throw new Exception("Network request fail, response not found.");
						}
	                    if (response.isError()) {
   
	                        throw new RuntimeException(response.getError());
	                    } else {
   
	                        return response.getResult();
	                    }                 
					}
				});

最后,调用send()方法,完成执行器的注册操作。
具体代码如下:

public RpcResponse send(RpcRequest request) throws Exception {
   
		try {
   
			// serialize request
			byte[] requestBytes = HessianSerializer.serialize(request);
 
			// reqURL
			String reqURL = request.getServerAddress();
			if (reqURL!=null && reqURL.toLowerCase().indexOf("http")==-1) {
   
				reqURL = "http://" + request.getServerAddress() + "/";	// IP:PORT, need parse to url
			}
            //发送post请求进行服务注册,简单注册一下IP和端口信息等
			// remote invoke
			byte[] responseBytes = HttpClientUtil.postRequest(reqURL, requestBytes);
			if (responseBytes == null || responseBytes.length==0) {
   
				RpcResponse rpcResponse = new RpcResponse();
				rpcResponse.setError("Network request fail, RpcResponse byte[] is null");
				return rpcResponse;
            }
 
            // deserialize response
			RpcResponse rpcResponse = (RpcResponse) HessianSerializer.deserialize(responseBytes, RpcResponse.class);
			return rpcResponse;
		} catch (Exception e) {
   
			logger.error(e.getMessage(), e);
 
			RpcResponse rpcResponse = new RpcResponse();
			rpcResponse.setError("Network request error: " + e.getMessage());
			return rpcResponse;
		}
	}

总结:STEP1中,执行器注册时,注册到任务调度中心的信息仅仅包括一些基本信息,IP、端口和应用名称等等,并不用将具体的任务类等信息注册到任务调度中心,所以任务调度中心无法感知执行器的具体业务逻辑信息,可以通过添加标识或打印日志详细信息进行监管。


STEP2 任务调度中心接收注册

任务调度中心对外提供注册地址http://127.0.0.1:8080/api,通过JobApiController#api接口用来接受注册执行器相关信息。具体代码如下:

@Controller
public class JobApiController {
   
    private static Logger logger = LoggerFactory.getLogger(JobApiController.class);
 
    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;
        }
    }
 
	//对外提供api接口
    @RequestMapping(AdminBiz.MAPPING)
    @PermessionLimit(limit=false)
    public void api(HttpServletRequest request, HttpServletResponse response) throws IOException {
   
 
        // invoke
        RpcResponse rpcResponse = doInvoke(request);
 
        // serialize response
        byte[] responseBytes = HessianSerializer.serialize(rpcResponse);
 
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        //baseRequest.setHandled(true);
 
        OutputStream out = response.getOutputStream();
        out.write(responseBytes);
        out.flush();
    }
}

其中,在NetComServerFactory类中调用invokeService()方法,根据反射调用AdminBiz接口,实现AdminBizImpl类register()方法,完成执行器注册操作。
具体代码如下:

public static RpcResponse invokeService(RpcRequest request, Object serviceBean) {
   
		if (serviceBean==null) {
   
			serviceBean = serviceMap.get(request.getClassName());
		}
		if (serviceBean == null) {
   
			// TODO
		}
 
		RpcResponse response = new RpcResponse();
 
		if (System.currentTimeMillis() - request.getCreateMillisTime() > 180000) {
   
			response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The timestamp difference between admin and executor exceeds the limit."));
			return response;
		}
		if (accessToken!=null && accessToken.trim().length()>0 && !accessToken.trim().equals(request.getAccessToken())) {
   
			response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The access token[" + request.getAccessToken() + "] is wrong."));
			return response;
		}
 
		try {
   
			//接口AdminBiz的实现类AdminBizImpl
			Class<?> serviceClass = serviceBean.getClass();
			//AdminBiz的register方法
			String methodName = request.getMethodName();
			Class<?>[] parameterTypes = request.getParameterTypes();
			Object[] parameters = request.getParameters();
 
			FastClass serviceFastClass = FastClass.create(serviceClass);
			//调用AdminBizImpl的register方法
			FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
 
			Object result = serviceFastMethod.invoke(serviceBean, parameters);
 
			response.setResult(result);
		} catch (Throwable t) {
   
			t.printStackTrace();
			response.setError(t.getMessage());
		}
 
		return response;
	}

最后,在AdminBiz的实现类AdminBizImpl中,调用dao层完成注册信息的入库操作。
具体代码如下:

@Override
public ReturnT<String> registry(RegistryParam registryParam) {
   
		//在xxl_job_qrtz_trigger_registry表中添加或更新数据
        int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
        if (ret < 1) {
   
            xxlJobRegistryDao.registrySave(registryParam.getRegistGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
        }
        return ReturnT.SUCCESS;
}

此时表中数据如下所示:
在这里插入图片描述
此时在任务调度中心的前端页面中会展示相关执行器数据,如下所示:
在这里插入图片描述

总结:STEP2中,执行器将其服务器信息通过http请求发送至任务调度中心,任务调度中心将相关注册信息插入或更新到xxl_job_qrtz_trigger_registry表中,在任务调度中心的前端页面上通过列表展示,接下来相关任务执行时会根据执行器信息向某个指定执行器发送调用请求。

二、 任务调度中心任务管理

XXL-JOB框架是基于quartz来实现定时任务,其中任务调度中心任务执行基于quartz,任务执行器也是一个被调用执行的服务。

1、任务创建

调用JobInfoControlleradd()方法添加任务。
具体代码如下:

@RequestMapping("/add")
	@ResponseBody
	public ReturnT<
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值