对xxl-job架构的一点思考

本文中源码来自xxl-job 2.4.0版本

大家好,我是程序员侠客。在熟悉了xxl-job源码很多细节后,本文尝试跳出细节,思考其中值得借鉴学习的地方,如接口设计、RPC实现、CAP模型选择等。

希望在阅读源码过程中,帮助大家提高编程技巧外,还能提升个人架构思维。

一 接口或类体系设计

xxl-job源码中,接口或类的设计,简单清晰。

1.1 JobAlarm接口

仅有一个方法。提供了一个Email实现即EmailJobAlarm,我们可自行扩展其他方式,如电话、短信等。

java 复制代码
@Component
public class PhoneJobAlarm implements JobAlarm {
  public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog) {
    // 打电话
    return true;
  }
}

又定义了JobAlarmer类,在afterPropertiesSet()中初始化jobAlarmList,alarm过程就是遍历调用每一个JobAlarmer实现。

java 复制代码
@Component
public class JobAlarmer implements ApplicationContextAware, InitializingBean {
  private List<JobAlarm> jobAlarmList;

  // 简化后代码
  public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) {
    boolea   result = true;
    // 遍历每一个实现类
    for (JobAlarm alarm : jobAlarmList) {
        result = result && alarm.doAlarm(info, jobLog);
    }
    return result;
  }
}

1.2 ExecutorRouter抽象类

该类用于定义路由策略,route方法根据任务参数,从执行器地址列表中选择一个节点来触发执行。

java 复制代码
public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList);

源码中提供了以下实现,继承体系非常清晰。 子类对象并没有交给Spring容器管理,而是在枚举类中创建,实现了单例。可见,这些子类都是无状态的。

1.3 IJobHandler抽象类

IJobHandler是对任务体的定义,有3个方法。 源码中提供了Glue、脚本、基于方法的实现。

1.4 AdminBiz接口

该接口定义了执行器指向调度中心的RPC调用:注册、取消注册和执行结果回调。 有两个实现:执行器发送请求、调度中心处理请求。 ExecutorBiz接口也是同样的模式。 用一个接口来约定server、client两端的可交互内容,显得非常清晰。

二 RPC实现

2.1 执行器Socket客户端实现

执行器被集成在业务应用中。这些应用不一定是web项目,也未必使用如springMVC这样的web框架。因此,执行器的Socket客户端,得独立实现。

xxl-job的执行器端,使用了Netty,封装在EmbedServer类中。

2.2 调度中心Socket客户端实现

调度中心向执行器发送请求,使用了JDK中原生的java.net.HttpURLConnection,作为Http客户端。在XxlJobRemotingUtil类中

2.3 调度中心Socket服务端实现

调度中心本身基于springboot开发,本身是个web项目,使用内置的tomcat,作为Socket服务端实现。 调度中心处理执行器请求,使用RESTful风格接口和springmvc框架,。

三 对xxl-job架构的思考

3.1 调度中心的CAP选择

调度中心独立于执行器,需要单独部署,支持集群模式(没有主节点)。由于数据保持在mysql中,实现了多节点间共享。因此,不需要Zookeeper这样的中间件。

调度中心集群的CAP模型,取决于Mysql的部署方式。

3.2 有状态的执行器

xxl-job执行器本身,是有状态的:待执行的任务队列、回调队列和本地日志文件

如果某个执行器突然宕机,会导致该执行器上积压的任务丢失;调度平台访问执行日志时,也可能查询无果。

3.3 任务体是否有状态

除了分片任务,在一次调度中仅有一个节点被选中,且任务的阻塞策略(有3种)不允许并发执行。 因此,任务体似乎没有并发操作时的数据一致性问题。

但是,我们自己实现的任务体,可能是有状态的。如某个任务中需要处理数据库表数据,在没有避免并发处理的措施时,可能导致意外结果。

因为,在路由策略和执行周期选择不合理时,会导致多个执行器节点并发执行。

如使用FAILOVER即失效转移路由时,由于网络通信原因误判了第一次失败,又重新路由到第二个节点。那么这两个节点间就并发执行了。

或者任务的周期间隔设置不合理,使得下一次调度触发时,上一次长耗时执行还未结束,这两次执行又路由到不同的节点,此时就并发了。

在实现任务体时,我们需要考虑幂等或防止并发执行。

相关推荐
coderSong256829 分钟前
Java高级 |【实验八】springboot 使用Websocket
java·spring boot·后端·websocket
Mr_Air_Boy1 小时前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
豆沙沙包?2 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
打码人的日常分享2 小时前
物联网智慧医院建设方案(PPT)
大数据·物联网·架构·流程图·智慧城市·制造
年老体衰按不动键盘2 小时前
快速部署和启动Vue3项目
java·javascript·vue
咖啡啡不加糖2 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
liuyang-neu2 小时前
java内存模型JMM
java·开发语言
白水baishui3 小时前
搭建强化推荐的决策服务架构
架构·推荐系统·强化学习·决策服务·服务架构
何双新3 小时前
第23讲、Odoo18 邮件系统整体架构
ai·架构
雪碧聊技术3 小时前
将单体架构项目拆分成微服务时的两种工程结构
微服务·架构·module·project·工程结构