DolphinScheduler自身容错导致的服务器持续崩溃重大问题的排查与解决
01 问题复现
在DolphinScheduler中有如下一个Shell任务:
current_timestamp() { date +"%Y-%m-%d %H:%M:%S"
}TIMESTAMP=$(current_timestamp)
echo $TIMESTAMP
sleep 60
在DolphinScheduler将工作流执行策略设置为并行:
定时周期调度设置为10秒一次:
将定时调度上线后,会调度执行任务,此时一切正常:
此时将Master节点给kill掉,模拟宕机:
$ jps
1979710 AlertServer
1979626 WorkerServer
1979546 MasterServer
1979794 ApiApplicationServer
1980483 Jps
$ kill -9 1979546
去到DolphinScheduler中查看,发现Master已经不存在了:
此时观察DolphinScheduler工作流执行,发现其不会继续调度任务执行了,并且所有的任务则会一直执行下去,直到报错。
当过了一段时间后(模拟发现了宕机问题),此时重启DolphinScheduler:
sh bin/stop-all.sh
sh bin/start-all.sh
重启完成后,就会将之前没有执行成功的任务,包括没有执行的调度任务,全部都执行一次:
这就有一个致命的问题:如果都是高性能任务的话,就会导致CPU、内存被打满,从而让服务器整个宕机!!!
02 多场景测试
- Master宕机后,重启整个DS:会产生上述问题。
- Master宕机后,重启相应的Master:会产生上述问题。——有缺陷,官方没有单独的Master后台启动,只有前台启动的脚本,但可以重复执行start-all.sh。
- Worker宕机后,重启整个DS:不会产生上述问题。——因为Master会持续的调度任务,而Worker宕机后的结果就是调度任务直接失败。
- Worker宕机后,重启相应的Worker:不会产生上述问题。——有缺陷,官方没有单独的Worker后台启动,只有前台启动的脚本,但可以重复执行start-all.sh。
- DS整个宕机后,重启整个DS:会产生上述问题。
- DS使用stop-all.sh停止后,重启整个DS:会产生上述问题。
其核心就是在于Master,只要配置了周期任务,无论Master是宕机还是调用脚本关闭的,其都会产生上述问题。
03 原理分析
DolphinScheduler核心角色:
- MasterServer主要负责 DAG 任务切分、任务提交监控,并同时监听其它MasterServer和WorkerServer的健康状态。MasterServer服务启动时向Zookeeper注册临时节点,通过监听Zookeeper临时节点变化来进行容错处理。
- WorkerServer主要负责任务的执行和提供日志服务。WorkerServer服务启动时向Zookeeper注册临时节点,并维持心跳。
- ApiServer主要负责处理前端UI层的请求。
大致的任务运行流程如下:
在API-Server中创建任务,并将元数据持久化到DB中。
通过手动点击或定时执行生成一个触发工作流执行的Command写入DB。
Master消费DB中的Command,开始执行工作流,并将工作流中的任务分发给Worker执行。
当整个工作流执行结束之后,Master结束工作流的执行。
参考官网,上述的DolphinScheduler核心任务执行流程可以细化为如下:
鉴于任务调度的复杂性,一个大的流程可以划分为小的流程,在主线流程之外还附加了支线流程,下面对执行调度流程拆分进行分析一下,这样更容易理解:
在本次问题中,主要关注的就是Command分发流程。其Command分发流程是一个异步分布式生产消费模式。
i. 首先是生产者api-server,会将用户的运行工作流http请求封装成command数据,insert到t_ds_command表中,如下是一个启动工作流实例的command样例(老版本):
{"commandType": "START_PROCESS","processDefinitionCode": 14285512555584,"executorId": 1,"commandParam": "{}","taskDependType": "TASK_POST","failureStrategy": "CONTINUE","warningType": "NONE","startTime": 1723444881372,"processInstancePriority": "MEDIUM","updateTime": 1723444881372,"workerGroup": "default","tenantCode": "default","environmentCode": -1,"dryRun": 0,"processInstanceId": 0,"processDefinitionVersion": 1,"testFlag": 0
}
ii.其次是消费者,master server中的MasterSchedulerBootstrap loop程序, MasterSchedulerBootstrap使用ZK分配到自己的slot,从t_ds_command表中select属于slot的command列表处理,其查询语句是:
<select id="queryCommandPageBySlot" resultType="org.apache.dolphinscheduler.dao.entity.Command">select *from t_ds_commandwhere id % #{masterCount} = #{thisMasterSlot}order by process_instance_priority, id asclimit #{limit}
</select>
iii.MasterSchedulerBootstrap loop轮训查到待处理的command任务,将command任务和master host生成ProcessInstance,将ProcessInstance对象插入到t_ds_process_instance表中, 同时生成包含运行所需要的上下文信息的可执行任务workflowExecuteRunnable。 将workflowExecuteRunnablecache到本地cache processInstanceExecCacheManager,同时生产将ProcessInstance的WorkflowEventType.START_WORKFLOW生产到workflowEventQueue队列中。
上面的步骤是用户在Web页面点击启动任务后的流程,而本次的问题是Master周期调度的问题。经过查阅资料,周期调度任务则是MasterServer将其封装为命令数据并插入t_ds_process_instance表中,后续步骤如上,大致流程如下:
命令分发:以用户提交的工作流请求为触发,MasterServer将其封装为命令数据并插入数据库中。
任务分配:MasterServer循环查询待处理的命令,依照负载情况将任务分配到对应的ProcessInstance中。
任务执行:根据DAG的依赖关系,WorkerServer会优先执行无依赖的任务,然后根据优先级逐步执行其他任务。
状态反馈:任务执行过程中,WorkerServer会定期回调MasterServer,通知任务的进展和执行状态。
所以,上述的问题就在这,当Master从停止到启动时,t_ds_command中会产生大量的任务数据。
在DolphinScheduler3.2.1中,其t_ds_command数据样例为:
id |command_type|process_definition_code|process_definition_version|process_instance_id|command_param |task_depend_type|failure_strategy|warning_type|warning_group_id|schedule_time |start_time |executor_id|update_time |process_instance_priority|worker_group|tenant_code|environment_code|dry_run|test_flag|
----+------------+-----------------------+--------------------------+-------------------+-------------------------------------+----------------+----------------+------------+----------------+-------------------+-------------------+-----------+-------------------+-------------------------+------------+-----------+----------------+-------+---------+
1988| 6| 15921642898976| 4| 0|{"schedule_timezone":"Asia/Shanghai"}| 2| 1| 0| 0|2024-12-11 00:36:40|2024-12-11 00:39:01| 2|2024-12-11 00:39:01| 2|default |default | -1| 0| 0|
1989| 6| 15921642898976| 4| 0|{"schedule_timezone":"Asia/Shanghai"}| 2| 1| 0| 0|2024-12-11 00:36:50|2024-12-11 00:39:01| 2|2024-12-11 00:39:01| 2|default |default | -1| 0| 0|
1990| 6| 15921642898976| 4| 0|{"schedule_timezone":"Asia/Shanghai"}| 2| 1| 0| 0|2024-12-11 00:37:00|2024-12-11 00:39:01| 2|2024-12-11 00:39:01| 2|default |default | -1| 0| 0|
而command_type的枚举由源码中的CommandType定义,其内容如下:
/*** command types* 0 start a new process* 1 start a new process from current nodes* 2 recover tolerance fault process* 3 recover suspended process* 4 start process from failure task nodes* 5 complement data* 6 start a new process from scheduler* 7 repeat running a process* 8 pause a process* 9 stop a process* 10 recover waiting thread* 11 recover serial wait* 12 start a task node in a process instance*/
START_PROCESS(0, "start a new process"),
START_CURRENT_TASK_PROCESS(1, "start a new process from current nodes"),
RECOVER_TOLERANCE_FAULT_PROCESS(2, "recover tolerance fault process"),
RECOVER_SUSPENDED_PROCESS(3, "recover suspended process"),
START_FAILURE_TASK_PROCESS(4, "start process from failure task nodes"),
COMPLEMENT_DATA(5, "complement data"),
SCHEDULER(6, "start a new process from scheduler"),
REPEAT_RUNNING(7, "repeat running a process"),
PAUSE(8, "pause a process"),
STOP(9, "stop a process"),
RECOVER_WAITING_THREAD(10, "recover waiting thread"),
RECOVER_SERIAL_WAIT(11, "recover serial wait"),
EXECUTE_TASK(12, "start a task node in a process instance"),
DYNAMIC_GENERATION(13, "dynamic generation"),
;
那为什么会这样呢?本质上是Master自身的容错机制造成的,其容错机制可以细分为如下几个模块:
1)Master自身的容错:如果是多Master同时运行,其中一个作为Active Master负责处理任务调度请求,其他节点作为Standby Master。当Active Master出现故障时,Standby Master将自动接管其工作,确保系统的正常运行。这是通过ZooKeeper实现的,ZooKeeper负责选举Active Master节点,并监控节点的状态。
2)状态同步:多Master节点之间会进行状态同步,以确保在Active Master宕机时,Standby Master能够接管任务调度。
3)故障恢复:当Master节点宕机后,其他Master节点会通过ZooKeeper的Watcher机制监听到这一事件,并触发故障恢复。
4)正在运行任务的容错:当前Master节点宕机后,新Master会通过已下线的Master的地址和正在运行的工作流状态数组获取需要容错的ProcessInstance列表,之后将其放入t_ds_command表中(后续流程就是Master获取到并调度+Worker执行了)。
5)分布式锁:在容错过程中,Master节点会使用ZK分布式锁+采用指定command表分配ID的形式来确保只有一个Master节点执行容错操作,避免多个Master节点同时接管同一个任务。
6)定时容错线程:除了ZooKeeper的事件触发容错外,DolphinScheduler还实现了一个定时线程FailoverExecuteThread,用于Master重启后恢复自身之前的工作流实例。
7)任务重试:DolphinScheduler还支持任务失败后的重试机制,这与服务宕机容错相辅相成,确保任务的最终执行成功。
所以,此时根据原理+复现可以初步推测出,是在Master启动时的某一个线程进行的定时容错,接下来就进入源码来真正验证一下。
04 源码解析
在org.apache.dolphinscheduler.server.master.MasterServer下,启动Master时会有run入口:
/*** run master server*/
@PostConstruct
public void run() throws SchedulerException {// init rpc serverthis.masterRPCServer.start();// install task pluginthis.taskPluginManager.loadPlugin();this.masterSlotManager.start();// self tolerantthis.masterRegistryClient.start();this.masterRegistryClient.setRegistryStoppable(this);this.masterSchedulerBootstrap.start();this.eventExecuteService.start();this.failoverExecuteThread.start();this.schedulerApi.start();this.taskGroupCoordinator.start();MasterServerMetrics.registerMasterCpuUsageGauge(() -> {SystemMetrics systemMetrics = metricsProvider.getSystemMetrics();return systemMetrics.getTotalCpuUsedPercentage();});MasterServerMetrics.registerMasterMemoryAvailableGauge(() -> {SystemMetrics systemMetrics = metricsProvider.getSystemMetrics();return (systemMetrics.getSystemMemoryMax() - systemMetrics.getSystemMemoryUsed()) / 1024.0 / 1024 / 1024;});MasterServerMetrics.registerMasterMemoryUsageGauge(() -> {SystemMetrics systemMetrics = metricsProvider.getSystemMetrics();return systemMetrics.getJvmMemoryUsedPercentage();});Runtime.getRuntime().addShutdownHook(new Thread(() -> {if (!ServerLifeCycleManager.isStopped()) {close("MasterServer shutdownHook");}}));
}
通过上面的代码,可以看到Master启动执行了:
masterRPCServer.start():初始化并启动RPC服务器,用于节点间通信。
taskPluginManager.loadPlugin():加载任务插件,这些插件可以扩展DolphinScheduler的任务类型。
masterSlotManager.start():启动Master的Slot管理器,它负责管理Master的资源槽位,用于任务调度。
masterRegistryClient.start():启动Master的注册客户端,它负责将Master节点注册到分布式协调服务(如ZooKeeper)中。
masterRegistryClient.setRegistryStoppable(this):设置注册客户端的可停止对象,以便在Master停止时能够进行清理工作。
masterSchedulerBootstrap.start():启动Master的调度引导服务,它负责初始化调度相关的服务。
eventExecuteService.start():启动事件执行服务,它负责处理工作流中的事件,如任务状态变化。
failoverExecuteThread.start():启动故障恢复执行线程,它负责在Master宕机后恢复任务执行。
schedulerApi.start():启动调度API服务,提供调度相关的接口供外部调用。
taskGroupCoordinator.start():启动任务组协调器,它负责协调任务组内的任务执行。
经过源码探查,发现最关键的failoverExecuteThread不是重新执行未调度的周期任务,而是容错未执行完的任务。并且其他源码中也没有关于恢复周期任务调度的内容。
那现在需要换一个思路,就是从下往上走:
首先发现重启恢复后,Web页面上的“运行类型”是“调度执行”,而数据库的“command_type”是“6”,那就意味着必须有一个服务会有往数据库里面去插入command_type为6的方法。并且其会去获取t_ds_schedules表中的任务定时调度实例。
根据源码,排查到dolphinscheduler-dao项目下会存放所有的数据库操作DAO,遂可以找到ScheduleMapper类,此类是和t_ds_schedules相关的DAO类;之后根据t_ds_command反查,找到了CommandServiceImpl类中的createCommand方法;再根据两者反查+command_type为6,找到了ProcessScheduleTask类中的executeInternal方法。
ProcessScheduleTask类中的executeInternal方法,同时满足:获取了调度任务、插入command数据、类型为6这三个条件。
查看ProcessScheduleTask的executeInternal源码,前半部分是从Quartz上下文中获取到预定义的调度时间和调度实际运行时间,下半部分是校验这个调度Cron是否存在和上线。
在executeInternal中,最关键的其实就是scheduledFireTime和fireTime。
找到这里的话,我们再结合DolphinScheduler+Quartz总结一下调度的原理:
Web页面设置调度,其会通过SchedulerController中的createSchedule()来创建调度,并往t_ds_schedules中插入一条数据;
Web页面设置调度上线,其会通过QuartzScheduler中的insertOrUpdateScheduleTask()向Quartz中创建Trigger触发器,并往QRTZ_CRON_TRIGGERS中插入一条数据;
之后定期调用ProcessScheduleTask中的executeInternal()来往t_ds_command中插入数据;
之后就是Master-Worker的执行流程了;
了解了大致的调度流程后,结合源码中的scheduledFireTime和fireTime,就可以推断出调度时间不是由DolphinScheduler设置的,而是由Quartz设置的。
那就继续查阅Quartz相关的资料,发现在Quartz中有一个misfire机制:周期性任务A需要在某个规定的时间执行,但是由于某种原因导致任务A未执行,称为MisFire。
而Quartz判断一个任务是MisFire,提供了一个配置项:org.quartz.jobStore.misfireThreshold,默认是60000ms(即60秒)。
misfire产生需要有2个前置条件:
一个是job到达触发时间时没有被执行;
二是被执行的延迟时间超过了Quartz配置的misfireThreshold阀值;
如果延迟执行的时间小于阀值,则Quartz不认为发生了misfire,立即执行job;如果延迟执行的时间大于或者等于阀值,则被判断为misfire,然后会按照指定的策略来执行。
那misfire产生的原因一般如下:
当job达到触发时间时,所有线程都被其他job占用,没有可用线程。;
在job需要触发的时间点,scheduler停止了(可能是意外停止的);【——当前的问题属于这种类型】
job使用了@DisallowConcurrentExecution注解,job不能并发执行,当达到下一个job执行点的时候,上一个任务还没有完成;
job指定了过去的开始执行时间,例如当前时间是8点00分00秒,指定开始时间为7点00分00秒;
而判定了任务是MisFire后,会有一个补偿机制,补偿机制只有在任务确认为MisFire状态后,才会被执行。补偿机制配置在Quartz源码的Trigger中:
public interface Trigger extends Serializable, Cloneable, Comparable<Trigger> {long serialVersionUID = -3904243490805975570L;int MISFIRE_INSTRUCTION_SMART_POLICY = 0;int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;int DEFAULT_PRIORITY = 5;......
但是这个补偿机制需要根据Trigger来判定,如下是不同的Trigger:
在DolphinScheduler中,各种类型的Trigg都会涉及到:
Trigger的类型:
SimpleTrigger是一个简单的触发器,用于执行重复任务。它可以指定一个起始时间,然后按照固定的间隔时间重复执行任务,直到达到指定的重复次数。SimpleTrigger的属性包括重复间隔(repeatInterval)和重复次数(repeatCount),实际执行次数是repeatCount + 1,因为在开始时间(startTime)时会执行一次。
CronTrigger:CronTrigger使用Cron表达式来定义复杂的调度计划。Cron表达式由6或7个空格分隔的时间字段组成,分别表示秒、分、小时、一个月中的日期、月份、一周中的日期和可选的年份。CronTrigger允许设定非常复杂的触发时间表,基本上覆盖了其他触发器的绝大部分能力。
CalendarIntervalTrigger:CalendarIntervalTrigger指定从某一个时间开始,以一定的时间间隔执行的任务。不同于SimpleTrigger只支持毫秒单位的时间间隔,CalendarIntervalTrigger支持的间隔单位有秒、分钟、小时、天、月、年。它适合的任务类似于每周执行一次。
DailyTimeIntervalTrigger:DailyTimeIntervalTrigger指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。它适合的任务类似于每天9:00到18:00,每隔70秒执行一次,并且只要周一至周五执行。
......
所以,因为不同的Trigger类型其参数是不一样的,所以当Trigger触发Misfire机制时,根据Trigger的不同,策略也会不同:
/**公共的Misfire机制,在Trigger类中**/// 这是一个智能策略,Quartz会根据Trigger的类型自动选择一个合适的misfire策略。对于CronTrigger,默认使用MISFIRE_INSTRUCTION_FIRE_ONCE_NOW。
int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
// 这个策略会将所有错过的触发事件,立即执行所有补偿动作。即使定时任务执行的时间已经结束,它也会把所有应该执行的任务一次性全部执行完。
int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
/**SimpleTrigger的Misfire机制,在SimpleTrigger类中**/// 如果触发器错过了预定的触发时间,这个策略会立即执行一次任务,然后按照原计划继续执行后续的任务。
int MISFIRE_INSTRUCTION_FIRE_NOW = 1;
// 这个策略会将触发器的开始时间设置为当前时间,并立即执行错过的任务,包括已经错过的重复次数。
int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2;
// 类似于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,但是会忽略已经错过的触发次数,只执行剩余的重复次数。
int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3;
// 这个策略会忽略已经错过的触发次数,并在下一个预定的触发时间执行任务,执行剩余的重复次数。
int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4;
// 类似于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,但是会包括所有错过的重复次数。
int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5;
/**
CronTrigger的Misfire机制,在CronTrigger类中
**/
// 如果触发器错过了预定的触发时间,这个策略会立即执行一次任务,然后按照原计划继续执行后续的任务。
int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
// 对于CronTrigger,这个策略会忽略所有错过的触发事件,直接等待下一次预定的触发时间。
int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
......
在QuartzScheduler的insertOrUpdateScheduleTask()中,用的只有CronTrigger,其源码如下:
CronTrigger cronTrigger = newTrigger().withIdentity(triggerKey).startAt(startDate).endAt(endDate).withSchedule(cronSchedule(cronExpression).withMisfireHandlingInstructionIgnoreMisfires().inTimeZone(DateUtils.getTimezone(timezoneId))).forJob(jobDetail).build();
// 往下走
public CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() {this.misfireInstruction = -1;return this;
}
其补偿机制采用的-1编码,也就是会将所有错过的触发事件,立即执行所有补偿动作。所以此时就可以解释,为什么Master重启后,会将所有的未执行的周期任务,全部执行一次!!!
这个设置根据Trigger的不同,也可以分别设置不同的参数:
/**CronTrigger,引用CronScheduleBuilder中的设置**/
public CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() {this.misfireInstruction = -1;return this;
}
public CronScheduleBuilder withMisfireHandlingInstructionDoNothing() {this.misfireInstruction = 2;return this;
}
public CronScheduleBuilder withMisfireHandlingInstructionFireAndProceed() {this.misfireInstruction = 1;return this;
}
/**SimpleTrigger,引用SimpleScheduleBuilder的设置
**/
public SimpleScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() {this.misfireInstruction = -1;return this;
}
public SimpleScheduleBuilder withMisfireHandlingInstructionFireNow() {this.misfireInstruction = 1;return this;
}
public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithExistingCount() {this.misfireInstruction = 5;return this;
}
public SimpleScheduleBuilder withMisfireHandlingInstructionNextWithRemainingCount() {this.misfireInstruction = 4;return this;
}
public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithExistingCount() {this.misfireInstruction = 2;return this;
}
public SimpleScheduleBuilder withMisfireHandlingInstructionNowWithRemainingCount() {this.misfireInstruction = 3;return this;
}
05 解决方案
- 将任务设置为“串行等待”。——可行,但无法发挥出大数据集群的并行化优势。并且有一个致命的缺陷,就是串行等待的任务无法在页面数手动停止,需要去到t_ds_process_instance中更改状态或删除数据。
Master HA:单机Master时,对Master设置守护,宕机后自动拉起(但也有无法拉起的时候);多机Master时,部署多台Master,使其可以实现HA。
DolphinScheduler监控告警:持续监控DolphinScheduler运行状态,当有角色宕机后,及时发出告警信息(会有半夜宕机而运维人员没有及时发现告警信息的情况)。
设置DolphinScheduler的CPU和内存使用阈值:在配置文件中,默认的CPU和内存阈值是70%,以为着当服务器的CPU和内存占用达到了70%后,DolphinScheduler就不会在这台服务器上调度任务了。这种方式的好处是可以保证服务器资源不被打满,弊端是如果Master容错的旧任务打满了资源,那就会影响DolphinScheduler正常状态下的新任务了。并且有的任务是非常关键的任务,必须要跑成功的。
设置DolphinScheduler的任务数:在配置文件中,DolphinScheduler默认的任务数是单Worker100个,单Master是1000个。而在现网中,无法对任务数去做到精细的控制,并且DolphinScheduler也无法做到自动调配。
在宕机后重新启动前删除t_ds_command表中的数据:经过验证,Master在宕机后是不会往t_ds_command中写数据了。其会在重启启动后,将数据写到t_ds_command后执行,但其中的时间大概就1~2秒钟,手工无法去执行删除。
修改t_ds_process_instance中的数据:根据时间周期,修改t_ds_process_instance中所有这个范围内的工作流的状态,人工使其结束(但如果DolphinScheduler和元数据库在一台服务器上,容易DolphinScheduler启动后里面把服务器资源打满,造成无法操作元数据库了)。
上面的解决方案主要是分为:
避免或减少Master的宕机;
在Master宕机后,不要运行MisFire的任务;
首先是“避免或减少Master宕机”,这在生产环境中是很难做到的,计算机程序的假设就是100%会在某一个时刻产生某些问题,所以才有了各种微服务架构、高可用HA、多活、容灾等等机制。
其次是“不要运行MisFire的任务”,依照前面的解决方案,没有一个方案能解决这个问题。所以,根据之前的源码解析,需要考虑采用源码修改+重新编译打包的方式进行解决。
06 修改源码
将关键源码修改为:
CronTrigger cronTrigger = newTrigger().withIdentity(triggerKey).startAt(startDate).endAt(endDate).withSchedule(cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing()
// .withMisfireHandlingInstructionIgnoreMisfires().inTimeZone(DateUtils.getTimezone(timezoneId))).forJob(jobDetail).build();
07 开发环境验证
使用Java8进行。
更改Master、worker、API下的application.yaml中的MySQL链接信息:
spring:config:activate:on-profile: mysqldatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://IP地址:3306/dolphinscheduler?useUnicode=true&characterEncoding=UTF-8&useSSL=falseusername: 账号password: 密码quartz:properties:org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
更改Master、Worker、API下的Zookeeper信息:
registry:type: zookeeperzookeeper:namespace: dolphinscheduler_devconnect-string: IP地址:2181retry-policy:base-sleep-time: 60msmax-sleep: 300msmax-retries: 5session-timeout: 30sconnection-timeout: 9sblock-until-connected: 600msdigest: ~
更改bom下面的pom:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector.version}</version>
<!-- <scope>test</scope>--></dependency>
更改api、master、worker下的logback-spring.xml,开启运行日志:
<root level="INFO">
<!-- <if condition="${DOCKER:-false}">-->
<!-- <then>-->
<!-- <appender-ref ref="STDOUT"/>-->
<!-- </then>-->
<!-- </if>--><appender-ref ref="STDOUT"/><appender-ref ref="APILOGFILE"/></root><root level="INFO">
<!-- <if condition="${DOCKER:-false}">-->
<!-- <then>-->
<!-- <appender-ref ref="STDOUT"/>-->
<!-- </then>-->
<!-- </if>--><appender-ref ref="STDOUT"/><appender-ref ref="TASKLOGFILE"/><appender-ref ref="MASTERLOGFILE"/></root><root level="INFO">
<!-- <if condition="${DOCKER:-false}">-->
<!-- <then>-->
<!-- <appender-ref ref="STDOUT"/>-->
<!-- </then>-->
<!-- </if>--><appender-ref ref="STDOUT"/><appender-ref ref="TASKLOGFILE"/><appender-ref ref="WORKERLOGFILE"/></root>
启动 Master、Worker、Api:
Master VM Options:-Dlogging.config=classpath:logback-spring.xml -Ddruid.mysql.usePingMethod=false -Dspring.profiles.active=mysql
Worker VM Options:-Dlogging.config=classpath:logback-spring.xml -Ddruid.mysql.usePingMethod=false -Dspring.profiles.active=mysql
Api VM Options:-Dlogging.config=classpath:logback-spring.xml -Dspring.profiles.active=api,mysql
如果报错:
Error running 'ApiApplicationServer' Error running ApiApplicationServer. Command line is too long. Shorten the command line via JAR manifest or via a classpath file and rerun. 则加入:
如果还是报错缺少MySQL的JDBC驱动包,则在Master、Woker、API的Pom下面添加:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version>
</dependency>
08 整体编译打包
需要注意,此时打包的项目,需要只是经过了《修改源码》环境的,不是进行了《开发环境验证》环节的!!!
使用Java8进行。
在项目根目录下执行命令,打包时间较长:
mvn spotless:apply clean package -Dmaven.test.skip=true -Prelease
打包后的二进制文件在dolphinscheduler-dist/target 下 bin.tar.gz 后缀文件。
之后就可以尝试重新部署,验证是否解决上面的问题了。
09 只编译单个模块
去到dolphinscheduler-scheduler-quartz根目录下,执行:
mvn spotless:apply clean package -Dmaven.test.skip=true -Prelease
打包后的文件在dolphinscheduler-scheduler-quartz/target目录下:
将其在服务器上进行替换:
su dolphinscheduler -mv /opt/module/dolphinscheduler-3.2.1/master-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jar /opt/module/dolphinscheduler-3.2.1/master-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jar.bak
mv /opt/module/dolphinscheduler-3.2.1/api-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jar /opt/module/dolphinscheduler-3.2.1/api-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jar.bakcp dolphinscheduler-scheduler-quartz-3.2.1.jar /opt/module/dolphinscheduler-3.2.1/master-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jar
cp dolphinscheduler-scheduler-quartz-3.2.1.jar /opt/module/dolphinscheduler-3.2.1/api-server/libs/dolphinscheduler-scheduler-quartz-3.2.1.jarchown -R dolphinscheduler:dolphinscheduler /opt/module/dolphinscheduler-3.2.1/
之后就可以尝试重启DolphinScheduler,验证是否解决上面的问题了。
10 问题解决
再次进行问题复现,发现问题已经被解决了:
至此,本次问题排查及修复完成。
本文由 白鲸开源科技 提供发布支持!
相关文章:
DolphinScheduler自身容错导致的服务器持续崩溃重大问题的排查与解决
01 问题复现 在DolphinScheduler中有如下一个Shell任务: current_timestamp() { date "%Y-%m-%d %H:%M:%S" }TIMESTAMP$(current_timestamp) echo $TIMESTAMP sleep 60 在DolphinScheduler将工作流执行策略设置为并行: 定时周期调度设置…...
Linux 容器漏洞
定义:Linux 容器漏洞是指在容器技术(如 Docker、LXC 等)运行环境中存在的安全弱点。这些漏洞可能存在于容器镜像本身、容器运行时(如 runc)、容器编排工具(如 Kubernetes)或者容器与主机之间的交…...
前端依赖安装指南
前端依赖安装指南 一、NVM管理工具安装 1.在 Windows 上安装 下载 NVM for Windows 的安装程序:(最新版本可以在 nvm-windows Releases 页面 找到)运行下载的安装程序并按步骤操作。 2.配置 NVM exe安装自动配置环境变量 3. 验证 NVM 安装 验证 NVM 是否成功…...
滑动窗口限流算法:基于Redis有序集合的实现与优化
滑动窗口限流算法是一种基于时间窗口的流量控制策略,它将时间划分为固定大小的窗口,并在每个窗口内记录请求次数。通过动态滑动窗口,算法能够灵活调整限流速率,以应对流量的波动。 算法核心步骤 统计窗口内的请求数量࿱…...
JavaRestClient 客户端初始化+索引库操作
1. 介绍 ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。 Elasticsearch目前最新版本是8.0,其java客户端有很大变化。不过大多数企业使用的还是8以下版本 2. 客户端初始化 在elastic…...
理解Spark中运行程序时数据被分区的过程
在Spark中,数据分区是指将数据集分割成多个小的子集,即分区,以便在集群的多个节点上并行处理,从而提高处理效率。以下通过一个具体例子来理解: 例子背景 假设要分析一个包含100万条销售记录的数据集,每条…...
【微服务】面试 7、幂等性
幂等性概念及场景 概念:多次调用方法或接口不改变业务状态,重复调用结果与单次调用一致。例如在京东下单,多次点击提交订单只能成功一次。场景:包括用户重复点击、网络波动导致多次请求、mq 消息重复消费、代码中设置失败或超时重…...
10步打造完美ASP.NET、Web API和控制台应用程序文件夹结构
一、前言 在大型项目中,合理的文件夹结构是项目成功的关键之一。一个好的文件夹结构就像是一座井然有序的图书馆,每一本书(代码文件)都有其固定的位置,让人能迅速找到所需。它可以让团队成员更容易理解和维护代码&…...
30_Redis哨兵模式
在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性…...
双模充电桩发展前景:解锁新能源汽车未来的金钥匙,市场潜力无限
随着全球能源转型的浪潮席卷而来,新能源汽车行业正以前所未有的速度蓬勃发展,而作为其坚实后盾的充电基础设施,特别是双模充电桩,正逐渐成为推动这一变革的关键力量。本文将从多维度深入剖析双模充电桩的市场现状、显著优势、驱动…...
Trimble自动化激光监测支持历史遗产实现可持续发展【沪敖3D】
故事桥(Story Bridge)位于澳大利亚布里斯班,建造于1940年,全长777米,横跨布里斯班河,可载汽车、自行车和行人往返于布里斯班的北部和南部郊区。故事桥是澳大利亚最长的悬臂桥,是全世界两座手工建…...
深入解析 C++ 类型转换
简介 C 类型转换是开发者必须掌握的重要技能之一, 无论是处理隐式转换还是显式转换, 理解其背后的机制与用法至关重要. 本篇博客旨在从基础到高级全面解析 C 的类型转换, 包括实际开发中的应用场景和性能分析. 自动转换 隐式类型转换 编译器可以在无需明确指示的情况下, 将一…...
2025-1-9 QT 使用 QXlsx库 读取 .xlsx 文件 —— 导入 QXlsx库以及读取 .xlsx 的源码 实践出真知,你我共勉
文章目录 1. 导入QXlsx库2. 使用 QXlsx库 读取 .xlsx 文件小结 网上有很多教程,但太费劲了,这里有个非常简便的好方法,分享给大家。 1. 导入QXlsx库 转载链接 :https://github.com/QtExcel/QXlsx/blob/master/HowToSetProject.md…...
基于ILI9341液晶屏+STM32U5单片的显示试验
试验要求: 1、通过串口,下发两个命令 STR和PIC; 2、STR模式: (1)串口输入什么,屏幕上显示什么 (2)如果屏幕满,自动下滚 (3)输入回车&a…...
Vue.js组件开发-如何使用moment.js
在Vue.js组件开发中,需要处理日期和时间,moment.js 是一个非常有用的库。moment.js 提供了丰富的API来解析、验证、操作和显示日期和时间。 步骤: 1. 安装moment.js 首先,需要通过npm或yarn安装moment.js。在项目根目录下运行以…...
自然语言转 SQL:通过 One API 将 llama3 模型部署在 Bytebase SQL 编辑器
使用 Open AI 兼容的 API,可以在 Bytebase SQL 编辑器中使用自然语言查询数据库。 出于数据安全的考虑,私有部署大语言模型是一个较好的选择 – 本文选择功能强大的开源模型 llama3。 由于 OpenAI 默认阻止出站流量,为了简化网络配置&#…...
全面掌握APT、Vim和GCC:Ubuntu软件管理与开发指南
文章目录 Ubuntu 软件包管理器Ubuntu 软件包管理的基本概念常用的软件包管理器APTAPT常用命令 vimVim 的基本概念Vim 的工作模式Vim 的基本操作 gcc/gUbuntu 安装 gcc / g编译知识使用方法动静态函数库 Ubuntu 软件包管理器 在 **Ubuntu** 系统中,软件包管理器用于…...
项目实战--网页五子棋(用户模块)(1)
接下来我将使用Java语言,和Spring框架,实现一个简单的网页五子棋。 主要功能包括用户登录注册,人机对战,在线匹配对局,房间邀请对局,积分排行版等。 这篇文件讲解用户模块的后端代码 1. 用户表与实体类 …...
【Ubuntu与Linux操作系统:七、系统高级管理】
第7章 系统高级管理 7.1 Linux进程管理 进程是Linux系统中的基本运行单位,代表一个正在执行的程序。Linux通过进程管理实现多任务并发处理,支持用户高效利用系统资源。 1. 进程的基本概念: 进程状态:进程在运行过程中可能处于运…...
多线程面试相关
线程基础知识 线程与进程的区别 并行和并发的区别 创建线程的方式 Runnable和Callable有什么区别 run()方法和start()方法的区别 小结 线程包含哪些状态,各个状态之间如何变化 线程按顺序执行 notify()和notifyAll()的区别 Java中的wait方法和sleep方法的不同 如何…...
WMS仓库管理系统,Vue前端开发,Java后端技术源码(源码学习)
一、项目背景和建设目标 随着企业业务的不断扩展,仓库管理成为影响生产效率、成本控制及客户满意度的重要环节。为了提升仓库作业的透明度、准确性和效率,本方案旨在构建一套全面、高效、易用的仓库管理系统(WMS)。该系统将涵盖库…...
【前端】【CSS3】基础入门知识
目录 如何学习CSS 1.1什么是CSS编辑 1.2发展史 1.三种导入方式 1.1、行内样式 1.2、外部样式 1.3、嵌入方式 2.选择器 2.1、基本选择器 (1)元素选择器 (2)类选择器 (3)id选择器:必…...
51单片机——定时器中断(重点)
STC89C5X含有3个定时器:定时器0、定时器1、定时器2 注意:51系列单片机一定有基本的2个定时器(定时器0和定时器1),但不全有3个中断,需要查看芯片手册,通常我们使用的是基本的2个定时器ÿ…...
Android原生开发同一局域网内利用socket通信进行数据传输
1、数据接收端代码如下,注意:socket 接收信息需要异步运行: // port 端口号自定义一个值,比如 8888,但需和发送端使用的端口号保持一致 ServerSocket serverSocket new ServerSocket(port); while (true) {//这里为了…...
基于微信小程序的食堂线上预约点餐系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
算法初学者(图的存储)链式前向星
知识储备:概念,存储,遍历,最短路,最小生成树,拓扑排序-关键路径 图的存储:邻接矩阵,邻接表,十字链表,多重邻接表,边集数组 其中:邻接…...
Github 2025-01-11 Rust开源项目日报 Top10
根据Github Trendings的统计,今日(2025-01-11统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10C项目1Swift项目1Yazi - 快速终端文件管理器 创建周期:210 天开发语言:Rust协议类型:MIT LicenseStar数量:5668 个Fork数量:122…...
Leetcode 3419. Minimize the Maximum Edge Weight of Graph
Leetcode 3419. Minimize the Maximum Edge Weight of Graph 1. 解题思路2. 代码实现3. 算法优化 题目链接:3419. Minimize the Maximum Edge Weight of Graph 1. 解题思路 这一题我的思路就是二分法,找到能够完成遍历的临界值即可。 但是自己实际在…...
深入理解数据库索引及其优化策略
数据库作为现代应用系统的核心组件之一,如何高效地存储和检索数据成为开发者关注的焦点。 在处理大规模数据时,数据库索引 是提升查询性能的关键技术之一。本文将深入探讨数据库索引的工作原理、常见类型、创建索引的策略以及如何优化索引,以…...
安科瑞 Acrel-1000DP 分布式光伏监控系统在工业厂房分布式光伏发电项目中的应用
吕梦怡 18706162527 摘 要:常规能源以煤、石油、天然气为主,不仅资源有限,而且会造成严重的大气污染,开发清洁的可再生能源已经成为当今发展的重要任务,“节能优先,效率为本”的分布式发电能源符合社会发…...
css面试常考布局(圣杯布局、双飞翼布局、三栏布局、两栏布局、三角形)
两栏布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…...
【物流管理系统 - IDEAJavaSwingMySQL】基于Java实现的物流管理系统导入IDEA教程
有问题请留言或私信 步骤 下载项目源码:项目源码 解压项目源码到本地 打开IDEA 左上角:文件 → 新建 → 来自现有源代码的项目 找到解压在本地的项目源代码文件,点击确定,根据图示步骤继续导入项目 查看项目目录ÿ…...
IntelliJ IDEA 主题插件
在 IntelliJ IDEA 中,有很多优秀的主题插件可以帮助你改变 IDE 的外观和配色方案,使得开发过程更加愉悦和高效。以下是一些非常受欢迎和实用的 主题插件,以及如何安装和使用它们的步骤: 🌟 流行主题插件推荐 1️⃣ Ma…...
ASP.NET Core 中,Cookie 认证在集群环境下的应用
在 ASP.NET Core 中,Cookie 认证在集群环境下的应用通常会遇到一些挑战。主要的问题是 Cookie 存储在客户端的浏览器中,而认证信息(比如 Session 或身份令牌)通常是保存在 Cookie 中,多个应用实例需要共享这些 Cookie …...
k8s笔记29--使用kyverno提高运维效率
k8s笔记29--使用kyverno提高运维效率 介绍原理安装应用场景自动修正测试环境pod资源强制 Pod 标签限制容器镜像来源禁止特权容器其它潜在场景 注意事项说明 介绍 Kyverno是一个云原生的策略引擎,它最初是为k8s构建的,现在也可以在k8s集群之外用作统一的…...
快速上手Git——Windows系统下Git的安装与简单使用流程
一、Git的下载和安装 Git官网链接:https://git-scm.com/ 进入官网后选择Downloads 选择与系统相符合的版本下载,这里我使用的是windows系统 然后点击下载 根据流程安装完成后,使用以下命令查看git版本 git -v运行结果: 二、…...
apollo内置eureka dashboard授权登录
要确保访问Eureka Server时要求输入账户和密码,需要确保以下几点: 确保 eurekaSecurityEnabled 配置为 true:这个配置项控制是否启用Eureka的安全认证。如果它被设置为 false,即使配置了用户名和密码,也不会启用安全认…...
linux--防火墙 iptables 双网卡 NAT 桥接
linux--防火墙 iptables 双网卡 NAT 桥接 1 介绍1.1 概述1.2 iptables 的结构 2 四表五链2.1 iptables 的四表filter 表:过滤规则表,默认表。nat 表:地址转换表。mangle 表:修改数据包内容。raw 表:原始数据包表。 2.2…...
C#反射的应用案例与讲解
C# 反射 文章目录 C# 反射前言案例展示将对象转为字典测试用例执行效果代码讲解 HasValue扩展测试用例执行效果代码讲解 反射的底层逻辑反射的原理反射的基本概念反射常用的API和方法GetType类Activator类PropertyInfo类EventInfo 类MemberInfo类MethodInfo类 反射的优缺点优点…...
Mysql常见知识点
Mysql是最常用的数据库了。 1、什么是关系型数据库? 关系型数据库(RDB,Relational Database)就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多&#…...
玩转 JMeter:Random Order Controller让测试“乱”出花样
嘿,各位性能测试的小伙伴们!今天咱要来唠唠 JMeter 里超级有趣又超实用的 Random Order Controller(随机顺序控制器),它就像是性能测试这场大戏里的“魔术棒”,轻轻一挥,就能让测试场景变得千变…...
企业级PHP异步RabbitMQ协程版客户端 2.0 正式发布
概述 workerman/rabbitmq 是一个异步RabbitMQ客户端,使用AMQP协议。 RabbitMQ是一个基于AMQP(高级消息队列协议)实现的开源消息组件,它主要用于在分布式系统中存储和转发消息。RabbitMQ由高性能、高可用以及高扩展性出名的Erlan…...
计算机网络 (37)TCP的流量控制
前言 计算机网络中的TCP(传输控制协议)流量控制是一种重要机制,用于确保数据在发送方和接收方之间的传输既高效又稳定。 一、目的 TCP流量控制的主要目的是防止发送方发送数据过快,导致接收方无法及时处理,从而引起数据…...
Windows10下安装vue2.0项目所需环境
一、Node.js版本管理器NVM安装 1.下载NVM安装包 nvm全英文也叫node.js version management,是一个nodejs的版本管理工具。nvm和n都是node.js版本管理工具,为了解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js。目前最新版…...
使用Cilium/eBPF实现大规模云原生网络和安全
大家读完觉得有帮助记得关注和点赞!!! 目录 抽象 1 Trip.com 云基础设施 1.1 分层架构 1.2 更多细节 2 纤毛在 Trip.com 2.1 推出时间表 2.2 自定义 2.3 优化和调整 2.3.1 解耦安装 2.3.2 避免重试/重启风暴 2.3.3 稳定性优先 2…...
SQL美化器优化
文章目录 1.目录2.代码 1.目录 2.代码 package com.sunxiansheng.mybatis.plus.inteceptor;import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.*; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.*…...
网络基础1 http1.0 1.1 http/2的演进史
http1.0 1.1 http/2的演进史😎 (连接复用 队头阻塞 服务器推送 2进制分帧) 概述 我们主要关注的是应用层 传输层 http协议发展历史 http的报文结构:起始行 Header Body http的典型特征 http存在的典型问题 Keep Alive机制 chun…...
MySQL不使用子查询的原因
MySQL不使用子查询的原因及优化案例 目录 MySQL不使用子查询的原因及优化案例 目录不推荐使用子查询和JOIN的原因解决方案优化案例 案例1:查询所有有库存的商品信息案例2:使用EXISTS优化子查询案例3:使用JOIN代替子查询案例4:优化…...
网络编程(1)
网络编程概述 Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。 Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里&#…...
Jaeger UI使用、采集应用API排除特定路径
Jaeger使用 注: Jaeger服务端版本为:jaegertracing/all-in-one-1.6.0 OpenTracing版本为:0.33.0,最后一个版本,停留在May 06, 2019。最好升级到OpenTelemetry。 Jaeger客户端版本为:jaeger-client-1.3.2。…...