对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即失效转移路由时,由于网络通信原因误判了第一次失败,又重新路由到第二个节点。那么这两个节点间就并发执行了。

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

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

相关推荐
渣哥2 分钟前
聊聊我和 ArrayList、LinkedList、Vector 的“一地鸡毛”
java
知其然亦知其所以然3 分钟前
面试官微笑发问:第100万页怎么查?我差点当场沉默…
后端·mysql·面试
浮游本尊5 分钟前
Java学习第20天 - 性能优化与监控
java
文心快码BaiduComate28 分钟前
文心快码升级至3.5S版本,强化多智能体自协同能力
前端·后端·程序员
纪莫29 分钟前
技术面:Java并发(线程同步、死锁、多线程编排)
java·java面试⑧股
九章云极AladdinEdu34 分钟前
AI集群全链路监控:从GPU微架构指标到业务Metric关联
人工智能·pytorch·深度学习·架构·开源·gpu算力
衍余未了40 分钟前
k8s 内置的containerd配置阿里云个人镜像地址及认证
java·阿里云·kubernetes
蒋星熠44 分钟前
深入 Kubernetes:从零到生产的工程实践与原理洞察
人工智能·spring boot·微服务·云原生·容器·架构·kubernetes
叽哥1 小时前
Kotlin学习第 4 课:Kotlin 函数:从基础定义到高阶应用
android·java·kotlin
渣哥1 小时前
使用 HashMap 提高性能的小技巧
java