Apache DolphinScheduler 限制秒级别的定时调度

背景

Apache DolphinScheduler 定时任务配置采用的 7 位 Crontab 表达式,分别对应秒、分、时、月天、月、周天、年

在团队日常开发工作中,工作流的定时调度一般不会细化到秒级别。但历史上出现过因配置的疏忽大意而产生故障时间,如应该配置每分钟执行的工作流被配置长了每秒执行,造成短时间内产生大量工作流实例,对 Apache DolphinScheduler 服务可用性和提交任务的 Hadoop 集群造成影响。

基于此,团队决定将 DolphinScheduler 中定时任务配置模块的 Crontab 表达式做限制,从平台侧杜绝此类事件发生

方案

我们的方案是从前后端双方面限制 Crontab 表达式的第一位:

  • 前端配置选择不提供“每一秒钟”选项
  • 服务端接口判断第一位为 * 时,返回错误

前端修改

在前端项目中,秒、分、时 均为统一模版(CrontabTime),因此新增 dolphinscheduler-ui/src/components/crontab/modules/second.tsx

只保留两种模式:intervalTimespecificTime

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import _ from 'lodash'
import { defineComponent, onMounted, PropType, ref, toRefs, watch } from 'vue'
import { NInputNumber, NRadio, NRadioGroup, NSelect } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { ICrontabI18n } from '../types'
import { isStr, specificList } from '../common'
import styles from '../index.module.scss'

const props = {
    timeMin: {
        type: Number as PropType<number>,
        default: 0
    },
    timeMax: {
        type: Number as PropType<number>,
        default: 60
    },
    intervalPerform: {
        type: Number as PropType<number>,
        default: 5
    },
    intervalStart: {
        type: Number as PropType<number>,
        default: 3
    },
    timeSpecial: {
        type: Number as PropType<number | string>,
        default: 60
    },
    timeValue: {
        type: String as PropType<string>,
        default: '*'
    },
    timeI18n: {
        type: Object as PropType<ICrontabI18n>,
        require: true
    }
}

export default defineComponent({
    name: 'CrontabSecond',
    props,
    emits: ['update:timeValue'],
    setup(props, ctx) {
        const options = Array.from({ length: 60 }, (x, i) => ({
            label: i.toString(),
            value: i
        }))

        const timeRef = ref()
        const radioRef = ref()
        const intervalStartRef = ref(props.intervalStart)
        const intervalPerformRef = ref(props.intervalPerform)
        const specificTimesRef = ref<Array<number>>([])

        /**
         * Parse parameter value
         */
        const analyticalValue = () => {
            const $timeVal = props.timeValue
            // Interval time
            const $interval = isStr($timeVal, '/')
            // Specific time
            const $specific = isStr($timeVal, ',')

            // Positive integer (times)
            if (
                ($timeVal.length === 1 ||
                    $timeVal.length === 2 ||
                    $timeVal.length === 4) &&
                _.isInteger(parseInt($timeVal))
            ) {
                radioRef.value = 'specificTime'
                specificTimesRef.value = [parseInt($timeVal)]
                return
            }

            // Interval times
            if ($interval) {
                radioRef.value = 'intervalTime'
                intervalStartRef.value = parseInt($interval[0])
                intervalPerformRef.value = parseInt($interval[1])
                timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
                return
            }

            // Specific times
            if ($specific) {
                radioRef.value = 'specificTime'
                specificTimesRef.value = $specific.map((item) => parseInt(item))
                return
            }
        }

        // Interval start time(1)
        const onIntervalStart = (value: number | null) => {
            intervalStartRef.value = value || 0
            if (radioRef.value === 'intervalTime') {
                timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
            }
        }

        // Interval execution time(2)
        const onIntervalPerform = (value: number | null) => {
            intervalPerformRef.value = value || 0
            if (radioRef.value === 'intervalTime') {
                timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
            }
        }

        // Specific time
        const onSpecificTimes = (arr: Array<number>) => {
            specificTimesRef.value = arr
            if (radioRef.value === 'specificTime') {
                specificReset()
            }
        }

        // Reset interval time
        const intervalReset = () => {
            timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
        }

        // Reset specific time
        const specificReset = () => {
            let timeValue = '0'
            if (specificTimesRef.value.length) {
                timeValue = specificTimesRef.value.join(',')
            }
            timeRef.value = timeValue
        }

        const updateRadioTime = (value: string) => {
            switch (value) {
                case 'intervalTime':
                    intervalReset()
                    break
                case 'specificTime':
                    specificReset()
                    break
            }
        }

        watch(
            () => timeRef.value,
            () => ctx.emit('update:timeValue', timeRef.value.toString())
        )

        onMounted(() => analyticalValue())

        return {
            options,
            radioRef,
            intervalStartRef,
            intervalPerformRef,
            specificTimesRef,
            updateRadioTime,
            onIntervalStart,
            onIntervalPerform,
            onSpecificTimes,
            ...toRefs(props)
        }
    },
    render() {
        const { t } = useI18n()

        return (
            <NRadioGroup
                v-model:value={this.radioRef}
                onUpdateValue={this.updateRadioTime}
            >
                <div class={styles['crontab-list']}>
                    <NRadio value={'intervalTime'} />
                    <div class={styles['crontab-list-item']}>
                        <div class={styles['item-text']}>{t(this.timeI18n!.every)}</div>
                        <div class={styles['number-input']}>
                            <NInputNumber
                                defaultValue={5}
                                min={this.timeMin}
                                max={this.timeMax}
                                v-model:value={this.intervalPerformRef}
                                onUpdateValue={this.onIntervalPerform}
                            />
                        </div>
                        <div class={styles['item-text']}>
                                {t(this.timeI18n!.timeCarriedOut)}
                        </div>
                        <div class={styles['number-input']}>
                            <NInputNumber
                                defaultValue={3}
                                min={this.timeMin}
                                max={this.timeMax}
                                v-model:value={this.intervalStartRef}
                                onUpdateValue={this.onIntervalStart}
                            />
                        </div>
                        <div class={styles['item-text']}>{t(this.timeI18n!.timeStart)}</div>
                    </div>
                </div>
                <div class={styles['crontab-list']}>
                    <NRadio value={'specificTime'} />
                    <div class={styles['crontab-list-item']}>
                        <div>{t(this.timeI18n!.specificTime)}</div>
                        <div class={styles['select-input']}>
                            <NSelect
                                multiple
                                options={specificList[this.timeSpecial]}
                                placeholder={t(this.timeI18n!.specificTimeTip)}
                                v-model:value={this.specificTimesRef}
                                onUpdateValue={this.onSpecificTimes}
                            />
                        </div>
                    </div>
                </div>
            </NRadioGroup>
        )
    }
})

服务端

添加Crontab表达式检验(有两处:一处是新增Post接口、另一处是修改PUT接口),直接添加个检测方法供这两处调用:

        if (scheduleParam.getCrontab().startsWith("*")) {
            logger.error("The crontab must not start with *");
            putMsg(result, Status.CRONTAB_EVERY_SECOND_ERROR);
            return result;
        }

本文完!

本文由 白鲸开源科技 提供发布支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934543.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MTK Android12 开机向导

文章目录 需求-场景参考资料&#xff1a;博客资料官网参考资料&#xff1a;参考资料注意点 附件资料文件说明&#xff1a;推荐工具&#xff1a;配置定制的 声明叠加层 APK需求实现替换字符、删减开机向导界面、添加开机向导界面删除部分界面需求&#xff0c;官网说明如下更新部…

Text2SQL(NL2sql)对话数据库:设计、实现细节与挑战

Text2SQL&#xff08;NL2sql&#xff09;对话数据库&#xff1a;设计、实现细节与挑战 前言1.何为Text2SQL&#xff08;NL2sql&#xff09;2.Text2SQL结构与挑战3.金融领域实际业务场景4.注意事项5.总结 前言 随着信息技术的迅猛发展&#xff0c;人机交互的方式也在不断演进。…

长沙数字孪生工业互联网三维可视化技术,赋能新型工业化智能制造工厂

长沙正积极拥抱数字化转型的浪潮&#xff0c;特别是在工业互联网和智能制造领域&#xff0c;长沙数字孪生技术的广泛应用&#xff0c;为新型工业化智能制造工厂的建设与发展注入了强劲动力。 在长沙智能制造工厂内&#xff0c;三维可视化技术被广泛应用于产线设计仿真优化和产…

FPGA工作原理、架构及底层资源

FPGA工作原理、架构及底层资源 文章目录 FPGA工作原理、架构及底层资源前言一、FPGA工作原理二、FPGA架构及底层资源 1.FPGA架构2.FPGA底层资源 2.1可编程输入/输出单元简称&#xff08;IOB&#xff09;2.2可配置逻辑块2.3丰富的布线资源2.4数字时钟管理模块(DCM)2.5嵌入式块 …

计算机网络期末常见问答题总结

1、试说明为什么在运输建立时使用三报文握手&#xff0c;如果不采用三报文握手会出现什么情况&#xff1f; TCP三次握手的目的是确保客户端和服务器都能够接收对方的连接请求,并建立起可靠的连接。如果只进行两次握手,可能会导致以下情况的发生: - 如果客户端发送的SYN包在网…

Redis篇-4--原理篇3--Redis发布/订阅(Pub/Sub)

1、概述 Redis 发布/订阅&#xff08;Publish/Subscribe&#xff0c;简称 Pub/Sub&#xff09;是一种消息传递模式&#xff0c;允许客户端订阅一个或多个通道&#xff08;channel&#xff09;&#xff0c;并接收其他客户端发布到这些通道的消息。 2、Redis 发布/订阅的主要概…

第六届地博会世界酒中国菜助力广州龙美地标美食公司推动地标发展

第六届知交会暨地博会&#xff1a;世界酒中国菜助力广州龙美地标美食公司推动地标产品创新发展 2024年12月9日至11日&#xff0c;第六届粤港澳大湾区知识产权交易博览会暨国际地理标志产品交易博览会在中新广州知识城盛大启幕。本届盛会吸引了全球众多知识产权领域的专业人士和…

【CSS in Depth 2 精译_074】第 12 章 CSS 排版与间距概述 + 12.1 间距设置(下):行内元素的间距设置

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 12 章 CSS 排版与间距】 ✔️ 12.1 间距设置 12.1.1 使用 em 还是 px12.1.2 对行高的深入思考12.1.3 行内元素的间距设置 ✔️ 12.2 Web 字体12.3 谷歌字体 文章目…

基于FPGA的智能电子密码指纹锁(开源全免)

基于FPGA的智能电子密码指纹锁 一、功能描述硬件资源需求 二、整体框架知识准备AS608指纹模块4*4数字键盘模块 三、Verilog代码实现以及仿真验证1.AS608_data模块2.check_hand模块3.four_four_key模块4.check_mima模块5.change_mima模块6.seg_ctrl模块7.uart_top模块8.key_debo…

汽车网络安全 -- IDPS如何帮助OEM保证车辆全生命周期的信息安全

目录 1.强标的另一层解读 2.什么是IDPS 2.1 IDPS技术要点 2.2 车辆IDPS系统示例 3.车辆纵深防御架构 4.小结 1.强标的另一层解读 在最近发布的国家汽车安全强标《GB 44495》,在7.2节明确提出了12条关于通信安全的要求,分别涉及到车辆与车辆制造商云平台通信、车辆与车辆…

【C++】快速排序详解与优化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;快速排序的核心思想1. 算法原理2. 算法复杂度分析时间复杂度空间复杂度 &#x1f4af;快速排序的代码实现与解析代码实现代码解析1. 递归终止条件2. 动态分配子数组3. 分区…

Redis从入门到进阶(总结)

以下内容均以CentOS7为背景。 一、Redis安装及启动 mysql&#xff08;读&#xff1a;2000/s&#xff1b;写&#xff1a;600/s&#xff09; redis&#xff08;读&#xff1a;10w/s&#xff1b;写&#xff1a;8w/s&#xff09;通过官方给出的数据单机并发可以达到10w/s&#xf…

设计模式——单例模式和工厂模式

单例模式:一个类只创建一个类对象&#xff08;节省内存减少类对象数量,一个类对象多次重复使用&#xff09; 格式: class a: pass ba() z1b z2b #z1和z2为同一个类对象 工厂模式&#xff1a;&#xff08;大批量创建具体的类对象,统一类对象入口便于维护&#xf…

Flume基础概念

目录 作用组件构成ClientFlowAgentSourceSinkEvent 和Log4j的区别与定位事务传出流程输入到sourcesource端输入Channel 接收输入到SinkSink输出 作用 Flume可以从各种来源&#xff08;如日志文件、消息队列、网络数据、文件系统、数据库等&#xff09;收集数据&#xff0c;并将…

PHP语法学习(第七天)-循环语句,魔术常量

老套路了&#xff0c;朋友们&#xff0c;先回忆昨天讲的内容PHP语法学习(第六天)主要讲了PHP中的if…else语句、关联数组以及数组排序。 想要学习更多PHP语法相关内容点击“PHP专栏&#xff01;” 下列代码都是在PHP在线测试运行环境中得到的&#xff01;&#xff01; 还记得电…

生成:安卓证书uniapp

地址&#xff1a; https://ask.dcloud.net.cn/article/35777 // 使用keytool -genkey命令生成证书&#xff1a; 官网&#xff1a; keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore ----------------------------------…

Vue.js:代码架构组成与布局设置

前言&#xff1a;最近在弄一个开源的管理系统项目&#xff0c;前后端分离开发&#xff0c;这里对前端的Vue框架做一个总结&#xff0c;有遗漏和错误的地方欢迎大家指出~ &#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介…

【优选算法-滑动窗口】长度最小的子数组、无重复字符的最长子串、最大连续1的个数、将x减为0的最小操作数、水果成篮

一、长度最小的子数组 题目链接&#xff1a; 209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09; 题目介绍&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, .…

appium学习之二:adb命令

1、查看设备 adb devices 2、连接 adb connect IP:端口 3、安装 adb install xxx.apk 4、卸载 adb uninstall 【包名】 5、把对应目录下的1.txt文件传到手机sdcard下 adb push 1.txt /sdcard 6、进入对应的设备里 adb shell 7、切入sdcard目录 cd /sdcard 8、ls 查…

算法——差分

差分可以看作是前缀和的逆运算&#xff0c;前缀和可以帮我们快速得到某个区间的和&#xff0c;而差分就是我们将原数组看作是一个前缀和数组&#xff08;q[]&#xff09;我们去构造一个差分数组&#xff08;c[]&#xff09; 一维差分 使存在如下关系&#xff1a; q[i] c[1]…