分布式任务调度系统xxl-job源码探究(一、客户端)
前面讲了xxl-job的搭建,现在来粗略的解析下该分布式调度系统的源码,先来客户点代码
客户端源码
- 客户端开启的时候会向服务中心进行注册,其实现用的是jetty连接,且每隔半分钟会发送一次心跳,来告诉服务中心该执行器是否正常
- 查看源码可以从配置文件入手
@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源码探究(一、客户端)的更多相关文章
- 基于nginx+xxl-job+springboot高可用分布式任务调度系统
技术.原理讲解: <分布式任务调度平台XXL-JOB--源码解析一:项目介绍> <分布式任务调度平台XXL-JOB--源码解析二:基于docker搭建admin调度中心和execut ...
- Vue源码探究-事件系统
Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
- Vue源码探究-源码文件组织
Vue源码探究-源码文件组织 源码探究基于最新开发分支,当前发布版本为v2.5.17-beta.0 Vue 2.0版本的大整改不仅在于使用功能上的优化和调整,整个代码库也发生了天翻地覆的重组.可见随着 ...
- Vue源码探究-数据绑定的实现
Vue源码探究-数据绑定的实现 本篇代码位于vue/src/core/observer/ 在总结完数据绑定实现的逻辑架构一篇后,已经对Vue的数据观察系统的角色和各自的功能有了比较透彻的了解,这一篇继 ...
- 分布式任务调度系统:xxl-job
任务调度,通俗来说实际上就是"定时任务",分布式任务调度系统,翻译一下就是"分布式环境下定时任务系统". xxl-job一个分布式任务调度平台,其核心设计目标是 ...
- spring-cloud-sleuth+zipkin源码探究
1. spring-cloud-sleuth+zipkin源码探究 1.1. 前言 粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪, ...
- spring-boot-2.0.3之quartz集成,数据源问题,源码探究
前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...
- Linux 系统下用源码包安装软件
Linux系统下用源码包安装软件 by:授客 QQ:1033553122 下载源码安装包,解压或者直接双击打开(如果有安装zip或rar等压缩/解压缩软件的话),查找相关的安装说明文件,一般是READ ...
- Vue源码探究-全局API
Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...
随机推荐
- unity编程心得
1. 不要通过public变量 从工程面板 直接 拖 GameObjct 的引用, 当这样的public变量很多 ,子物体很多,又没有做成预制体,,别人重新移植这段功能会很麻烦,,应该用GameOb ...
- JQuery的可见性选择器
1. <div id="test" style="width:400px;height:200; background:#0000ff;display:block; ...
- djjango models表关系
*** detail表级联 student表,detail获取student的学生信息,自己做为扩展 一对一关系 models.OneToOneField student = models.OneTo ...
- MySQL经典练习题
表名和字段 –1.学生表 Student(s_id,s_name,s_birth,s_sex) –学生编号,学生姓名, 出生年月,学生性别 –2.课程表 Course(c_id,c_name,t_id ...
- 记录----第一次使用BFS(广度搜索)学习经验总结
学习经验记录与分享—— 最近在学习中接触到了一种解决最短路径的实用方法----BFS(广度搜索),在这里总结并分享一下第一次学习的经验. 首先第一个要了解的是"queue"(队列函 ...
- sqlserver2017 重装过程中出现“无法找到数据库引擎启动句柄”错误的解决办法
sqlserver数据库引擎修改账号名,详情参考:http://blog.51cto.com/djclouds/2089047?utm_source=oschina-app 在SQL Server安装 ...
- ASP.NET CORE 2.0 发布到IIS,IIS如何设置环境变量来区分生产环境和测试环境
0.前言 因为给前端的测试环境是windows,所以要设置windows上的环境变量,如果上Linux就没有这篇文章了,所以大家不要在意为什么core不放在linux上. 1.网上的解决方案 a 方式 ...
- 循序渐进VBA EXCEL数据操作小实例
1 向指定单元格区域内写入数据 Sub example1() ) arr() = Array("A", "B", "C", "D& ...
- Boost::bind使用详解
1.Boost::bind 在STL中,我们经常需要使用bind1st,bind2st函数绑定器和fun_ptr,mem_fun等函数适配器,这些函数绑定器和函数适配器使用起来比较麻烦,需要根据是全局 ...
- python基础之Day7part2 史上最清晰字符编码理解
二.字符编码 基础知识: 文本编辑器存取文件原理与py执行原理异同: 存/写:进入文本编辑器 写内容 保存后 内存数据刷到硬盘 取/读:进入文本编辑器 找到内容 从硬盘读到内存 notepad把文件内 ...