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

打印 上一主题 下一主题

主题 1764|帖子 1764|积分 5292

背景

Apache DolphinScheduler 定时任务配置采用的 7 位 Crontab 表达式,分别对应秒、分、时、月天、月、周天、年。
在团队一样平常开发工作中,工作流的定时调度一样平常不会细化到秒级别。但汗青上出现过因配置的疏忽大意而产生故障时间,如应该配置每分钟实验的工作流被配置长了每秒实验,造成短时间内产生大量工作流实例,对 Apache DolphinScheduler 服务可用性和提交任务的 Hadoop 集群造成影响。

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

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

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

在前端项目中,秒、分、时 均为同一模版(CrontabTime),因此新增 dolphinscheduler-ui/src/components/crontab/modules/second.tsx
只保存两种模式:intervalTime 和 specificTime
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements.  See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License.  You may obtain a copy of the License at
  8. *
  9. *    http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. import _ from 'lodash'
  18. import { defineComponent, onMounted, PropType, ref, toRefs, watch } from 'vue'
  19. import { NInputNumber, NRadio, NRadioGroup, NSelect } from 'naive-ui'
  20. import { useI18n } from 'vue-i18n'
  21. import { ICrontabI18n } from '../types'
  22. import { isStr, specificList } from '../common'
  23. import styles from '../index.module.scss'
  24. const props = {
  25.     timeMin: {
  26.         type: Number as PropType<number>,
  27.         default: 0
  28.     },
  29.     timeMax: {
  30.         type: Number as PropType<number>,
  31.         default: 60
  32.     },
  33.     intervalPerform: {
  34.         type: Number as PropType<number>,
  35.         default: 5
  36.     },
  37.     intervalStart: {
  38.         type: Number as PropType<number>,
  39.         default: 3
  40.     },
  41.     timeSpecial: {
  42.         type: Number as PropType<number | string>,
  43.         default: 60
  44.     },
  45.     timeValue: {
  46.         type: String as PropType<string>,
  47.         default: '*'
  48.     },
  49.     timeI18n: {
  50.         type: Object as PropType<ICrontabI18n>,
  51.         require: true
  52.     }
  53. }
  54. export default defineComponent({
  55.     name: 'CrontabSecond',
  56.     props,
  57.     emits: ['update:timeValue'],
  58.     setup(props, ctx) {
  59.         const options = Array.from({ length: 60 }, (x, i) => ({
  60.             label: i.toString(),
  61.             value: i
  62.         }))
  63.         const timeRef = ref()
  64.         const radioRef = ref()
  65.         const intervalStartRef = ref(props.intervalStart)
  66.         const intervalPerformRef = ref(props.intervalPerform)
  67.         const specificTimesRef = ref<Array<number>>([])
  68.         /**
  69.          * Parse parameter value
  70.          */
  71.         const analyticalValue = () => {
  72.             const $timeVal = props.timeValue
  73.             // Interval time
  74.             const $interval = isStr($timeVal, '/')
  75.             // Specific time
  76.             const $specific = isStr($timeVal, ',')
  77.             // Positive integer (times)
  78.             if (
  79.                 ($timeVal.length === 1 ||
  80.                     $timeVal.length === 2 ||
  81.                     $timeVal.length === 4) &&
  82.                 _.isInteger(parseInt($timeVal))
  83.             ) {
  84.                 radioRef.value = 'specificTime'
  85.                 specificTimesRef.value = [parseInt($timeVal)]
  86.                 return
  87.             }
  88.             // Interval times
  89.             if ($interval) {
  90.                 radioRef.value = 'intervalTime'
  91.                 intervalStartRef.value = parseInt($interval[0])
  92.                 intervalPerformRef.value = parseInt($interval[1])
  93.                 timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
  94.                 return
  95.             }
  96.             // Specific times
  97.             if ($specific) {
  98.                 radioRef.value = 'specificTime'
  99.                 specificTimesRef.value = $specific.map((item) => parseInt(item))
  100.                 return
  101.             }
  102.         }
  103.         // Interval start time(1)
  104.         const onIntervalStart = (value: number | null) => {
  105.             intervalStartRef.value = value || 0
  106.             if (radioRef.value === 'intervalTime') {
  107.                 timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
  108.             }
  109.         }
  110.         // Interval execution time(2)
  111.         const onIntervalPerform = (value: number | null) => {
  112.             intervalPerformRef.value = value || 0
  113.             if (radioRef.value === 'intervalTime') {
  114.                 timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
  115.             }
  116.         }
  117.         // Specific time
  118.         const onSpecificTimes = (arr: Array<number>) => {
  119.             specificTimesRef.value = arr
  120.             if (radioRef.value === 'specificTime') {
  121.                 specificReset()
  122.             }
  123.         }
  124.         // Reset interval time
  125.         const intervalReset = () => {
  126.             timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
  127.         }
  128.         // Reset specific time
  129.         const specificReset = () => {
  130.             let timeValue = '0'
  131.             if (specificTimesRef.value.length) {
  132.                 timeValue = specificTimesRef.value.join(',')
  133.             }
  134.             timeRef.value = timeValue
  135.         }
  136.         const updateRadioTime = (value: string) => {
  137.             switch (value) {
  138.                 case 'intervalTime':
  139.                     intervalReset()
  140.                     break
  141.                 case 'specificTime':
  142.                     specificReset()
  143.                     break
  144.             }
  145.         }
  146.         watch(
  147.             () => timeRef.value,
  148.             () => ctx.emit('update:timeValue', timeRef.value.toString())
  149.         )
  150.         onMounted(() => analyticalValue())
  151.         return {
  152.             options,
  153.             radioRef,
  154.             intervalStartRef,
  155.             intervalPerformRef,
  156.             specificTimesRef,
  157.             updateRadioTime,
  158.             onIntervalStart,
  159.             onIntervalPerform,
  160.             onSpecificTimes,
  161.             ...toRefs(props)
  162.         }
  163.     },
  164.     render() {
  165.         const { t } = useI18n()
  166.         return (
  167.             <NRadioGroup
  168.                 v-model:value={this.radioRef}
  169.                 onUpdateValue={this.updateRadioTime}
  170.             >
  171.                
  172.                     <NRadio value={'intervalTime'} />
  173.                     
  174.                         {t(this.timeI18n!.every)}
  175.                         
  176.                             <NInputNumber
  177.                                 defaultValue={5}
  178.                                 min={this.timeMin}
  179.                                 max={this.timeMax}
  180.                                 v-model:value={this.intervalPerformRef}
  181.                                 onUpdateValue={this.onIntervalPerform}
  182.                             />
  183.                         
  184.                         
  185.                                 {t(this.timeI18n!.timeCarriedOut)}
  186.                         
  187.                         
  188.                             <NInputNumber
  189.                                 defaultValue={3}
  190.                                 min={this.timeMin}
  191.                                 max={this.timeMax}
  192.                                 v-model:value={this.intervalStartRef}
  193.                                 onUpdateValue={this.onIntervalStart}
  194.                             />
  195.                         
  196.                         {t(this.timeI18n!.timeStart)}
  197.                     
  198.                
  199.                
  200.                     <NRadio value={'specificTime'} />
  201.                     
  202.                         {t(this.timeI18n!.specificTime)}
  203.                         
  204.                             <NSelect
  205.                                 multiple
  206.                                 options={specificList[this.timeSpecial]}
  207.                                 placeholder={t(this.timeI18n!.specificTimeTip)}
  208.                                 v-model:value={this.specificTimesRef}
  209.                                 onUpdateValue={this.onSpecificTimes}
  210.                             />
  211.                         
  212.                     
  213.                
  214.             </NRadioGroup>
  215.         )
  216.     }
  217. })
复制代码
服务端

添加Crontab表达式检验(有两处:一处是新增Post接口、另一处是修改PUT接口),直接添加个检测方法供这两处调用:
  1.         if (scheduleParam.getCrontab().startsWith("*")) {
  2.             logger.error("The crontab must not start with *");
  3.             putMsg(result, Status.CRONTAB_EVERY_SECOND_ERROR);
  4.             return result;
  5.         }
复制代码
本文完!
本文由 白鲸开源 提供发布支持!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表