本文中源码来自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 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 route(TriggerParam triggerParam, List 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
即失效转移路由时,由于网络通信原因误判了第一次失败,又重新路由到第二个节点。那么这两个节点间就并发执行了。或者任务的周期间隔设置不合理,使得下一次调度触发时,上一次长耗时执行还未结束,这两次执行又路由到不同的节点,此时就并发了。
在实现任务体时,我们需要考虑幂等或防止并发执行。
评论记录:
回复评论: