首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一. 前言
准备花三个篇幅来看看 XXL-JOB 的相关概念 , 这一篇主要看看 Client 端的请求流程 .
二. 使用
使用主要分为2个部分 :
- 继承 IJobHandler 接口 , 实现 execute
- 标注 @JobHandler(value = "testJobHandler") 接口
@JobHandler(value = "testJobHandler")
@Component
@Service
public class TestHandleService extends IJobHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ReturnT<String> execute(String s) throws Exception {
logger.info("param is :{}", s);
return null;
}
}
服务端配置
三. 源码
下面来看一下 Job 源码的处理方式 ,对于 Spring 系列 ,是通过 XxlJobSpringExecutor 作为其执行器使用的 :
3.1 初始化操作
XxlJobSpringExecutor 是通过 ApplicationContextAware 来实现通知的 , 其启动的方法是在配置类中定义的 :
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobSpringExecutor xxlJobExecutor() {
// 设置 AdminAddresses 和 AppName 和 IP 等
}
3.1.1 XxlJobSpringExecutor # start 部分 :
// C- XxlJobSpringExecutor
public void start() throws Exception {
// init JobHandler Repository -> 3.2 JobHandler 扫描
initJobHandlerRepository(applicationContext);
// refresh GlueFactory
// 通过 type 类型创建 GlueFactory 或者 SpringGlueFactory
GlueFactory.refreshInstance(1);
// super start , 调用 XxlJobExecutor.start
super.start();
}
3.1.2 XxlJobExecutor # start 部分 :
// C- XxlJobExecutor
public void start() throws Exception {
// init logpath
// 其中包括准备 logBasePath 和 glueSrcPath 以及 mkdir 创建路径
XxlJobFileAppender.initLogPath(logPath);
// init invoker, admin-client
// Step 1 : adminAddresses.trim().split(",") -> 用逗号分隔集群
// Step 2 : new XxlRpcReferenceBean 构建 AdminBiz
// Step 3 : 添加到 List<AdminBiz> adminBizList 中
initAdminBizList(adminAddresses, accessToken);
// init JobLogFileCleanThread
// 创建一个单独的线程处理 log 文件
JobLogFileCleanThread.getInstance().start(logRetentionDays);
// init TriggerCallbackThread -> 详见下方
TriggerCallbackThread.getInstance().start();
// init executor-server
// 提供 9999 的默认端口 , 以及设置 IP
port = port>0?port: NetUtil.findAvailablePort(9999);
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
//
initRpcProvider(ip, port, appName, accessToken);
}
3.1.3 TriggerCallbackThread 回调处理
作用 : 返回调用结果
创建 : 其中创建了2个线程 :
- triggerCallbackThread
- triggerRetryCallbackThread
public void start() {
triggerCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
// while 死循环 , 直到结束信号
while(!toStop){
// 内部包含一个核心对象 : ReturnT<String> executeResult
// executeResult = code + msg + content
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
// callback list param
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
// LinkedBlockingQueue<HandleCallbackParam> callBackQueue
// 此处从 queue 中一次性获取所有的数据的个数
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
callbackParamList.add(callback);
// callback, will retry if error
if (callbackParamList!=null && callbackParamList.size()>0) {
// 调用 callback
doCallback(callbackParamList);
}
}
}
// last callback
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
logger.info(">>>>>>>>>>> xxl-job, executor callback thread destory.");
}
});
triggerCallbackThread.setDaemon(true);
triggerCallbackThread.setName("xxl-job, executor TriggerCallbackThread");
triggerCallbackThread.start();
// retry
triggerRetryCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
while(!toStop){
retryFailCallbackFile();
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
}
logger.info(">>>>>>>>>>> xxl-job, executor retry callback thread destory.");
}
});
triggerRetryCallbackThread.setDaemon(true);
triggerRetryCallbackThread.start();
}
doCallback
private void doCallback(List<HandleCallbackParam> callbackParamList){
boolean callbackRet = false;
// callback, will retry if error
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
// callback 发起 RPC 远程调用
ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback finish.");
callbackRet = true;
break;
} else {
// 创建 callback log
callbackLog(callbackParamList, "<br>----------- xxl-job job callback fail, callbackResult:" + callbackResult);
}
}
if (!callbackRet) {
appendFailCallbackFile(callbackParamList);
}
}
3.1.4 initRpcProvider 注册服务
// C- XxlJobExecutor
private void initRpcProvider(String ip, int port, String appName, String accessToken) throws Exception {
// init, provider factory
String address = IpUtil.getIpPort(ip, port);
// 准备远程调用属性
Map<String, String> serviceRegistryParam = new HashMap<String, String>();
serviceRegistryParam.put("appName", appName);
serviceRegistryParam.put("address", address);
// 初始化配置信息 , 包括使用 NettyHttpServer , 序列化方式 , ExecutorServiceRegistry
// NetEnum 包括四种常见的 :
// - NettyServer / NettyClient
// - NettyHttpServer / NettyHttpClient
// - MinaServer / MinaClient
// - JettyServer / JettyClient
xxlRpcProviderFactory = new XxlRpcProviderFactory();
xxlRpcProviderFactory.initConfig(NetEnum.NETTY_HTTP, Serializer.SerializeEnum.HESSIAN.getSerializer(), ip, port, accessToken, ExecutorServiceRegistry.class, serviceRegistryParam);
// add services -> 加入 Map<String, Object> serviceData
xxlRpcProviderFactory.addService(ExecutorBiz.class.getName(), null, new ExecutorBizImpl());
// start -> Service 的注册
xxlRpcProviderFactory.start();
}
远程注册 Service
// C-
public void start() throws Exception {
// start server
serviceAddress = IpUtil.getIpPort(this.ip, port);
server = netType.serverClass.newInstance();
server.setStartedCallback(new BaseCallback() { // serviceRegistry started
@Override
public void run() throws Exception {
// start registry
if (serviceRegistryClass != null) {
// 构建 serviceRegistry 用于注册
serviceRegistry = serviceRegistryClass.newInstance();
// ExecutorRegistryThread.getInstance().start(param.get("appName"), param.get("address"));
// -> 3.3 发起实际注册流程 -> ExecutorServiceRegistry
serviceRegistry.start(serviceRegistryParam);
if (serviceData.size() > 0) {
serviceRegistry.registry(serviceData.keySet(), serviceAddress);
}
}
}
});
// 注册停止回调
server.setStopedCallback(new BaseCallback() { // serviceRegistry stoped
@Override
public void run() {
// stop registry
if (serviceRegistry != null) {
if (serviceData.size() > 0) {
// 移除加停止
serviceRegistry.remove(serviceData.keySet(), serviceAddress);
}
// registryThread.interrupt();
// registryThread.join();
serviceRegistry.stop();
serviceRegistry = null;
}
}
});
// 开启线程处理
server.start(this);
}
Server 和 ServiceRegistry 类结构
3.2 注解的扫描
在 XxlJobSpringExecutor# initJobHandlerRepository 中对所有的 jobHandler 进行了扫描
private void initJobHandlerRepository(ApplicationContext applicationContext){
// 通过 applicationContext 工具获取所有带 jobHandler 注解的类
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
if (serviceBeanMap!=null && serviceBeanMap.size()>0) {
for (Object serviceBean : serviceBeanMap.values()) {
// 这里就是为什么定义的 Job 任务需要实现 IJobHandler 接口
if (serviceBean instanceof IJobHandler){
// 获取注解携带的 name 信息
String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
IJobHandler handler = (IJobHandler) serviceBean;
// jobHandlerRepository 中不可以存在重名的job
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler naming conflicts.");
}
// 将 Handler 加入 ConcurrentMap 中
registJobHandler(name, handler);
}
}
}
}
// jobHandlerRepository 实际是一个 Map
private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
return jobHandlerRepository.put(name, jobHandler);
}
public static IJobHandler loadJobHandler(String name){
return jobHandlerRepository.get(name);
}
3.3 流程的处理
这一部分来看一下当前@JobHandler 被注册到 admin 的细节 , 其主要处理类为 ExecutorRegistryThread
// C- ExecutorServiceRegistry
ExecutorRegistryThread.getInstance().start(param.get("appName"), param.get("address"));
// C- ExecutorRegistryThread
public void start(final String appName, final String address){
// 如果 appName 和 XxlJobExecutor.getAdminBizList() 为 null , 则直接 return
registryThread = new Thread(new Runnable() {
@Override
public void run() {
// registry 死循环
while (!toStop) {
try {
// 准备 RegistryParam 对象进行注册处理
RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appName, address);
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
// 发起流程注册
// client.asyncSend(finalAddress, xxlRpcRequest)
// TODO : XXL-JOB 的 RPC 调用也是个比较大的模块 , 后续专门看
ReturnT<String> registryResult = adminBiz.registry(registryParam);
if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
registryResult = ReturnT.SUCCESS;
logger.debug(">>>>>>>>>>> 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) {
// 打印异常日志
}
// while 循环中自动等待
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
}
// registry remove , 移除 service , 主要包含2件事
- AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()
- ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
- registryResult = ReturnT.SUCCESS;
}
});
registryThread.setDaemon(true);
registryThread.setName("xxl-job, executor ExecutorRegistryThread");
registryThread.start();
}
3.4 JobThread 的调用
TODO : 这里先不关注客户端回调的相关逻辑 , 下一篇文章专门看看
总结
作为开篇 , 挑了些软柿子捏 , 后面再深入更细化的层面 , xxl-job 是一个非常好用的任务调度平台 , 除了它提供的全面的功能外 , 代码和结构都非常清晰.
- 开局会注册 service 到 server 中 , 有多种方式注册
- 执行完成后通过 callback 回调 server