前面讲了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);

分布式任务调度系统xxl-job源码探究(一、客户端)的更多相关文章

  1. 基于nginx+xxl-job+springboot高可用分布式任务调度系统

    技术.原理讲解: <分布式任务调度平台XXL-JOB--源码解析一:项目介绍> <分布式任务调度平台XXL-JOB--源码解析二:基于docker搭建admin调度中心和execut ...

  2. Vue源码探究-事件系统

    Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...

  3. Vue源码探究-状态初始化

    Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...

  4. Vue源码探究-源码文件组织

    Vue源码探究-源码文件组织 源码探究基于最新开发分支,当前发布版本为v2.5.17-beta.0 Vue 2.0版本的大整改不仅在于使用功能上的优化和调整,整个代码库也发生了天翻地覆的重组.可见随着 ...

  5. Vue源码探究-数据绑定的实现

    Vue源码探究-数据绑定的实现 本篇代码位于vue/src/core/observer/ 在总结完数据绑定实现的逻辑架构一篇后,已经对Vue的数据观察系统的角色和各自的功能有了比较透彻的了解,这一篇继 ...

  6. 分布式任务调度系统:xxl-job

    任务调度,通俗来说实际上就是"定时任务",分布式任务调度系统,翻译一下就是"分布式环境下定时任务系统". xxl-job一个分布式任务调度平台,其核心设计目标是 ...

  7. spring-cloud-sleuth+zipkin源码探究

    1. spring-cloud-sleuth+zipkin源码探究 1.1. 前言   粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪, ...

  8. spring-boot-2.0.3之quartz集成,数据源问题,源码探究

    前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...

  9. Linux 系统下用源码包安装软件

    Linux系统下用源码包安装软件 by:授客 QQ:1033553122 下载源码安装包,解压或者直接双击打开(如果有安装zip或rar等压缩/解压缩软件的话),查找相关的安装说明文件,一般是READ ...

  10. Vue源码探究-全局API

    Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...

随机推荐

  1. String、StringBuffer、StringBuilder区别

    String.StringBuffer.StringBuilder区别 StringBuffer.StringBuilder和String一样,也用来代表字符串.String类是不可变类,任何对Str ...

  2. oralce 中union 和union all 的简单使用说明

    union:对两个结果集进行合并操作:不包括重复行:同时进行默认规则的排序: union all:对两个结果集进行合并操作:包括重复行:不排序:

  3. “AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (下)

    在前一部分的最后,我们给出了一个寻路的示例,在大多数情况下,运行还算良好,但是有一个小问题,如下图: 很明显,障碍物已经把路堵死了,但是小球仍然穿过对角线跑了出来! 问题在哪里:我们先回顾一下ASta ...

  4. 【笔记】计算机原理,网络,Linux操作系统

    一,计算机原理 1,化繁为简的思想,产生二进制,产生把所有计算归结为加法运算 二,网络 1,分层思想,产生ISO七层协议,商用了TCP/IP的五层协议 三,Linux操作系统 1,分层思想,硬件-&g ...

  5. java集合框架(1) hashMap 简单使用以及深度分析(转)

    java.util 类 HashMap<K,V>java.lang.Object  java.util.AbstractMap<K,V>      java.util.Hash ...

  6. Install_WordPress_In_CentOS_7

    1 – Install Apache Http Server# yum install httpd.x86_64 2 – Install php# yum install php.x86_64 3 – ...

  7. [leetcode]12. Integer to Roman整数转罗马数字

    Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M. Symbol Value I 1 ...

  8. springboot与缓存(redis,或者caffeine,guava)

    1.理论介绍 Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry. CachingProvide ...

  9. Bootstrap+PHP fileinput 实现多图上传 这是ajax上传,只能单张单张图片地上传

    插件及源代码可以在这里下载 http://www.jq22.com/jquery-info5231下面是根据下载的demo进行补充:使用bootstrap界面美观,可预览,可拖拽上传,可配合ajax异 ...

  10. javaweb开发.常用的第三方包

      序号 开发包名称 描述 1 dom4j-1.6.1.jar dom4j用于操作XML文件 2 jaxen-1.1-beta-6.jar 用于解析XPath表达式 3 commons-beanuti ...