源码地址:https://github.com/xuxueli/xxl-job
主要表:任务信息表、任务执行流水表、执行器信息表、执行器注册表
总体流程
启动机器注册监控线程:每30秒清理一次注册表中的无效机器(admin/executor);
启动失败重试监控线程:没10秒检测失败任务,报警和重试;
initRpcProvider:初始化xxlRpcProviderFactory注册服务adminBiz用于响应执行器的注册请求,并将其作为ServletServerHandler的成员变量。当调用JobApiController的api方法是会调用XxlJobScheduler.invokeAdminService(request, response),进一步调用ServletServerHandler的handle方法,handle方法判断如果是服务匹配的请求直接将本服务器信息返回,如果是调用方的服务请求则解析请求参数调用xxlRpcProviderFactory.invokeService,xxlRpcProviderFactory.invokeService通过服务key找到相应的服务类实际就是adminBiz,调用请求中要求的方法来获取返回数据;
开启调度:JobScheduleHelper.getInstance().start(),下面是调度相关的介绍。
调度线程定时5秒执行一次
1. 超时未调度(超过调度时间5秒)的任务不再执行,修改下次执行时间。
2. 超过调度时间但未超时(超过5秒之内)的任务,立即放入执行线程池,再修改执行时间,接着判断下次执行时间若在5秒之内,加入timewheel的map后再次修改下次执行时间。
3. 调度时间在未来5秒之内的(预读5s),基于timewheel时间轮(map<秒数,list<任务实体>>),根据5秒内即将执行的任务的执行时间的秒数,将其放到timeheel对应秒数的list中,修改下次执行时间。
时间轮执行线程定时每1秒执行一次
1. 删除并取出当前秒数的list和前一秒的list立即放入执行线程池。(往前取1秒防止前1秒的任务未执行,比如当前秒数是59,如果57的任务执行时间大于1秒,可能58的任务就没有被执行过,所以59秒的时候取58和59的任务防止这种情况)。
将1分钟内任务执行超时(超过500ms视为超时)数量记录在Map
快线程池:1分钟内未超时,或超时次数少于10的任务
慢线程池:1分钟为超时次数大于等于10的任务
1. 第一个:addressList.get(0)
2. 最后一个:addressList.get(addressList.size()-1)
3. 轮询:ConcurrentMap
4. 随机:addressList.get(localRandom.nextInt(addressList.size()))
5. 一致性HASH:将执行器地址映射到0-2^31的数,组成一个map<执行器hash,执行器地址>;再将jobid映射到0-2^31的数,取map中顺时针与jobid的hash最接近的执行器hash,去除执行器地址。为防止执行器节点数太少导致数据倾斜的问题(如图中node4没有的话,数据会倾斜到node3),将每个执行器变成5个虚拟节点(比如node1变成node1-0,node1-1,node1-2……)
6. 最不经常使用:ConcurrentMap
7. 最近最久未使用:ConcurrentMap
8. 故障转移:按照顺序依次发送beat信息,检测到故障跳过,检测到正常的返回
9. 忙碌转移:按照顺序依次发送idlebeat进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度
10. 分片广播:广播触发对应集群中所有执行器执行一次任务,并将分片参数(当前分片序号,总结点数)传给执行器,执行器根据分片参数进行分片业务处理。
新代理的生成过程
通过XxlRpcReferenceBean.getObject()生成代理对象(保存在ConcurrentMap<地址, ExecutorBiz代理对象>),并重写invoke方法。
invoke方法:1. 当执行器路由策略中addressList为空的时候,初始化XxlRpcReferenceBean的时候并无法获取到服务地址,所以invoke中判断地址为空时,通过servicekey去注册中心discover具体的服务地址,根据XxlRpcReferenceBean设置的负载均衡策略(随机、轮询、LRU、LFU、一致性hash)找到服务address。2. 发送xxlRpcRequest。3. 根据XxlRpcReferenceBean设置的响应类型(同步、异步future、回调、单向)返回结果。
响应处理方式:
同步方式:设置一个XxlRpcFutureResponse,将XxlRpcInvokerFactory作为其成员变量,在XxlRpcInvokerFactory对象中使用ConcurrentMap<请求id, XxlRpcFutureResponse> 将请求id与future对应,阻塞当前线程,直到future获取到返回结果或超时。
异步方式:同样设置一个XxlRpcFutureResponse,将XxlRpcInvokerFactory作为其成员变量,在XxlRpcInvokerFactory对象中使用ConcurrentMap<请求id, XxlRpcFutureResponse> 将请求id与future对应,将XxlRpcFutureResponse作为成员变量放入一个XxlRpcInvokeFuture对象,再将XxlRpcInvokeFuture作为线程独有变量,在invoke方法中直接返回null。(之后调用方可以去处理其他业务,最后调用方再从线程独有变量中,取出XxlRpcInvokeFuture,调用XxlRpcInvokeFuture的get方法取出XxlRpcFutureResponse成员变量中的真正返回值。)
回调方式:在构建代理的时候设置一个XxlRpcInvokeCallback对象,或是构建代理之后通过XxlRpcInvokeCallback的线程独有变量将回调处理方式——一个XxlRpcInvokeCallback对象封装在里面。同样设置一个XxlRpcFutureResponse,将XxlRpcInvokerFactory作为其成员变量,并将XxlRpcInvokeCallback对象作为成员变量保存在XxlRpcFutureResponse中,在XxlRpcInvokerFactory对象中使用ConcurrentMap<请求id, XxlRpcFutureResponse> 将请求id与future对应,然后在invoke方法中直接返回null。
单向方式:只发出,不做其他处理。
jetty或netty或nettyhttp或其他通讯协议的客户端处理器监听到有数据返回的时候,调用XxlRpcInvokerFactory的方法从map中用requestid取出XxlRpcFutureResponse对象,如果XxlRpcFutureResponse对象有回调处理则处理回调,否则将返回数据set到XxlRpcFutureResponse对象。
总体流程
在执行器服务启动的时候,将IJobHandler实现类的对象注册到XxlJobExecutor,再调用XxlJobExecutor的start方法启动,启动过程包含初始化日志路径、创建代理对象(保存List
初始化xxlRpcProviderFactory
启动注册线程,注册当前服务器
启动jetty或netty或nettyhttp或其他通讯协议的服务端,定义一个线程池作为NettyHttpServerHandler的成员变量,将xxlRpcProviderFactory作为NettyHttpServerHandler的成员变量
当jetty或netty或nettyhttp或其他通讯协议的服务端处理器监听到请求时,判断如果是服务匹配的请求直接将本服务器信息返回,如果是调用方的服务请求则解析请求参数调用xxlRpcProviderFactory.invokeService获取放回数据。
xxlRpcProviderFactory.invokeService
xxl-job中invoke会调用ExecutorBizImpl.run来处理请求。ExecutorBizImpl.run处理过程如下:
找到对应的IJobHandler实例,通过jobid找到对应的jobThread,如果没有找到,则创建新的jobThread保存
ConcurrentMap