All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vip.saturn.job.console.service.impl.JobServiceImpl Maven / Gradle / Ivy

/**
 * Copyright 1999-2015 dangdang.com.
 * 

* Licensed 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. *

*/ package com.vip.saturn.job.console.service.impl; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.vip.saturn.job.console.domain.*; import com.vip.saturn.job.console.domain.ExecutionInfo.ExecutionStatus; import com.vip.saturn.job.console.exception.SaturnJobConsoleException; import com.vip.saturn.job.console.exception.SaturnJobConsoleHttpException; import com.vip.saturn.job.console.mybatis.entity.JobConfig4DB; import com.vip.saturn.job.console.mybatis.service.CurrentJobConfigService; import com.vip.saturn.job.console.repository.zookeeper.CuratorRepository; import com.vip.saturn.job.console.repository.zookeeper.CuratorRepository.CuratorFrameworkOp; import com.vip.saturn.job.console.service.JobService; import com.vip.saturn.job.console.service.RegistryCenterService; import com.vip.saturn.job.console.service.SystemConfigService; import com.vip.saturn.job.console.service.helper.SystemConfigProperties; import com.vip.saturn.job.console.utils.*; import com.vip.saturn.job.console.vo.GetJobConfigVo; import com.vip.saturn.job.sharding.node.SaturnExecutorsNode; import jxl.Cell; import jxl.CellType; import jxl.Sheet; import jxl.Workbook; import jxl.write.*; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.zookeeper.data.Stat; import org.codehaus.jackson.map.type.MapType; import org.codehaus.jackson.map.type.TypeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; import java.lang.Boolean; import java.text.ParseException; import java.util.*; import java.util.regex.Pattern; import static com.vip.saturn.job.console.exception.SaturnJobConsoleException.ERROR_CODE_BAD_REQUEST; import static com.vip.saturn.job.console.exception.SaturnJobConsoleException.ERROR_CODE_NOT_EXISTED; public class JobServiceImpl implements JobService { public static final String CONFIG_ITEM_LOAD_LEVEL = "loadLevel"; public static final String CONFIG_ITEM_ENABLED = "enabled"; public static final String CONFIG_ITEM_DESCRIPTION = "description"; public static final String CONFIG_ITEM_CUSTOM_CONTEXT = "customContext"; public static final String CONFIG_ITEM_JOB_TYPE = "jobType"; public static final String CONFIG_ITEM_JOB_MODE = "jobMode"; public static final String CONFIG_ITEM_SHARDING_ITEM_PARAMETERS = "shardingItemParameters"; public static final String CONFIG_ITEM_JOB_PARAMETER = "jobParameter"; public static final String CONFIG_ITEM_QUEUE_NAME = "queueName"; public static final String CONFIG_ITEM_CHANNEL_NAME = "channelName"; public static final String CONFIG_ITEM_FAILOVER = "failover"; public static final String CONFIG_ITEM_MONITOR_EXECUTION = "monitorExecution"; public static final String CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS = "timeout4AlarmSeconds"; public static final String CONFIG_ITEM_TIMEOUT_SECONDS = "timeoutSeconds"; public static final String CONFIG_ITEM_TIME_ZONE = "timeZone"; public static final String CONFIG_ITEM_CRON = "cron"; public static final String CONFIG_ITEM_PAUSE_PERIOD_DATE = "pausePeriodDate"; public static final String CONFIG_ITEM_PAUSE_PERIOD_TIME = "pausePeriodTime"; public static final String CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS = "processCountIntervalSeconds"; public static final String CONFIG_ITEM_SHARDING_TOTAL_COUNT = "shardingTotalCount"; public static final String CONFIG_ITEM_SHOW_NORMAL_LOG = "showNormalLog"; public static final String CONFIG_ITEM_JOB_DEGREE = "jobDegree"; public static final String CONFIG_ITEM_ENABLED_REPORT = "enabledReport"; public static final String CONFIG_ITEM_PREFER_LIST = "preferList"; public static final String CONFIG_ITEM_USE_DISPREFER_LIST = "useDispreferList"; public static final String CONFIG_ITEM_LOCAL_MODE = "localMode"; public static final String CONFIG_ITEM_USE_SERIAL = "useSerial"; public static final String CONFIG_ITEM_DEPENDENCIES = "dependencies"; public static final String CONFIG_ITEM_GROUPS = "groups"; public static final String CONFIG_ITEM_JOB_CLASS = "jobClass"; public static final String CONFIG_ITEM_RERUN = "rerun"; public static final String CONFIG_ITEM_DOWNSTREAM = "downStream"; public static final String CONFIG_ITEM_UPSTREAM = "upStream"; private static final Logger log = LoggerFactory.getLogger(JobServiceImpl.class); private static final int DEFAULT_MAX_JOB_NUM = 100; private static final int DEFAULT_INTERVAL_TIME_OF_ENABLED_REPORT = 5; // 最大允许显示的job log为zk默认的max jute buffer size private static final int DEFAULT_MAX_ZNODE_DATA_LENGTH = 1048576; private static final String ERR_MSG_PENDING_STATUS = "job:[{}] item:[{}] on executor:[{}] execution status is " + "PENDING as {}"; private static final String ERR_MSG_TOO_LONG_TO_DISPLAY = "Not display the log as the length is out of max length"; @Resource private RegistryCenterService registryCenterService; @Resource private CurrentJobConfigService currentJobConfigService; @Resource private SystemConfigService systemConfigService; private Random random = new Random(); private MapType customContextType = TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class); private JobStatus getJobStatus(final String jobName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp, boolean enabled) { // see if all the shards is finished. boolean isAllShardsFinished = isAllShardsFinished(jobName, curatorFrameworkOp); if (enabled) { if (isAllShardsFinished) { return JobStatus.READY; } return JobStatus.RUNNING; } else { if (isAllShardsFinished) { return JobStatus.STOPPED; } return JobStatus.STOPPING; } } private boolean isAllShardsFinished(final String jobName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { List executionItems = curatorFrameworkOp.getChildren(JobNodePath.getExecutionNodePath(jobName)); boolean isAllShardsFinished = true; if (executionItems != null && !executionItems.isEmpty()) { for (String itemStr : executionItems) { boolean isItemCompleted = curatorFrameworkOp .checkExists(JobNodePath.getExecutionNodePath(jobName, itemStr, "completed")); boolean isItemRunning = curatorFrameworkOp .checkExists(JobNodePath.getExecutionNodePath(jobName, itemStr, "running")); // if executor is kill by -9 while it is running, completed node won't exists as // well as running node. // under this circumstance, we consider it is completed. if (!isItemCompleted && isItemRunning) { isAllShardsFinished = false; break; } } } return isAllShardsFinished; } @Override public List getGroups(String namespace) throws SaturnJobConsoleException { Set groups = new HashSet<>(); List unSystemJobs = getUnSystemJobs(namespace); if (unSystemJobs != null) { for (JobConfig jobConfig : unSystemJobs) { String jobGroups = jobConfig.getGroups(); if (StringUtils.isBlank(jobGroups)) { jobGroups = SaturnConstants.NO_GROUPS_LABEL; } String[] groupArray = jobGroups.replaceAll("\\s*", "").split(","); groups.addAll(Arrays.asList(groupArray)); } } ArrayList groupList = new ArrayList<>(groups); Collections.sort(groupList); return groupList; } @Transactional(rollbackFor = Exception.class) @Override public void enableJob(String namespace, String jobName, String updatedBy) throws SaturnJobConsoleException { JobConfig4DB jobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能启用该作业(" + jobName + "),因为该作业不存在"); } if (jobConfig.getEnabled()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "该作业(" + jobName + ")已经处于启用状态"); } CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); boolean allShardsFinished = isAllShardsFinished(jobName, curatorFrameworkOp); if (!allShardsFinished) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "不能启用该作业(" + jobName + "),因为该作业不处于STOPPED状态"); } jobConfig.setEnabled(true); jobConfig.setLastUpdateTime(new Date()); jobConfig.setLastUpdateBy(updatedBy); currentJobConfigService.updateByPrimaryKey(jobConfig); curatorFrameworkOp.update(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED), true); } @Transactional(rollbackFor = Exception.class) @Override public void disableJob(String namespace, String jobName, String updatedBy) throws SaturnJobConsoleException { JobConfig4DB jobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能禁用该作业(" + jobName + "),因为该作业不存在"); } if (!jobConfig.getEnabled()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "该作业(" + jobName + ")已经处于禁用状态"); } jobConfig.setEnabled(Boolean.FALSE); jobConfig.setLastUpdateTime(new Date()); jobConfig.setLastUpdateBy(updatedBy); currentJobConfigService.updateByPrimaryKey(jobConfig); CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); curatorFrameworkOp.update(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED), false); } @Transactional(rollbackFor = Exception.class) @Override public void removeJob(String namespace, String jobName) throws SaturnJobConsoleException { JobConfig4DB jobConfig4DB = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig4DB == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能删除该作业(" + jobName + "),因为该作业不存在"); } String upStream = jobConfig4DB.getUpStream(); if (StringUtils.isNotBlank(upStream)) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能删除该作业(" + jobName + "),因为该作业存在上游作业(" + upStream + "),请先断开上下游关系再删除"); } String downStream = jobConfig4DB.getDownStream(); if (StringUtils.isNotBlank(downStream)) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能删除该作业(" + jobName + "),因为该作业存在下游作业(" + downStream + "),请先断开上下游关系再删除"); } CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); JobStatus jobStatus = getJobStatus(jobName, curatorFrameworkOp, jobConfig4DB.getEnabled()); if (JobStatus.STOPPED != jobStatus) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("不能删除该作业(%s),因为该作业不处于STOPPED状态", jobName)); } Stat stat = curatorFrameworkOp.getStat(JobNodePath.getJobNodePath(jobName)); if (stat != null) { long createTimeDiff = System.currentTimeMillis() - stat.getCtime(); if (createTimeDiff < SaturnConstants.JOB_CAN_BE_DELETE_TIME_LIMIT) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("不能删除该作业(%s),因为该作业创建时间距离现在不超过%d分钟", jobName, SaturnConstants.JOB_CAN_BE_DELETE_TIME_LIMIT / 60000)); } } // remove job from db currentJobConfigService.deleteByPrimaryKey(jobConfig4DB.getId()); // remove job from zk removeJobFromZk(jobName, curatorFrameworkOp); } /** * 删除zk上的作业结点。先持久化config/toDelete结点,让executor收到该事件,shutdown自身的该作业。如果所有executor都已经shutdown该作业,则才可以安全删除作业结点。 * * @return 等待executor shutdown作业,等待一定时间后,如果executor还没完全shutdown,则放弃等待,返回false。 * 否则,在等待时间内,executor都shutdown完全,则删除作业结点,并返回true。 */ private boolean removeJobFromZk(String jobName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) throws SaturnJobConsoleException { // 1.作业的executor全online的情况,添加toDelete节点,触发监听器动态删除节点 String toDeleteNodePath = JobNodePath.getConfigNodePath(jobName, "toDelete"); if (curatorFrameworkOp.checkExists(toDeleteNodePath)) { curatorFrameworkOp.deleteRecursive(toDeleteNodePath); } curatorFrameworkOp.create(toDeleteNodePath); for (int i = 0; i < 20; i++) { // 2.作业的executor全offline的情况,或有几个online,几个offline的情况 String jobServerPath = JobNodePath.getServerNodePath(jobName); if (!curatorFrameworkOp.checkExists(jobServerPath)) { // (1)如果不存在$Job/JobName/servers节点,说明该作业没有任何executor接管,可直接删除作业节点 curatorFrameworkOp.deleteRecursive(JobNodePath.getJobNodePath(jobName)); return true; } // (2)如果该作业servers下没有任何executor,可直接删除作业节点 List executors = curatorFrameworkOp.getChildren(jobServerPath); if (CollectionUtils.isEmpty(executors)) { curatorFrameworkOp.deleteRecursive(JobNodePath.getJobNodePath(jobName)); return true; } // (3)只要该作业没有一个能运行的该作业的executor在线,那么直接删除作业节点 boolean hasOnlineExecutor = false; for (String executor : executors) { if (curatorFrameworkOp.checkExists(ExecutorNodePath.getExecutorNodePath(executor, "ip")) && curatorFrameworkOp.checkExists(JobNodePath.getServerStatus(jobName, executor))) { hasOnlineExecutor = true; } else { curatorFrameworkOp.deleteRecursive(JobNodePath.getServerNodePath(jobName, executor)); } } if (!hasOnlineExecutor) { curatorFrameworkOp.deleteRecursive(JobNodePath.getJobNodePath(jobName)); return true; } try { Thread.sleep(200); } catch (Exception e) { throw new SaturnJobConsoleException(e); } } return false; } @Override public List getCandidateExecutors(String namespace, String jobName) throws SaturnJobConsoleException { JobConfig4DB currentJobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (currentJobConfig == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能获取该作业(" + jobName + ")可选择的优先Executor,因为该作业不存在"); } List executorProvidedList = new ArrayList<>(); CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String executorsNodePath = SaturnExecutorsNode.getExecutorsNodePath(); if (!curatorFrameworkOp.checkExists(executorsNodePath)) { return executorProvidedList; } List executors = curatorFrameworkOp.getChildren(executorsNodePath); if (executors == null) { executors = new ArrayList<>(); } if (!executors.isEmpty()) { for (String executor : executors) { if (curatorFrameworkOp.checkExists(SaturnExecutorsNode.getExecutorTaskNodePath(executor))) { continue;// 过滤容器中的Executor,容器资源只需要可以选择taskId即可 } ExecutorProvided executorProvided = new ExecutorProvided(); executorProvided.setType(ExecutorProvidedType.PHYSICAL); executorProvided.setExecutorName(executor); executorProvided.setNoTraffic( curatorFrameworkOp.checkExists(SaturnExecutorsNode.getExecutorNoTrafficNodePath(executor))); String ip = curatorFrameworkOp.getData(SaturnExecutorsNode.getExecutorIpNodePath(executor)); if (StringUtils.isNotBlank(ip)) { executorProvided.setStatus(ExecutorProvidedStatus.ONLINE); executorProvided.setIp(ip); } else { executorProvided.setStatus(ExecutorProvidedStatus.OFFLINE); } executorProvidedList.add(executorProvided); } } List dockerExecutorProvided = getContainerTaskIds(curatorFrameworkOp); executorProvidedList.addAll(dockerExecutorProvided); if (StringUtils.isBlank(jobName)) { return executorProvidedList; } String preferListNodePath = JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST); if (!curatorFrameworkOp.checkExists(preferListNodePath)) { return executorProvidedList; } String preferList = curatorFrameworkOp.getData(preferListNodePath); if (Strings.isNullOrEmpty(preferList)) { return executorProvidedList; } handlerPreferListString(curatorFrameworkOp, preferList, executors, dockerExecutorProvided, executorProvidedList); return executorProvidedList; } private void handlerPreferListString(CuratorRepository.CuratorFrameworkOp curatorFrameworkOp, String preferList, List executors, List dockerExecutorProvided, List executorProvidedList) { String[] preferExecutorList = preferList.split(","); for (String preferExecutor : preferExecutorList) { if (!preferExecutor.startsWith("@")) { if (!executors.contains(preferExecutor)) { ExecutorProvided executorProvided = new ExecutorProvided(); executorProvided.setExecutorName(preferExecutor); executorProvided.setType(ExecutorProvidedType.PHYSICAL); executorProvided.setStatus(ExecutorProvidedStatus.DELETED); executorProvided.setNoTraffic(curatorFrameworkOp .checkExists(SaturnExecutorsNode.getExecutorNoTrafficNodePath(preferExecutor))); executorProvidedList.add(executorProvided); } } else { String executorName = preferExecutor.substring(1); boolean include = false; for (ExecutorProvided executorProvided : dockerExecutorProvided) { if (executorProvided.getExecutorName().equals(executorName)) { include = true; break; } } if (!include) { ExecutorProvided executorProvided = new ExecutorProvided(); executorProvided.setExecutorName(executorName); executorProvided.setType(ExecutorProvidedType.DOCKER); executorProvided.setStatus(ExecutorProvidedStatus.DELETED); executorProvidedList.add(executorProvided); } } } } /** * 先获取DCOS节点下的taskID节点;如果没有此节点,则尝试从executor节点下获取; *

* 不存在既有DCOS容器,又有K8S容器的模式。 */ protected List getContainerTaskIds(CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { List executorProvidedList = new ArrayList<>(); List containerTaskIds = getDCOSContainerTaskIds(curatorFrameworkOp); if (CollectionUtils.isEmpty(containerTaskIds)) { containerTaskIds = getK8SContainerTaskIds(curatorFrameworkOp); } if (!CollectionUtils.isEmpty(containerTaskIds)) { for (String task : containerTaskIds) { ExecutorProvided executorProvided = new ExecutorProvided(); executorProvided.setExecutorName(task); executorProvided.setType(ExecutorProvidedType.DOCKER); executorProvidedList.add(executorProvided); } } return executorProvidedList; } private List getDCOSContainerTaskIds(CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { List containerTaskIds = Lists.newArrayList(); String containerNodePath = ContainerNodePath.getDcosTasksNodePath(); if (curatorFrameworkOp.checkExists(containerNodePath)) { containerTaskIds = curatorFrameworkOp.getChildren(containerNodePath); } return containerTaskIds; } private List getK8SContainerTaskIds(CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { List taskIds = new ArrayList<>(); String executorsNodePath = SaturnExecutorsNode.getExecutorsNodePath(); List executors = curatorFrameworkOp.getChildren(executorsNodePath); if (executors != null) { for (String executor : executors) { String executorTaskNodePath = SaturnExecutorsNode.getExecutorTaskNodePath(executor); if (curatorFrameworkOp.checkExists(executorTaskNodePath)) { String taskId = curatorFrameworkOp.getData(executorTaskNodePath); if (taskId != null && !taskIds.contains(taskId)) { taskIds.add(taskId); } } } } return taskIds; } @Transactional(rollbackFor = Exception.class) @Override public void setPreferList(String namespace, String jobName, String preferList, String updatedBy) throws SaturnJobConsoleException { // save to db JobConfig4DB oldJobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (oldJobConfig == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "设置该作业(" + jobName + ")优先Executor失败,因为该作业不存在"); } // 启用状态的本地模式作业,不能设置preferList Boolean enabled = oldJobConfig.getEnabled(); Boolean localMode = oldJobConfig.getLocalMode(); if (enabled != null && enabled && localMode != null && localMode) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("启用状态的本地模式作业(%s),不能设置优先Executor,请先禁用它", jobName)); } JobConfig4DB newJobConfig = new JobConfig4DB(); BeanUtils.copyProperties(oldJobConfig, newJobConfig); newJobConfig.setPreferList(preferList); currentJobConfigService.updateNewAndSaveOld2History(newJobConfig, oldJobConfig, updatedBy); // save to zk CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String jobConfigPreferListNodePath = SaturnExecutorsNode.getJobConfigPreferListNodePath(jobName); curatorFrameworkOp.update(jobConfigPreferListNodePath, preferList); // delete and create the forceShard node String jobConfigForceShardNodePath = SaturnExecutorsNode.getJobConfigForceShardNodePath(jobName); curatorFrameworkOp.delete(jobConfigForceShardNodePath); curatorFrameworkOp.create(jobConfigForceShardNodePath); } @Override public List getCandidateUpStream(String namespace) throws SaturnJobConsoleException { List candidateDownStream = new ArrayList<>(); List unSystemJobs = getUnSystemJobs(namespace); for (JobConfig temp : unSystemJobs) { if (canBeUpStream(temp)) { candidateDownStream.add(temp.getJobName()); } } Collections.sort(candidateDownStream); return candidateDownStream; } @Override public List getCandidateDownStream(String namespace) throws SaturnJobConsoleException { List candidateDownStream = new ArrayList<>(); List unSystemJobs = getUnSystemJobs(namespace); for (JobConfig temp : unSystemJobs) { if (canBeDownStream(temp)) { candidateDownStream.add(temp.getJobName()); } } Collections.sort(candidateDownStream); return candidateDownStream; } private void validateJobConfig(String namespace, JobConfig jobConfig, List unSystemJobs, Set streamChangedJobs) throws SaturnJobConsoleException { // 作业名必填 String jobName = jobConfig.getJobName(); if (StringUtils.isBlank(jobName)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业名必填"); } // 作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_ if (!jobName.matches("[0-9a-zA-Z_]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_"); } // 依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号, if (jobConfig.getDependencies() != null && !jobConfig.getDependencies().matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,"); } // 作业类型必填 if (StringUtils.isBlank(jobConfig.getJobType())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业类型必填"); } // 验证作业类型 JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (jobType == JobType.UNKNOWN_JOB) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业类型未知"); } // 如果是JAVA作业,作业实现类必填 if (JobType.isJava(jobType) && StringUtils.isBlank(jobConfig.getJobClass())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "对于java作业,作业实现类必填"); } // 如果是消息作业,queue必填 if (JobType.isMsg(jobType)) { validateQueue(jobConfig); } // 校验cron validateCronFieldOfJobConfig(jobConfig); // 校验shardingItemParameters validateShardingItemFieldOfJobConfig(jobConfig); // 校验分组 validateGroupsFieldOfJobConfig(jobConfig); // 不能添加系统作业 if (jobConfig.getJobMode() != null && jobConfig.getJobMode().startsWith(JobMode.SYSTEM_PREFIX)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业模式有误,不能添加系统作业"); } // 校验上下游作业,并联动更新其他相关联作业的上下游 validateStreamAndLinkingUpdateOtherJobs(namespace, jobConfig, unSystemJobs, streamChangedJobs); } protected void validateQueue(JobConfig jobConfig) throws SaturnJobConsoleException { if (StringUtils.isBlank(jobConfig.getQueueName())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "对于消息作业,queue必填"); } jobConfig.setQueueName(jobConfig.getQueueName().trim().replaceAll("\r\n", "")); } private void validateGroupsFieldOfJobConfig(JobConfig jobConfig) throws SaturnJobConsoleException { String groups = jobConfig.getGroups(); if (groups == null) { return; } if (groups.length() > 255) { throw new SaturnJobConsoleException("分组过长,不能超过255个字符"); } Pattern pattern = Pattern.compile("[`!@#$%^!@#¥%……&*()|{}【】‘;:”“’。,、? ]"); for (String group : groups.split(",")) { validateGroupName(group, pattern); } } private void validateGroupName(String groupName, Pattern pattern) throws SaturnJobConsoleException { if ("未分组".equals(groupName.trim())) { throw new SaturnJobConsoleException("分组名不能为:未分组"); } if (pattern.matcher(groupName).find()) { throw new SaturnJobConsoleException("分组名中不能包含特殊字符"); } } private void validateStreamAndLinkingUpdateOtherJobs(String namespace, JobConfig jobConfig, List unSystemJobs, Set streamChangedJobs) throws SaturnJobConsoleException { Set downStream = parseStreamToList(jobConfig.getDownStream()); if (!downStream.isEmpty()) { validateDownStreamBasic(jobConfig, true); } Set upStream = parseStreamToList(jobConfig.getUpStream()); if (!upStream.isEmpty()) { validateUpStreamBasic(jobConfig, true); } // 如果是添加新作业,合并jobConfig和unSystemJobs List newUnSystemJobs = new ArrayList<>(); newUnSystemJobs.addAll(unSystemJobs); boolean included = false; for (JobConfig otherJob : newUnSystemJobs) { if (otherJob.getJobName().equals(jobConfig.getJobName())) { included = true; break; } } if (!included) { newUnSystemJobs.add(jobConfig); } // 校验上游作业,更新上游作业的downStream validateAndUpdateStream(jobConfig, upStream, newUnSystemJobs, streamChangedJobs, false); // 校验下游作业,更新下游作业的upStream validateAndUpdateStream(jobConfig, downStream, newUnSystemJobs, streamChangedJobs, true); // 校验该作业处于的路径是否有环 getAncestors(namespace, jobConfig, newUnSystemJobs, new Stack(), true); // 格式化上游作业,去除多余空格 jobConfig.setUpStream(formatStream(upStream)); // 格式化下游作业,去除多余空格 jobConfig.setDownStream(formatStream(downStream)); } private Set parseStreamToList(String stream) { Set streamList = new HashSet<>(); if (StringUtils.isBlank(stream)) { return streamList; } String[] split = stream.split(","); for (String temp : split) { if (StringUtils.isNotBlank(temp)) { streamList.add(temp.trim()); } } return streamList; } private void validateDownStreamBasic(JobConfig jobConfig, boolean isCurrentJob) throws SaturnJobConsoleException { // 只能是cron/passive作业,才能配置下游 JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (!JobType.isCron(jobType) && !JobType.isPassive(jobType)) { if (isCurrentJob) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "只能是定时作业或者被动作业,才能配置下游作业"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "配置的上游作业(" + jobConfig.getJobName() + ")不是定时作业或被动作业"); } } // 不能是本地模式作业,因为本地模式不能保证分片数1 if (jobConfig.getLocalMode() != null && jobConfig.getLocalMode()) { if (isCurrentJob) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "非本地模式作业,才能配置下游作业"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "配置的上游作业(" + jobConfig.getJobName() + ")不能是本地模式作业"); } } // 只能只有一个分片,才能配置下游 if (jobConfig.getShardingTotalCount() != 1) { if (isCurrentJob) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "分片数为1,才能配置下游作业"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "配置的上游作业(" + jobConfig.getJobName() + ")分片数必须为1"); } } } private void validateUpStreamBasic(JobConfig jobConfig, boolean isCurrentJob) throws SaturnJobConsoleException { // 只能是passive作业,才能配置上游 JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (!JobType.isPassive(jobType)) { if (isCurrentJob) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "只能是被动作业,才能配置上游作业"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "配置的下游作业(" + jobConfig.getJobName() + ")不是被动作业"); } } } private void validateAndUpdateStream(JobConfig jobConfig, Set stream, List unSystemJobs, Set streamChangedJobs, boolean isDownStream) throws SaturnJobConsoleException { String jobName = jobConfig.getJobName(); for (String elem : stream) { if (elem.equals(jobName)) { if (isDownStream) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "下游作业(" + elem + ")不能是该作业本身"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "上游作业(" + elem + ")不能是该作业本身"); } } boolean found = false; for (JobConfig otherJob : unSystemJobs) { if (elem.equals(otherJob.getJobName())) { if (isDownStream) { validateUpStreamBasic(otherJob, false); otherJob.setUpStream(appendToStream(jobName, otherJob.getUpStream())); streamChangedJobs.add(otherJob); } else { validateDownStreamBasic(otherJob, false); otherJob.setDownStream(appendToStream(jobName, otherJob.getDownStream())); streamChangedJobs.add(otherJob); } found = true; break; } } if (!found) { if (isDownStream) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "下游作业(" + elem + ")不存在"); } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "上游作业(" + elem + ")不存在"); } } } for (JobConfig otherJob : unSystemJobs) { String otherJobName = otherJob.getJobName(); if (otherJobName.equals(jobName)) { continue; } if (stream.contains(otherJobName)) { continue; } if (isDownStream) { String upStream = removeFromStreamIfNecessary(jobName, otherJob.getUpStream()); if (upStream != null) { otherJob.setUpStream(upStream); streamChangedJobs.add(otherJob); } } else { String downStream = removeFromStreamIfNecessary(jobName, otherJob.getDownStream()); if (downStream != null) { otherJob.setDownStream(downStream); streamChangedJobs.add(otherJob); } } } } private String appendToStream(String jobName, String stream) { Set streamSet = parseStreamToList(stream); if (StringUtils.isNotBlank(jobName)) { streamSet.add(jobName); } return formatStream(streamSet); } private String removeFromStreamIfNecessary(String jobName, String stream) { Set streamSet = parseStreamToList(stream); if (StringUtils.isNotBlank(jobName)) { if (streamSet.remove(jobName)) { return formatStream(streamSet); } } return null; } private String formatStream(Set streamSet) { StringBuilder sb = new StringBuilder(); for (String temp : streamSet) { if (StringUtils.isNotBlank(temp)) { sb.append(temp).append(','); } } int length = sb.length(); if (length > 0) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } private Set getAncestors(String namespace, JobConfig jobConfig, List unSystemJobs, Stack onePathRecords, boolean throwExceptionWhenHasARing) throws SaturnJobConsoleException { onePathRecords.push(jobConfig.getJobName()); Set ancestors = new HashSet<>(); Set upStream = parseStreamToList(jobConfig.getUpStream()); for (String parent : upStream) { for (JobConfig otherJobConfig : unSystemJobs) { if (parent.equals(otherJobConfig.getJobName())) { if (onePathRecords.search(parent) != -1) { onePathRecords.push(parent); if (throwExceptionWhenHasARing) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该域(%s)作业编排有误,存在环: %s", namespace, onePathRecords)); } else { log.error("{} job arrange error, because it includes a ring: {}", namespace, onePathRecords); onePathRecords.pop(); } } if (!ancestors.contains(parent)) { ancestors.add(parent); ancestors.addAll(getAncestors(namespace, otherJobConfig, unSystemJobs, onePathRecords, throwExceptionWhenHasARing)); } break; } } } onePathRecords.pop(); return ancestors; } private Set getDescendants(String namespace, JobConfig jobConfig, List unSystemJobs, Stack onePathRecords, boolean throwExceptionWhenHasARing) throws SaturnJobConsoleException { onePathRecords.push(jobConfig.getJobName()); Set descendants = new HashSet<>(); Set downStream = parseStreamToList(jobConfig.getDownStream()); for (String child : downStream) { for (JobConfig otherJobConfig : unSystemJobs) { if (child.equals(otherJobConfig.getJobName())) { if (onePathRecords.search(child) != -1) { onePathRecords.push(child); if (throwExceptionWhenHasARing) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该域(%s)作业编排有误,存在环: %", namespace, onePathRecords)); } else { log.error("{} job arrange error, because it includes a ring: {}", namespace, onePathRecords); onePathRecords.pop(); } } if (!descendants.contains(child)) { descendants.add(child); descendants.addAll(getDescendants(namespace, otherJobConfig, unSystemJobs, onePathRecords, throwExceptionWhenHasARing)); } break; } } } onePathRecords.pop(); return descendants; } private void validateCronFieldOfJobConfig(JobConfig jobConfig) throws SaturnJobConsoleException { if (JobType.isCron(JobType.getJobType(jobConfig.getJobType()))) { // cron表达式必填 if (jobConfig.getCron() == null || jobConfig.getCron().trim().isEmpty()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "对于cron作业,cron表达式必填"); } // cron表达式语法验证 try { CronExpression.validateExpression(jobConfig.getCron()); } catch (ParseException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "cron表达式语法有误" + e); } } else { jobConfig.setCron(""); // 其他类型的不需要持久化保存cron表达式 } } private void validateShardingItemFieldOfJobConfig(JobConfig jobConfig) throws SaturnJobConsoleException { if (jobConfig.getLocalMode() != null && jobConfig.getLocalMode()) { if (jobConfig.getShardingItemParameters() == null) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "对于本地模式作业,分片参数必填。"); } else { String[] split = jobConfig.getShardingItemParameters().split(","); boolean includeXing = false; for (String tmp : split) { String[] split2 = tmp.split("="); if ("*".equalsIgnoreCase(split2[0].trim())) { includeXing = true; break; } } if (!includeXing) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "对于本地模式作业,分片参数必须包含如*=xx。"); } } } else { // 分片参数不能小于分片总数 if (jobConfig.getShardingTotalCount() == null || jobConfig.getShardingTotalCount() < 1) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "分片数不能为空,并且不能小于1"); } if ((jobConfig.getShardingTotalCount() > 0) && (jobConfig.getShardingItemParameters() == null || jobConfig.getShardingItemParameters().trim().isEmpty() || jobConfig.getShardingItemParameters().split(",").length < jobConfig.getShardingTotalCount())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "分片参数不能小于分片总数"); } validateShardingItemFormat(jobConfig); } } @Transactional(rollbackFor = Exception.class) @Override public void addJob(String namespace, JobConfig jobConfig, String createdBy) throws SaturnJobConsoleException { addOrCopyJob(namespace, jobConfig, null, createdBy); } @Transactional(rollbackFor = Exception.class) @Override public void copyJob(String namespace, JobConfig jobConfig, String jobNameCopied, String createdBy) throws SaturnJobConsoleException { addOrCopyJob(namespace, jobConfig, jobNameCopied, createdBy); } private void addOrCopyJob(String namespace, JobConfig jobConfig, String jobNameCopied, String createdBy) throws SaturnJobConsoleException { List unSystemJobs = getUnSystemJobs(namespace); Set streamChangedJobs = new HashSet<>(); validateJobConfig(namespace, jobConfig, unSystemJobs, streamChangedJobs); // 如果数据存在相同作业名,则抛异常 // 直接再查一次,不使用unSystemJobs,因为也不能与系统作业名相同 String jobName = jobConfig.getJobName(); if (currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName) != null) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该作业(%s)已经存在", jobName)); } // 如果zk存在该作业,则尝试删除 CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); if (curatorFrameworkOp.checkExists(JobNodePath.getJobNodePath(jobName))) { if (!removeJobFromZk(jobName, curatorFrameworkOp)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该作业(%s)正在删除中,请稍后再试", jobName)); } } // 该域作业总数不能超过一定数量 int maxJobNum = getMaxJobNum(); if (jobIncExceeds(namespace, maxJobNum, 1)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("总作业数超过最大限制(%d),作业名%s创建失败", maxJobNum, jobName)); } // 如果是copy作业,则从数据库中复制被拷贝的作业的配置到新的作业配置 JobConfig myJobConfig = jobConfig; if (jobNameCopied != null) { myJobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobNameCopied); SaturnBeanUtils.copyPropertiesIgnoreNull(jobConfig, myJobConfig); } // 设置作业配置字段默认值,并且强制纠正某些字段 correctConfigValueWhenAddJob(myJobConfig); // 添加该作业到数据库 currentJobConfigService.create(constructJobConfig4DB(namespace, myJobConfig, createdBy, createdBy)); // 更新关联作业的上下游 for (JobConfig streamChangedJob : streamChangedJobs) { currentJobConfigService.updateStream(constructJobConfig4DB(namespace, streamChangedJob, null, createdBy)); } // 添加该作业配置到zk,并联动更新关联作业的上下游 createJobConfigToZk(myJobConfig, streamChangedJobs, curatorFrameworkOp); } private JobConfig4DB constructJobConfig4DB(String namespace, JobConfig jobConfig, String createdBy, String updatedBy) { JobConfig4DB jobConfig4DB = new JobConfig4DB(); SaturnBeanUtils.copyProperties(jobConfig, jobConfig4DB); Date now = new Date(); if (StringUtils.isNotBlank(createdBy)) { jobConfig4DB.setCreateTime(now); jobConfig4DB.setCreateBy(createdBy); } jobConfig4DB.setLastUpdateTime(now); jobConfig4DB.setLastUpdateBy(updatedBy); jobConfig4DB.setNamespace(namespace); return jobConfig4DB; } private void correctConfigValueWhenAddJob(JobConfig jobConfig) { jobConfig.setDefaultValues(); jobConfig.setEnabled(false); JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (JobType.isShell(jobType)) { jobConfig.setJobClass(""); } if (JobType.isMsg(jobType)) { jobConfig.setFailover(false); jobConfig.setRerun(false); } if (JobType.isPassive(jobType)) { jobConfig.setRerun(false); } if (jobConfig.getLocalMode()) { jobConfig.setFailover(false); } boolean enabledReport = getEnabledReport(jobType, jobConfig.getCron(), jobConfig.getTimeZone()); jobConfig.setEnabledReport(enabledReport); if (!enabledReport) { jobConfig.setFailover(false); jobConfig.setRerun(false); } } @Override public int getMaxJobNum() { int result = systemConfigService.getIntegerValue(SystemConfigProperties.MAX_JOB_NUM, DEFAULT_MAX_JOB_NUM); return result <= 0 ? DEFAULT_MAX_JOB_NUM : result; } private int getMaxZnodeDataLength() { int result = systemConfigService.getIntegerValue(SystemConfigProperties.MAX_ZNODE_DATA_LENGTH, DEFAULT_MAX_ZNODE_DATA_LENGTH); return result <= 0 ? DEFAULT_MAX_ZNODE_DATA_LENGTH : result; } @Override public boolean jobIncExceeds(String namespace, int maxJobNum, int inc) throws SaturnJobConsoleException { if (maxJobNum <= 0) { return false; } int curJobSize = getUnSystemJobs(namespace).size(); return (curJobSize + inc) > maxJobNum; } @Override public List getUnSystemJobs(String namespace) throws SaturnJobConsoleException { List unSystemJobs = new ArrayList<>(); List jobConfig4DBList = currentJobConfigService.findConfigsByNamespace(namespace); if (jobConfig4DBList != null) { for (JobConfig4DB jobConfig4DB : jobConfig4DBList) { if (!isSystemJob(jobConfig4DB)) { JobConfig jobConfig = new JobConfig(); SaturnBeanUtils.copyProperties(jobConfig4DB, jobConfig); unSystemJobs.add(jobConfig); } } } return unSystemJobs; } private boolean isSystemJob(JobConfig jobConfig) { String jobMode = jobConfig.getJobMode(); return StringUtils.isNotBlank(jobMode) && jobMode.startsWith(JobMode.SYSTEM_PREFIX); } @Override public List getUnSystemJobsWithCondition(String namespace, Map condition, int page, int size) throws SaturnJobConsoleException { List jobConfig4DBList = getJobConfigByStatusWithCondition(namespace, condition, page, size); if (CollectionUtils.isEmpty(jobConfig4DBList)) { return new ArrayList<>(); } Iterator iterator = jobConfig4DBList.iterator(); while (iterator.hasNext()) { if (isSystemJob(iterator.next())) { iterator.remove(); } } return jobConfig4DBList; } private List getJobConfigByStatusWithCondition(String namespace, Map condition, int page, int size) throws SaturnJobConsoleException { JobStatus jobStatus = (JobStatus) condition.get("jobStatus"); if (jobStatus == null) { return currentJobConfigService.findConfigsByNamespaceWithCondition(namespace, condition, PageableUtil.generatePageble(page, size)); } List jobConfig4DBList = new ArrayList<>(); List enabledJobConfigList = currentJobConfigService.findConfigsByNamespaceWithCondition(namespace, condition, null); for (JobConfig4DB jobConfig4DB : enabledJobConfigList) { JobStatus currentJobStatus = getJobStatus(namespace, jobConfig4DB.getJobName()); if (jobStatus.equals(currentJobStatus)) { jobConfig4DBList.add(jobConfig4DB); } } return jobConfig4DBList; } @Override public int countUnSystemJobsWithCondition(String namespace, Map condition) throws SaturnJobConsoleException { return currentJobConfigService.countConfigsByNamespaceWithCondition(namespace, condition); } @Override public int countEnabledUnSystemJobs(String namespace) throws SaturnJobConsoleException { return currentJobConfigService.countEnabledUnSystemJobsByNamespace(namespace); } @Override public List getUnSystemJobNames(String namespace) throws SaturnJobConsoleException { List unSystemJobs = new ArrayList<>(); List jobConfig4DBList = currentJobConfigService.findConfigsByNamespace(namespace); if (jobConfig4DBList != null) { for (JobConfig4DB jobConfig4DB : jobConfig4DBList) { if (!(StringUtils.isNotBlank(jobConfig4DB.getJobMode()) && jobConfig4DB.getJobMode().startsWith(JobMode.SYSTEM_PREFIX))) { unSystemJobs.add(jobConfig4DB.getJobName()); } } } return unSystemJobs; } @Override public List getJobNames(String namespace) throws SaturnJobConsoleException { List jobNames = currentJobConfigService.findConfigNamesByNamespace(namespace); return jobNames != null ? jobNames : Lists.newArrayList(); } @Override public void persistJobFromDB(String namespace, JobConfig jobConfig) throws SaturnJobConsoleException { jobConfig.setDefaultValues(); CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); saveJobConfigToZk(jobConfig, curatorFrameworkOp); } @Override public void persistJobFromDB(JobConfig jobConfig, CuratorFrameworkOp curatorFrameworkOp) { jobConfig.setDefaultValues(); saveJobConfigToZk(jobConfig, curatorFrameworkOp); } /** * 对于被动作业,返回true;
* 对于定时作业,根据cron和INTERVAL_TIME_OF_ENABLED_REPORT来计算是否需要上报状态 see #286 */ private boolean getEnabledReport(JobType jobType, String cron, String timeZone) { if (JobType.isPassive(jobType)) { return true; } if (!JobType.isCron(jobType)) { return false; } boolean enabledReport = true; try { Integer intervalTimeConfigured = systemConfigService.getIntegerValue( SystemConfigProperties.INTERVAL_TIME_OF_ENABLED_REPORT, DEFAULT_INTERVAL_TIME_OF_ENABLED_REPORT); if (intervalTimeConfigured == null) { log.warn("unexpected error, get INTERVAL_TIME_OF_ENABLED_REPORT null"); intervalTimeConfigured = DEFAULT_INTERVAL_TIME_OF_ENABLED_REPORT; } CronExpression cronExpression = new CronExpression(cron); cronExpression.setTimeZone(TimeZone.getTimeZone(timeZone)); Date lastNextTime = cronExpression.getNextValidTimeAfter(new Date()); if (lastNextTime != null) { for (int i = 0; i < 5; i++) { Date nextTime = cronExpression.getNextValidTimeAfter(lastNextTime); if (nextTime == null) { break; } long interval = nextTime.getTime() - lastNextTime.getTime(); if (interval < intervalTimeConfigured * 1000) { enabledReport = false; break; } lastNextTime = nextTime; } } } catch (ParseException e) { log.warn(e.getMessage(), e); } return enabledReport; } private void createJobConfigToZk(JobConfig jobConfig, Set streamChangedJobs, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) throws SaturnJobConsoleException { try { String jobName = jobConfig.getJobName(); // 添加作业根节点和config结点 curatorFrameworkOp.create(JobNodePath.getConfigNodePath(jobName), ""); CuratorFrameworkOp.CuratorTransactionOp curatorTransactionOp = curatorFrameworkOp.inTransaction(); // 数据库有可能有重复作业的数据,去重,zk无需更新两次 Collection streamChangedJobsNew = removeDuplicateByJobName(streamChangedJobs); // 更新关联作业的上下游 for (JobConfig streamChangedJob : streamChangedJobsNew) { String changedJobName = streamChangedJob.getJobName(); if (!curatorFrameworkOp.checkExists(JobNodePath.getConfigNodePath(changedJobName))) { // 数据库存在该作业,但是zk不存在该作业,为垃圾数据 log.warn("the job({}) config node is not existing in zk", changedJobName); continue; } curatorTransactionOp .replaceIfChanged(JobNodePath.getConfigNodePath(changedJobName, CONFIG_ITEM_UPSTREAM), streamChangedJob.getUpStream()) .replaceIfChanged(JobNodePath.getConfigNodePath(changedJobName, CONFIG_ITEM_DOWNSTREAM), streamChangedJob.getDownStream()); } // 添加作业 curatorTransactionOp .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED), jobConfig.getEnabled()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DESCRIPTION), jobConfig.getDescription()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CUSTOM_CONTEXT), jobConfig.getCustomContext()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_TYPE), jobConfig.getJobType()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_MODE), jobConfig.getJobMode()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_ITEM_PARAMETERS), jobConfig.getShardingItemParameters()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_PARAMETER), jobConfig.getJobParameter()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_QUEUE_NAME), jobConfig.getQueueName()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CHANNEL_NAME), jobConfig.getChannelName()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_FAILOVER), jobConfig.getFailover()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_MONITOR_EXECUTION), "true") .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS), jobConfig.getTimeout4AlarmSeconds()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_SECONDS), jobConfig.getTimeoutSeconds()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIME_ZONE), jobConfig.getTimeZone()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON), jobConfig.getCron()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_DATE), jobConfig.getPausePeriodDate()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_TIME), jobConfig.getPausePeriodTime()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS), jobConfig.getProcessCountIntervalSeconds()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_TOTAL_COUNT), jobConfig.getShardingTotalCount()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG), jobConfig.getShowNormalLog()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOAD_LEVEL), jobConfig.getLoadLevel()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_DEGREE), jobConfig.getJobDegree()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED_REPORT), jobConfig.getEnabledReport()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST), jobConfig.getPreferList()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_DISPREFER_LIST), jobConfig.getUseDispreferList()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOCAL_MODE), jobConfig.getLocalMode()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_SERIAL), jobConfig.getUseSerial()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DEPENDENCIES), jobConfig.getDependencies()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_GROUPS), jobConfig.getGroups()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_RERUN), jobConfig.getRerun()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_UPSTREAM), jobConfig.getUpStream()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DOWNSTREAM), jobConfig.getDownStream()) .create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_CLASS), jobConfig.getJobClass()); // 注意!!! jobClass要最后更新,因为executor认为该结点为作业添加完成 // 提交事务 curatorTransactionOp.commit(); } catch (Exception e) { log.error("create job to zk failed", e); throw new SaturnJobConsoleException(e); } } private void saveJobConfigToZk(JobConfig jobConfig, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { String jobName = jobConfig.getJobName(); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED), jobConfig.getEnabled()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DESCRIPTION), jobConfig.getDescription()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CUSTOM_CONTEXT), jobConfig.getCustomContext()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_TYPE), jobConfig.getJobType()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_MODE), jobConfig.getJobMode()); curatorFrameworkOp.fillJobNodeIfNotExist( JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_ITEM_PARAMETERS), jobConfig.getShardingItemParameters()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_PARAMETER), jobConfig.getJobParameter()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_QUEUE_NAME), jobConfig.getQueueName()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CHANNEL_NAME), jobConfig.getChannelName()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_FAILOVER), jobConfig.getFailover()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_MONITOR_EXECUTION), "true"); curatorFrameworkOp.fillJobNodeIfNotExist( JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS), jobConfig.getTimeout4AlarmSeconds()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_SECONDS), jobConfig.getTimeoutSeconds()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIME_ZONE), jobConfig.getTimeZone()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON), jobConfig.getCron()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_DATE), jobConfig.getPausePeriodDate()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_TIME), jobConfig.getPausePeriodTime()); curatorFrameworkOp.fillJobNodeIfNotExist( JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS), jobConfig.getProcessCountIntervalSeconds()); curatorFrameworkOp.fillJobNodeIfNotExist( JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_TOTAL_COUNT), jobConfig.getShardingTotalCount()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG), jobConfig.getShowNormalLog()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOAD_LEVEL), jobConfig.getLoadLevel()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_DEGREE), jobConfig.getJobDegree()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED_REPORT), jobConfig.getEnabledReport()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST), jobConfig.getPreferList()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_DISPREFER_LIST), jobConfig.getUseDispreferList()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOCAL_MODE), jobConfig.getLocalMode()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_SERIAL), jobConfig.getUseSerial()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DEPENDENCIES), jobConfig.getDependencies()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_GROUPS), jobConfig.getGroups()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_RERUN), jobConfig.getRerun()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_UPSTREAM), jobConfig.getUpStream()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DOWNSTREAM), jobConfig.getDownStream()); curatorFrameworkOp.fillJobNodeIfNotExist(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_CLASS), jobConfig.getJobClass()); // 注意!!! jobClass要最后更新,因为executor认为该结点为作业添加完成 } @Override public List importJobs(String namespace, MultipartFile file, String createdBy) throws SaturnJobConsoleException { try { Workbook workbook = Workbook.getWorkbook(file.getInputStream()); Sheet[] sheets = workbook.getSheets(); List jobConfigList = new ArrayList<>(); // 第一行为配置项提示,从第二行开始为作业配置信息 // 先获取数据并检测内容格式的正确性 for (int i = 0; i < sheets.length; i++) { Sheet sheet = sheets[i]; int rows = sheet.getRows(); for (int row = 1; row < rows; row++) { Cell[] rowCells = sheet.getRow(row); // 如果这一行的表格全为空,则跳过这一行。 if (!isBlankRow(rowCells)) { jobConfigList.add(convertJobConfig(i + 1, row + 1, rowCells)); } } } int maxJobNum = getMaxJobNum(); if (jobIncExceeds(namespace, maxJobNum, jobConfigList.size())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("总作业数超过最大限制(%d),导入失败", maxJobNum)); } return doCreateJobFromImportFile(namespace, jobConfigList, createdBy); } catch (SaturnJobConsoleException e) { throw e; } catch (Exception e) { throw new SaturnJobConsoleException(e); } } protected List doCreateJobFromImportFile(String namespace, List jobConfigList, String createdBy) throws SaturnJobConsoleException { Map resultMap = new LinkedHashMap<>(); List jobConfigUpdatedList = new ArrayList<>(); for (JobConfig jobConfig : jobConfigList) { String jobName = jobConfig.getJobName(); BatchJobResult batchJobResult = new BatchJobResult(); batchJobResult.setJobName(jobName); try { // 如果存在上下游关联关系,直接导入会检验不通过;需要先解除关联关系,创建成功后再更新关联关系 JobConfig jobConfigUpdated = null; if (StringUtils.isNotBlank(jobConfig.getUpStream()) || StringUtils.isNotBlank(jobConfig.getDownStream())) { jobConfigUpdated = new JobConfig(); jobConfigUpdated.setJobName(jobName); jobConfigUpdated.setUpStream(jobConfig.getUpStream()); jobConfigUpdated.setDownStream(jobConfig.getDownStream()); jobConfig.setUpStream(null); jobConfig.setDownStream(null); } addJob(namespace, jobConfig, createdBy); batchJobResult.setSuccess(true); if (jobConfigUpdated != null) { jobConfigUpdatedList.add(jobConfigUpdated); } } catch (SaturnJobConsoleException e) { batchJobResult.setSuccess(false); batchJobResult.setMessage(e.getMessage()); log.warn(e.getMessage(), e); } catch (Exception e) { batchJobResult.setSuccess(false); batchJobResult.setMessage(e.toString()); log.warn(e.getMessage(), e); } resultMap.put(jobName, batchJobResult); } for (JobConfig jobConfig : jobConfigUpdatedList) { BatchJobResult batchJobResult = resultMap.get(jobConfig.getJobName()); try { updateJobConfig(namespace, jobConfig, createdBy); } catch (SaturnJobConsoleException e) { batchJobResult.appendMessage(e.getMessage()); log.warn(e.getMessage(), e); } catch (Exception e) { batchJobResult.appendMessage(e.toString()); log.warn(e.getMessage(), e); } } return new ArrayList<>(resultMap.values()); } private boolean isBlankRow(Cell[] rowCells) { for (int i = 0; i < rowCells.length; i++) { if (!CellType.EMPTY.equals(rowCells[i].getType())) { return false; } } return true; } private JobConfig convertJobConfig(int sheetNumber, int rowNumber, Cell[] rowCells) throws SaturnJobConsoleException { String jobName = getContents(rowCells, 0); if (jobName == null || jobName.trim().isEmpty()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 1, "作业名必填。")); } if (!jobName.matches("[0-9a-zA-Z_]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 1, "作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_。")); } JobConfig jobConfig = new JobConfig(); jobConfig.setJobName(jobName); String jobType = getContents(rowCells, 1); if (jobType == null || jobType.trim().isEmpty()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 2, "作业类型必填。")); } JobType jobTypeObj = JobType.getJobType(jobType); if (jobTypeObj == JobType.UNKNOWN_JOB) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 2, "作业类型未知。")); } jobConfig.setJobType(jobType); String jobClass = getContents(rowCells, 2); if (JobType.isJava(jobTypeObj) && (jobClass == null || jobClass.trim().isEmpty())) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 3, "对于java作业,作业实现类必填。")); } jobConfig.setJobClass(jobClass); String cron = getContents(rowCells, 3); if (JobType.isCron(jobTypeObj)) { if (cron == null || cron.trim().isEmpty()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 4, "对于cron作业,cron表达式必填。")); } cron = cron.trim(); try { CronExpression.validateExpression(cron); } catch (ParseException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 4, "cron表达式语法有误," + e)); } } else { cron = "";// 其他类型的不需要持久化保存cron表达式 } jobConfig.setCron(cron); jobConfig.setDescription(getContents(rowCells, 4)); jobConfig.setLocalMode(Boolean.valueOf(getContents(rowCells, 5))); int shardingTotalCount = 1; if (jobConfig.getLocalMode()) { jobConfig.setShardingTotalCount(shardingTotalCount); } else { String tmp = getContents(rowCells, 6); if (tmp != null && !tmp.trim().isEmpty()) { try { shardingTotalCount = Integer.parseInt(tmp); } catch (NumberFormatException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 7, "分片数有误," + e)); } } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 7, "分片数必填")); } if (shardingTotalCount < 1) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 7, "分片数不能小于1")); } jobConfig.setShardingTotalCount(shardingTotalCount); } int timeoutSeconds = 0; try { String tmp = getContents(rowCells, 7); if (tmp != null && !tmp.trim().isEmpty()) { timeoutSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 8, "超时(Kill线程/进程)时间有误," + e)); } jobConfig.setTimeoutSeconds(timeoutSeconds); jobConfig.setJobParameter(getContents(rowCells, 8)); String shardingItemParameters = getContents(rowCells, 9); if (jobConfig.getLocalMode()) { if (shardingItemParameters == null || shardingItemParameters.trim().isEmpty()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 10, "对于本地模式作业,分片参数必填。")); } else { String[] split = shardingItemParameters.split(","); boolean includeXing = false; for (String tmp : split) { String[] split2 = tmp.split("="); if ("*".equalsIgnoreCase(split2[0].trim())) { includeXing = true; break; } } if (!includeXing) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 10, "对于本地模式作业,分片参数必须包含如*=xx。")); } } } else if ((shardingTotalCount > 0) && (shardingItemParameters == null || shardingItemParameters.trim().isEmpty() || shardingItemParameters.split(",").length < shardingTotalCount)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 10, "分片参数不能小于分片总数。")); } jobConfig.setShardingItemParameters(shardingItemParameters); jobConfig.setQueueName(getContents(rowCells, 10)); jobConfig.setChannelName(getContents(rowCells, 11)); jobConfig.setPreferList(getContents(rowCells, 12)); jobConfig.setUseDispreferList(!Boolean.parseBoolean(getContents(rowCells, 13))); int processCountIntervalSeconds = 300; try { String tmp = getContents(rowCells, 14); if (tmp != null && !tmp.trim().isEmpty()) { processCountIntervalSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 15, "统计处理数据量的间隔秒数有误," + e)); } jobConfig.setProcessCountIntervalSeconds(processCountIntervalSeconds); int loadLevel = 1; try { String tmp = getContents(rowCells, 15); if (tmp != null && !tmp.trim().isEmpty()) { loadLevel = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 16, "负荷有误," + e)); } jobConfig.setLoadLevel(loadLevel); jobConfig.setShowNormalLog(Boolean.valueOf(getContents(rowCells, 16))); jobConfig.setPausePeriodDate(getContents(rowCells, 17)); jobConfig.setPausePeriodTime(getContents(rowCells, 18)); jobConfig.setUseSerial(Boolean.valueOf(getContents(rowCells, 19))); int jobDegree = 0; try { String tmp = getContents(rowCells, 20); if (tmp != null && !tmp.trim().isEmpty()) { jobDegree = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 21, "作业重要等级有误," + e)); } jobConfig.setJobDegree(jobDegree); // 第21列,上报运行状态失效,由算法决定是否上报,看下面setEnabledReport时的逻辑,看addJob String jobMode = getContents(rowCells, 22); if (jobMode != null && jobMode.startsWith(com.vip.saturn.job.console.domain.JobMode.SYSTEM_PREFIX)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 23, "作业模式有误,不能添加系统作业")); } jobConfig.setJobMode(jobMode); String dependencies = getContents(rowCells, 23); if (dependencies != null && !dependencies.matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 24, "依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,")); } jobConfig.setDependencies(dependencies); jobConfig.setGroups(getContents(rowCells, 24)); int timeout4AlarmSeconds = 0; try { String tmp = getContents(rowCells, 25); if (tmp != null && !tmp.trim().isEmpty()) { timeout4AlarmSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 26, "超时(告警)时间有误," + e)); } jobConfig.setTimeout4AlarmSeconds(timeout4AlarmSeconds); String timeZone = getContents(rowCells, 26); if (timeZone == null || timeZone.trim().length() == 0) { timeZone = SaturnConstants.TIME_ZONE_ID_DEFAULT; } else { timeZone = timeZone.trim(); if (!SaturnConstants.TIME_ZONE_IDS.contains(timeZone)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 27, "时区有误")); } } jobConfig.setTimeZone(timeZone); Boolean failover = null; String failoverStr = getContents(rowCells, 27); if (StringUtils.isNotBlank(failoverStr)) { failover = Boolean.valueOf(failoverStr.trim()); if (failover) { if (jobConfig.getLocalMode()) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 28, "本地模式不支持failover")); } if (JobType.isMsg(jobTypeObj)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 28, "消息作业不支持failover")); } // 如果不上报运行状态,则强制设置为false // 上报运行状态失效,由算法决定是否上报,看下面setEnabledReport时的逻辑,看addJob } } jobConfig.setFailover(failover); Boolean rerun = null; String rerunStr = getContents(rowCells, 28); if (StringUtils.isNotBlank(rerunStr)) { rerun = Boolean.valueOf(rerunStr.trim()); if (rerun) { if (JobType.isMsg(jobTypeObj)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 29, "消息作业不支持rerun")); } if (JobType.isPassive(jobTypeObj)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 29, "被动作业不支持rerun")); } // 如果不上报运行状态,则强制设置为false // 上报运行状态失效,由算法决定是否上报,看下面setEnabledReport时的逻辑,看addJob } } jobConfig.setRerun(rerun); String upStream = getContents(rowCells, 29); if (upStream != null && !upStream.matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 30, "上游作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,")); } jobConfig.setUpStream(upStream); String downStream = getContents(rowCells, 30); if (downStream != null && !downStream.matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, createExceptionMessage(sheetNumber, rowNumber, 31, "下游作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,")); } jobConfig.setDownStream(downStream); return jobConfig; } private String getContents(Cell[] rowCell, int column) { if (rowCell.length > column) { return rowCell[column].getContents(); } return null; } private String createExceptionMessage(int sheetNumber, int rowNumber, int columnNumber, String message) { return "内容格式有误,错误发生在表格页:" + sheetNumber + ",行号:" + rowNumber + ",列号:" + columnNumber + ",错误信息:" + message; } @Override public File exportJobs(String namespace) throws SaturnJobConsoleException { try { File tmp = new File(SaturnConstants.CACHES_FILE_PATH, "tmp_exportFile_" + System.currentTimeMillis() + "_" + random.nextInt(1000) + ".xls"); if (!tmp.exists()) { FileUtils.forceMkdir(tmp.getParentFile()); tmp.createNewFile(); } WritableWorkbook writableWorkbook = Workbook.createWorkbook(tmp); WritableSheet sheet1 = writableWorkbook.createSheet("Sheet1", 0); setExcelHeader(sheet1); List unSystemJobs = getUnSystemJobs(namespace); // sort by jobName Collections.sort(unSystemJobs, new Comparator() { @Override public int compare(JobConfig o1, JobConfig o2) { return o1.getJobName().compareTo(o2.getJobName()); } }); setExcelContent(namespace, sheet1, unSystemJobs); writableWorkbook.write(); writableWorkbook.close(); return tmp; } catch (Exception e) { throw new SaturnJobConsoleException(e); } } @Override public File exportSelectedJobs(String namespace, List jobList) throws SaturnJobConsoleException { try { File tmp = new File(SaturnConstants.CACHES_FILE_PATH, "tmp_exportFile_" + System.currentTimeMillis() + "_" + random.nextInt(1000) + ".xls"); if (!tmp.exists()) { FileUtils.forceMkdir(tmp.getParentFile()); tmp.createNewFile(); } WritableWorkbook writableWorkbook = Workbook.createWorkbook(tmp); WritableSheet sheet1 = writableWorkbook.createSheet("Sheet1", 0); setExcelHeader(sheet1); // 单个域下作业量不会很大,直接复用之前的方法取该域下全部作业再过滤 List targetJobs = filterTargetJobs(jobList, getUnSystemJobs(namespace)); // sort by jobName Collections.sort(targetJobs, new Comparator() { @Override public int compare(JobConfig o1, JobConfig o2) { return o1.getJobName().compareTo(o2.getJobName()); } }); setExcelContent(namespace, sheet1, targetJobs); writableWorkbook.write(); writableWorkbook.close(); return tmp; } catch (Exception e) { throw new SaturnJobConsoleException(e); } } private List filterTargetJobs(List jobList, List jobConfigList) { List result = new ArrayList<>(); for (JobConfig jobConfig : jobConfigList) { if (jobList.contains(jobConfig.getJobName())) { result.add(jobConfig); } } return result; } protected void setExcelContent(String namespace, WritableSheet sheet1, List unSystemJobs) throws SaturnJobConsoleException, WriteException { if (unSystemJobs != null && !unSystemJobs.isEmpty()) { CuratorFrameworkOp curatorFrameworkOp = registryCenterService.getCuratorFrameworkOp(namespace); for (int i = 0; i < unSystemJobs.size(); i++) { String jobName = unSystemJobs.get(i).getJobName(); sheet1.addCell(new Label(0, i + 1, jobName)); sheet1.addCell(new Label(1, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_TYPE)))); sheet1.addCell(new Label(2, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_CLASS)))); sheet1.addCell(new Label(3, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON)))); sheet1.addCell(new Label(4, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DESCRIPTION)))); sheet1.addCell(new Label(5, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOCAL_MODE)))); sheet1.addCell(new Label(6, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_TOTAL_COUNT)))); sheet1.addCell(new Label(7, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_SECONDS)))); sheet1.addCell(new Label(8, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_PARAMETER)))); sheet1.addCell(new Label(9, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_ITEM_PARAMETERS)))); sheet1.addCell(new Label(10, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_QUEUE_NAME)))); sheet1.addCell(new Label(11, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CHANNEL_NAME)))); sheet1.addCell(new Label(12, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST)))); String useDispreferList = curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_DISPREFER_LIST)); if (useDispreferList != null) { useDispreferList = String.valueOf(!Boolean.parseBoolean(useDispreferList)); } sheet1.addCell(new Label(13, i + 1, useDispreferList)); sheet1.addCell(new Label(14, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS)))); sheet1.addCell(new Label(15, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOAD_LEVEL)))); sheet1.addCell(new Label(16, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG)))); sheet1.addCell(new Label(17, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_DATE)))); sheet1.addCell(new Label(18, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_TIME)))); sheet1.addCell(new Label(19, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, JobServiceImpl.CONFIG_ITEM_USE_SERIAL)))); sheet1.addCell(new Label(20, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_DEGREE)))); sheet1.addCell(new Label(21, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED_REPORT)))); sheet1.addCell(new Label(22, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_MODE)))); sheet1.addCell(new Label(23, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DEPENDENCIES)))); // sheet1.addCell(new Label(24, i + 1, // curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_GROUPS)))); // 批量分组后,分组只写到了db并没有写到zk,导出的Excel表格缺失分组名数据,所以这里直接取db的数据 sheet1.addCell(new Label(24, i + 1, unSystemJobs.get(i).getGroups())); sheet1.addCell(new Label(25, i + 1, curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS)))); sheet1.addCell(new Label(26, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIME_ZONE)))); sheet1.addCell(new Label(27, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_FAILOVER)))); sheet1.addCell(new Label(28, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_RERUN)))); sheet1.addCell(new Label(29, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_UPSTREAM)))); sheet1.addCell(new Label(30, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DOWNSTREAM)))); sheet1.addCell(new Label(31, i + 1, curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED)))); } } } protected void setExcelHeader(WritableSheet sheet1) throws WriteException { sheet1.addCell(new Label(0, 0, "作业名称")); sheet1.addCell(new Label(1, 0, "作业类型")); sheet1.addCell(new Label(2, 0, "作业实现类")); sheet1.addCell(new Label(3, 0, "cron表达式")); sheet1.addCell(new Label(4, 0, "作业描述")); Label localModeLabel = new Label(5, 0, "本地模式"); setCellComment(localModeLabel, "对于非本地模式,默认为false;对于本地模式,该配置无效,固定为true"); sheet1.addCell(localModeLabel); Label shardingTotalCountLabel = new Label(6, 0, "分片数"); setCellComment(shardingTotalCountLabel, "对本地作业无效"); sheet1.addCell(shardingTotalCountLabel); Label timeoutSecondsLabel = new Label(7, 0, "超时(Kill线程/进程)时间"); setCellComment(timeoutSecondsLabel, "0表示无超时"); sheet1.addCell(timeoutSecondsLabel); sheet1.addCell(new Label(8, 0, "自定义参数")); sheet1.addCell(new Label(9, 0, "分片序列号/参数对照表")); sheet1.addCell(new Label(10, 0, "Queue名")); sheet1.addCell(new Label(11, 0, "执行结果发送的Channel")); Label preferListLabel = new Label(12, 0, "优先Executor"); setCellComment(preferListLabel, "可填executorName,多个元素使用英文逗号隔开"); sheet1.addCell(preferListLabel); Label usePreferListOnlyLabel = new Label(13, 0, "只使用优先Executor"); setCellComment(usePreferListOnlyLabel, "默认为false"); sheet1.addCell(usePreferListOnlyLabel); sheet1.addCell(new Label(14, 0, "统计处理数据量的间隔秒数")); sheet1.addCell(new Label(15, 0, "负荷")); sheet1.addCell(new Label(16, 0, "显示控制台输出日志")); sheet1.addCell(new Label(17, 0, "暂停日期段")); sheet1.addCell(new Label(18, 0, "暂停时间段")); Label useSerialLabel = new Label(19, 0, "串行消费"); setCellComment(useSerialLabel, "默认为false"); sheet1.addCell(useSerialLabel); Label jobDegreeLabel = new Label(20, 0, "作业重要等级"); setCellComment(jobDegreeLabel, "0:没有定义,1:非线上业务,2:简单业务,3:一般业务,4:重要业务,5:核心业务"); sheet1.addCell(jobDegreeLabel); Label enabledReportLabel = new Label(21, 0, "上报运行状态"); setCellComment(enabledReportLabel, "对于定时作业,默认为true;对于消息作业,默认为false"); sheet1.addCell(enabledReportLabel); Label jobModeLabel = new Label(22, 0, "作业模式"); setCellComment(jobModeLabel, "用户不能添加系统作业"); sheet1.addCell(jobModeLabel); Label dependenciesLabel = new Label(23, 0, "依赖的作业"); setCellComment(dependenciesLabel, "作业的启用、禁用会检查依赖关系的作业的状态。依赖多个作业,使用英文逗号给开。该字段已过期。"); sheet1.addCell(dependenciesLabel); Label groupsLabel = new Label(24, 0, "所属分组"); setCellComment(groupsLabel, "作业所属分组,一个作业可以属于多个分组,一个分组可以包含多个作业"); sheet1.addCell(groupsLabel); Label timeout4AlarmSecondsLabel = new Label(25, 0, "超时(告警)时间"); setCellComment(timeout4AlarmSecondsLabel, "0表示无超时"); sheet1.addCell(timeout4AlarmSecondsLabel); Label timeZoneLabel = new Label(26, 0, "时区"); setCellComment(timeZoneLabel, "作业运行时区"); sheet1.addCell(timeZoneLabel); sheet1.addCell(new Label(27, 0, "failover")); sheet1.addCell(new Label(28, 0, "失败重跑")); Label upStream = new Label(29, 0, "上游作业"); setCellComment(upStream, "上游作业执行成功后,触发本作业执行。多个上游作业使用英文逗号隔开。"); sheet1.addCell(upStream); Label downStream = new Label(30, 0, "下游作业"); setCellComment(downStream, "该作业执行成功后,触发下游作业执行。多个下游作业使用英文逗号隔开。"); sheet1.addCell(downStream); sheet1.addCell(new Label(31, 0, "是否启用")); } protected void setCellComment(WritableCell cell, String comment) { WritableCellFeatures cellFeatures = new WritableCellFeatures(); cellFeatures.setComment(comment); cell.setCellFeatures(cellFeatures); } @Override public ArrangeLayout getArrangeLayout(String namespace) throws SaturnJobConsoleException { ArrangeLayout arrangeLayout = new ArrangeLayout(); // get all ArrangeNodes Map nodeMap = new HashMap<>(); List unSystemJobs = getUnSystemJobs(namespace); Map unSystemJobsMap = new HashMap<>(); for (JobConfig jobConfig : unSystemJobs) { String jobName = jobConfig.getJobName(); unSystemJobsMap.put(jobName, jobConfig); ArrangeNode node = nodeMap.get(jobName); if (node == null) { node = new ArrangeNode(); node.setName(jobName); nodeMap.put(jobName, node); } if (StringUtils.isNotBlank(jobConfig.getDownStream())) { for (String split : jobConfig.getDownStream().split(",")) { String temp = split.trim(); if (StringUtils.isNotBlank(temp)) { node.getChildren().add(temp); } } } } Collection nodes = nodeMap.values(); // set paths for (ArrangeNode node : nodes) { String name = node.getName(); for (String child : node.getChildren()) { ArrangePath path = new ArrangePath(); path.setSource(name); path.setTarget(child); arrangeLayout.getPaths().add(path); } } Collections.sort(arrangeLayout.getPaths(), new Comparator() { @Override public int compare(ArrangePath o1, ArrangePath o2) { int compare1 = o1.getSource().compareTo(o2.getSource()); return compare1 != 0 ? compare1 : o1.getTarget().compareTo(o2.getTarget()); } }); // set levels for (ArrangeNode node : nodes) { int maxLevel = getMaxArrangeNodeLevel(node, 0, nodes, new Stack()); if (maxLevel > node.getLevel()) { node.setLevel(maxLevel); } } int maxLevel = 0; for (ArrangeNode node : nodes) { maxLevel = Math.max(maxLevel, node.getLevel()); } for (int i = 0; i <= maxLevel; i++) { arrangeLayout.getLevels().add(new ArrayList()); } for (ArrangeNode node : nodes) { int level = node.getLevel(); if (level == 0 && node.getChildren().isEmpty()) { continue; } ArrangeLevel arrangeLevel = new ArrangeLevel(); SaturnBeanUtils.copyProperties(node, arrangeLevel); arrangeLevel.setDescription(unSystemJobsMap.get(node.getName()).getDescription()); arrangeLevel.setJobStatus(getJobStatus(namespace, unSystemJobsMap.get(node.getName()))); arrangeLayout.getLevels().get(level).add(arrangeLevel); } for (int i = 0; i <= maxLevel; i++) { Collections.sort(arrangeLayout.getLevels().get(i), new Comparator() { @Override public int compare(ArrangeLevel o1, ArrangeLevel o2) { return o1.getName().compareTo(o2.getName()); } }); } return arrangeLayout; } private int getMaxArrangeNodeLevel(ArrangeNode currentNode, int level, Collection nodes, Stack onePathRecords) throws SaturnJobConsoleException { String currentName = currentNode.getName(); onePathRecords.push(currentName); int maxLevel = level; for (ArrangeNode node : nodes) { if (node.getChildren().contains(currentName)) { String name = node.getName(); if (onePathRecords.search(name) != -1) { onePathRecords.push(name); throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "作业编排不允许有环,形成环的作业有: " + onePathRecords); } maxLevel = Math.max(maxLevel, getMaxArrangeNodeLevel(node, level + 1, nodes, onePathRecords)); } } onePathRecords.pop(); return maxLevel; } @Override public JobConfig getJobConfigFromZK(String namespace, String jobName) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); JobConfig result = new JobConfig(); result.setJobName(jobName); result.setJobType(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_TYPE))); result.setJobClass(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_CLASS))); // 兼容旧版没有msg_job。 if (StringUtils.isBlank(result.getJobType())) { if (result.getJobClass().indexOf("script") >= 0) { result.setJobType(JobType.SHELL_JOB.name()); } else { result.setJobType(JobType.JAVA_JOB.name()); } } result.setShardingTotalCount(Integer.valueOf( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_TOTAL_COUNT)))); String timeZone = curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIME_ZONE)); if (Strings.isNullOrEmpty(timeZone)) { result.setTimeZone(SaturnConstants.TIME_ZONE_ID_DEFAULT); } else { result.setTimeZone(timeZone); } result.setCron(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON))); result.setPausePeriodDate( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_DATE))); result.setPausePeriodTime( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_TIME))); result.setShardingItemParameters(curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_ITEM_PARAMETERS))); result.setJobParameter( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_PARAMETER))); result.setProcessCountIntervalSeconds(Integer.valueOf(curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS)))); String timeout4AlarmSecondsStr = curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS)); if (Strings.isNullOrEmpty(timeout4AlarmSecondsStr)) { result.setTimeout4AlarmSeconds(0); } else { result.setTimeout4AlarmSeconds(Integer.valueOf(timeout4AlarmSecondsStr)); } result.setTimeoutSeconds(Integer.valueOf( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_SECONDS)))); String lv = curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOAD_LEVEL)); if (Strings.isNullOrEmpty(lv)) { result.setLoadLevel(1); } else { result.setLoadLevel(Integer.valueOf(lv)); } String jobDegree = curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_DEGREE)); if (Strings.isNullOrEmpty(jobDegree)) { result.setJobDegree(0); } else { result.setJobDegree(Integer.valueOf(jobDegree)); } result.setEnabled(Boolean .valueOf(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED))));// 默认是禁用的 result.setPreferList( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST))); String useDispreferList = curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_DISPREFER_LIST)); if (Strings.isNullOrEmpty(useDispreferList)) { result.setUseDispreferList(null); } else { result.setUseDispreferList(Boolean.valueOf(useDispreferList)); } result.setLocalMode(Boolean .valueOf(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOCAL_MODE)))); result.setDependencies( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DEPENDENCIES))); result.setGroups(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_GROUPS))); result.setDescription( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DESCRIPTION))); result.setJobMode(curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, JobServiceImpl.CONFIG_ITEM_JOB_MODE))); result.setUseSerial(Boolean.valueOf(curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, JobServiceImpl.CONFIG_ITEM_USE_SERIAL)))); result.setQueueName(curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_QUEUE_NAME))); result.setChannelName( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CHANNEL_NAME))); if (!curatorFrameworkOp .checkExists(JobNodePath.getConfigNodePath(jobName, JobServiceImpl.CONFIG_ITEM_SHOW_NORMAL_LOG))) { curatorFrameworkOp.create(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG)); } String enabledReport = curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED_REPORT)); Boolean enabledReportValue = Boolean.valueOf(enabledReport); if (Strings.isNullOrEmpty(enabledReport)) { enabledReportValue = true; } result.setEnabledReport(enabledReportValue); result.setShowNormalLog(Boolean.valueOf( curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG)))); return result; } @Override public JobConfig getJobConfig(String namespace, String jobName) throws SaturnJobConsoleException { JobConfig4DB jobConfig4DB = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig4DB == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, String.format("该作业(%s)不存在", jobName)); } JobConfig jobConfig = new JobConfig(); SaturnBeanUtils.copyProperties(jobConfig4DB, jobConfig); return jobConfig; } @Override public JobStatus getJobStatus(String namespace, String jobName) throws SaturnJobConsoleException { JobConfig4DB jobConfig = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "不能获取该作业(" + jobName + ")的状态,因为该作业不存在"); } CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); return getJobStatus(jobName, curatorFrameworkOp, jobConfig.getEnabled()); } @Override public JobStatus getJobStatus(String namespace, JobConfig jobConfig) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); return getJobStatus(jobConfig.getJobName(), curatorFrameworkOp, jobConfig.getEnabled()); } @Override public boolean isJobShardingAllocatedExecutor(String namespace, String jobName) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String executorsPath = JobNodePath.getServerNodePath(jobName); List executors = curatorFrameworkOp.getChildren(executorsPath); if (CollectionUtils.isEmpty(executors)) { return false; } for (String executor : executors) { String sharding = curatorFrameworkOp.getData(JobNodePath.getServerNodePath(jobName, executor, "sharding")); if (StringUtils.isNotBlank(sharding)) { return true; } } return false; } @Override public List getJobServerList(String namespace, String jobName) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String executorsPath = JobNodePath.getServerNodePath(jobName); List executors = curatorFrameworkOp.getChildren(executorsPath); if (executors == null || CollectionUtils.isEmpty(executors)) { return Lists.newArrayList(); } return executors; } @Override public GetJobConfigVo getJobConfigVo(String namespace, String jobName) throws SaturnJobConsoleException { JobConfig4DB jobConfig4DB = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig4DB == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, String.format("该作业(%s)不存在", jobName)); } GetJobConfigVo getJobConfigVo = new GetJobConfigVo(); JobConfig jobConfig = new JobConfig(); SaturnBeanUtils.copyProperties(jobConfig4DB, jobConfig); jobConfig.setDefaultValues(); getJobConfigVo.copyFrom(jobConfig); getJobConfigVo.setTimeZonesProvided(Arrays.asList(TimeZone.getAvailableIDs())); getJobConfigVo.setPreferListProvided(getCandidateExecutors(namespace, jobName)); getJobConfigVo.setUpStreamProvided(getCandidateUpStream(namespace, jobConfig)); getJobConfigVo.setDownStreamProvided(getCandidateDownStream(namespace, jobConfig)); CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); getJobConfigVo .setStatus(getJobStatus(getJobConfigVo.getJobName(), curatorFrameworkOp, getJobConfigVo.getEnabled())); return getJobConfigVo; } private List getCandidateDownStream(String namespace, JobConfig jobConfig) throws SaturnJobConsoleException { List candidateDownStream = new ArrayList<>(); if (!canBeUpStream(jobConfig)) { return candidateDownStream; } Set downStream = parseStreamToList(jobConfig.getDownStream()); List unSystemJobs = getUnSystemJobs(namespace); Set ancestors = getAncestors(namespace, jobConfig, unSystemJobs, new Stack(), false); for (JobConfig otherJob : unSystemJobs) { String otherJobName = otherJob.getJobName(); if (!jobConfig.getJobName().equals(otherJobName) && !downStream.contains(otherJobName) && !ancestors.contains(otherJobName) && canBeDownStream(otherJob)) { candidateDownStream.add(otherJobName); } } return candidateDownStream; } private boolean canBeDownStream(JobConfig jobConfig) { return JobType.isPassive(JobType.getJobType(jobConfig.getJobType())); } private List getCandidateUpStream(String namespace, JobConfig jobConfig) throws SaturnJobConsoleException { List candidateUpStream = new ArrayList<>(); if (!canBeDownStream(jobConfig)) { return candidateUpStream; } Set upStream = parseStreamToList(jobConfig.getUpStream()); List unSystemJobs = getUnSystemJobs(namespace); Set descendants = getDescendants(namespace, jobConfig, unSystemJobs, new Stack(), false); for (JobConfig otherJob : unSystemJobs) { String otherJobName = otherJob.getJobName(); if (jobConfig.getJobName().equals(otherJobName)) { continue; } if (upStream.contains(otherJobName)) { continue; } if (descendants.contains(otherJobName)) { continue; } if (canBeUpStream(otherJob)) { candidateUpStream.add(otherJobName); } } return candidateUpStream; } private boolean canBeUpStream(JobConfig jobConfig) { JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (!JobType.isCron(jobType) && !JobType.isPassive(jobType)) { return false; } if (jobConfig.getLocalMode() == Boolean.TRUE) { return false; } if (jobConfig.getShardingTotalCount() != null && jobConfig.getShardingTotalCount() > 1) { return false; } return true; } @Transactional(rollbackFor = Exception.class) @Override public void updateJobConfig(String namespace, JobConfig jobConfig, String updatedBy) throws SaturnJobConsoleException { JobConfig4DB oldJobConfig4DB = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobConfig.getJobName()); if (oldJobConfig4DB == null) { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, String.format("该作业(%s)不存在", jobConfig.getJobName())); } // 从数据库拿出老的数据,将需要更新的数据赋值(为空的字段视为不需要更新) JobConfig4DB newJobConfig4DB = new JobConfig4DB(); SaturnBeanUtils.copyProperties(oldJobConfig4DB, newJobConfig4DB); SaturnBeanUtils.copyPropertiesIgnoreNull(jobConfig, newJobConfig4DB); // 与老的数据库中的该作业的配置对比,如果没有改变,则直接返回 if (oldJobConfig4DB.equals(newJobConfig4DB)) { return; } // 设置作业配置字段默认值,并且强制纠正某些字段 correctConfigValueWhenUpdateJob(newJobConfig4DB); // 校验作业配置 List unSystemJobs = getUnSystemJobs(namespace); Set streamChangedJobs = new HashSet<>(); validateJobConfig(namespace, newJobConfig4DB, unSystemJobs, streamChangedJobs); // 更新该作业到数据库 currentJobConfigService.updateNewAndSaveOld2History(newJobConfig4DB, oldJobConfig4DB, updatedBy); // 更新关联作业的上下游 for (JobConfig streamChangedJob : streamChangedJobs) { currentJobConfigService.updateStream(constructJobConfig4DB(namespace, streamChangedJob, null, updatedBy)); } // 更新作业配置到zk,并联动更新关联作业的上下游 updateJobConfigToZk(newJobConfig4DB, streamChangedJobs, registryCenterService.getCuratorFrameworkOp(namespace)); } private void correctConfigValueWhenUpdateJob(JobConfig jobConfig) { // 对不符合要求的字段重新设置为默认值 jobConfig.setDefaultValues(); // 消息作业不failover不rerun JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (JobType.isMsg(jobType)) { jobConfig.setFailover(false); jobConfig.setRerun(false); } // 被动作业不rerun if (JobType.isPassive(jobType)) { jobConfig.setRerun(false); } // 本地模式不failover if (jobConfig.getLocalMode()) { jobConfig.setFailover(false); } // 不上报作业不failover不rerun if (!jobConfig.getEnabledReport()) { jobConfig.setFailover(false); jobConfig.setRerun(false); } } private void updateJobConfigToZk(JobConfig jobConfig, Set streamChangedJobs, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) throws SaturnJobConsoleException { try { String jobName = jobConfig.getJobName(); // 当关闭上报时,要清理execution节点 if (jobConfig.getEnabledReport() == Boolean.FALSE) { log.info("the switch of enabledReport set to false, now deleting the execution zk node"); String executionNodePath = JobNodePath.getExecutionNodePath(jobName); if (curatorFrameworkOp.checkExists(executionNodePath)) { curatorFrameworkOp.deleteRecursive(executionNodePath); } } CuratorFrameworkOp.CuratorTransactionOp curatorTransactionOp = curatorFrameworkOp.inTransaction(); // 数据库有可能有重复作业的数据,去重,zk无需更新两次 Collection streamChangedJobsNew = removeDuplicateByJobName(streamChangedJobs); // 更新关联作业的上下游 for (JobConfig streamChangedJob : streamChangedJobsNew) { String changedJobName = streamChangedJob.getJobName(); if (!curatorFrameworkOp.checkExists(JobNodePath.getConfigNodePath(changedJobName))) { // 数据库存在该作业,但是zk不存在该作业,为垃圾数据 log.warn("the job({}) config node is not existing in ZK", changedJobName); continue; } curatorTransactionOp .replaceIfChanged(JobNodePath.getConfigNodePath(changedJobName, CONFIG_ITEM_UPSTREAM), streamChangedJob.getUpStream()) .replaceIfChanged(JobNodePath.getConfigNodePath(changedJobName, CONFIG_ITEM_DOWNSTREAM), streamChangedJob.getDownStream()); } // 更新作业 curatorTransactionOp .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED), jobConfig.getEnabled()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DESCRIPTION), jobConfig.getDescription()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CUSTOM_CONTEXT), jobConfig.getCustomContext()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_TYPE), jobConfig.getJobType()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_MODE), jobConfig.getJobMode()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_ITEM_PARAMETERS), jobConfig.getShardingItemParameters()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_PARAMETER), jobConfig.getJobParameter()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_QUEUE_NAME), jobConfig.getQueueName()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CHANNEL_NAME), jobConfig.getChannelName()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_FAILOVER), jobConfig.getFailover()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_MONITOR_EXECUTION), "true") .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_4_ALARM_SECONDS), jobConfig.getTimeout4AlarmSeconds()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIMEOUT_SECONDS), jobConfig.getTimeoutSeconds()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_TIME_ZONE), jobConfig.getTimeZone()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON), jobConfig.getCron()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_DATE), jobConfig.getPausePeriodDate()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PAUSE_PERIOD_TIME), jobConfig.getPausePeriodTime()) .replaceIfChanged( JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PROCESS_COUNT_INTERVAL_SECONDS), jobConfig.getProcessCountIntervalSeconds()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHARDING_TOTAL_COUNT), jobConfig.getShardingTotalCount()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_SHOW_NORMAL_LOG), jobConfig.getShowNormalLog()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOAD_LEVEL), jobConfig.getLoadLevel()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_DEGREE), jobConfig.getJobDegree()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_ENABLED_REPORT), jobConfig.getEnabledReport()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_PREFER_LIST), jobConfig.getPreferList()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_DISPREFER_LIST), jobConfig.getUseDispreferList()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_LOCAL_MODE), jobConfig.getLocalMode()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_USE_SERIAL), jobConfig.getUseSerial()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DEPENDENCIES), jobConfig.getDependencies()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_GROUPS), jobConfig.getGroups()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_RERUN), jobConfig.getRerun()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_UPSTREAM), jobConfig.getUpStream()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_DOWNSTREAM), jobConfig.getDownStream()) .replaceIfChanged(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_JOB_CLASS), jobConfig.getJobClass()); // 提交事务 curatorTransactionOp.commit(); } catch (Exception e) { log.error("update job to zk failed", e); throw new SaturnJobConsoleException(e); } } private Collection removeDuplicateByJobName(Set streamChangedJobs) { Map streamChangedJobsMap = new HashMap<>(); for (JobConfig streamChangedJob : streamChangedJobs) { String jobName = streamChangedJob.getJobName(); if (streamChangedJobsMap.containsKey(jobName)) { log.warn("the DB have duplicated jobName({})", jobName); } else { streamChangedJobsMap.put(jobName, streamChangedJob); } } return streamChangedJobsMap.values(); } @Override public List getAllJobNamesFromZK(String namespace) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String jobsNodePath = JobNodePath.get$JobsNodePath(); List jobs = curatorFrameworkOp.getChildren(jobsNodePath); if (jobs == null) { return Lists.newArrayList(); } List allJobs = new ArrayList<>(); for (String job : jobs) { // 如果config节点存在才视为正常作业,其他异常作业在其他功能操作时也忽略 if (curatorFrameworkOp.checkExists(JobNodePath.getConfigNodePath(job))) { allJobs.add(job); } } Collections.sort(allJobs); return allJobs; } @Transactional(rollbackFor = Exception.class) @Override public void updateJobCron(String namespace, String jobName, String cron, Map customContext, String updatedBy) throws SaturnJobConsoleException { String cron0 = cron; if (cron0 != null && !cron0.trim().isEmpty()) { try { cron0 = cron0.trim(); CronExpression.validateExpression(cron0); } catch (ParseException e) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "The cron expression is invalid: " + cron); } } else { cron0 = ""; } CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); if (curatorFrameworkOp.checkExists(JobNodePath.getConfigNodePath(jobName))) { String newCustomContextStr = null; String oldCustomContextStr = curatorFrameworkOp .getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CUSTOM_CONTEXT)); Map oldCustomContextMap = toCustomContext(oldCustomContextStr); if (customContext != null && !customContext.isEmpty()) { oldCustomContextMap.putAll(customContext); newCustomContextStr = toCustomContext(oldCustomContextMap); if (newCustomContextStr.length() > 8000) { throw new SaturnJobConsoleException("The all customContext is out of db limit (Varchar[8000])"); } if (newCustomContextStr.getBytes().length > 1024 * 1024) { throw new SaturnJobConsoleException("The all customContext is out of zk limit memory(1M)"); } } String newCron = null; String oldCron = curatorFrameworkOp.getData(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON)); if (cron0 != null && oldCron != null && !cron0.equals(oldCron.trim())) { newCron = cron0; } if (newCustomContextStr != null || newCron != null) { saveCronToDb(jobName, curatorFrameworkOp, newCustomContextStr, newCron, updatedBy); } if (newCustomContextStr != null) { curatorFrameworkOp.update(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CUSTOM_CONTEXT), newCustomContextStr); } if (newCron != null) { curatorFrameworkOp.update(JobNodePath.getConfigNodePath(jobName, CONFIG_ITEM_CRON), newCron); } } else { throw new SaturnJobConsoleException(ERROR_CODE_NOT_EXISTED, "The job does not exists: " + jobName); } } private void saveCronToDb(String jobName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp, String newCustomContextStr, String newCron, String updatedBy) throws SaturnJobConsoleException { String namespace = curatorFrameworkOp.getCuratorFramework().getNamespace(); JobConfig4DB jobConfig4DB = currentJobConfigService.findConfigByNamespaceAndJobName(namespace, jobName); if (jobConfig4DB == null) { String errorMsg = "在DB找不到该作业的配置, namespace:" + namespace + " jobName:" + jobName; log.error(errorMsg); throw new SaturnJobConsoleHttpException(HttpStatus.INTERNAL_SERVER_ERROR.value(), errorMsg); } JobConfig4DB newJobConfig4DB = new JobConfig4DB(); SaturnBeanUtils.copyProperties(jobConfig4DB, newJobConfig4DB); if (newCustomContextStr != null) { newJobConfig4DB.setCustomContext(newCustomContextStr); } if (newCron != null) { newJobConfig4DB.setCron(newCron); } currentJobConfigService.updateNewAndSaveOld2History(newJobConfig4DB, jobConfig4DB, updatedBy); } /** * 将str转为map * * @param customContextStr str字符串 * @return 自定义上下文map */ private Map toCustomContext(String customContextStr) { Map customContext = null; if (customContextStr != null) { customContext = JsonUtils.fromJSON(customContextStr, customContextType); } if (customContext == null) { customContext = new HashMap<>(); } return customContext; } /** * 将map转为str字符串 * * @param customContextMap 自定义上下文map * @return 自定义上下文str */ private String toCustomContext(Map customContextMap) { String result = JsonUtils.toJSON(customContextMap); if (result == null) { result = ""; } return result.trim(); } @Override public List getJobServers(String namespace, String jobName) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); String serverNodePath = JobNodePath.getServerNodePath(jobName); List executors = curatorFrameworkOp.getChildren(serverNodePath); List result = new ArrayList<>(); if (executors != null && !executors.isEmpty()) { String leaderIp = curatorFrameworkOp.getData(JobNodePath.getLeaderNodePath(jobName, "election/host")); JobStatus jobStatus = getJobStatus(namespace, jobName); for (String each : executors) { JobServer jobServer = getJobServer(jobName, leaderIp, each, curatorFrameworkOp); jobServer.setJobStatus(jobStatus); result.add(jobServer); } } return result; } @Override public List getJobServersStatus(String namespace, String jobName) throws SaturnJobConsoleException { CuratorRepository.CuratorFrameworkOp curatorFrameworkOp = registryCenterService .getCuratorFrameworkOp(namespace); List executors = getJobServerList(namespace, jobName); List result = new ArrayList<>(); if (executors != null && !executors.isEmpty()) { for (String each : executors) { result.add(getJobServerStatus(jobName, each, curatorFrameworkOp)); } } return result; } private JobServerStatus getJobServerStatus(String jobName, String executorName, CuratorFrameworkOp curatorFrameworkOp) { JobServerStatus result = new JobServerStatus(); result.setExecutorName(executorName); result.setJobName(jobName); result.setServerStatus(getJobServerStatus0(jobName, executorName, curatorFrameworkOp)); return result; } private ServerStatus getJobServerStatus0(String jobName, String executorName, CuratorFrameworkOp curatorFrameworkOp) { String status = curatorFrameworkOp.getData(JobNodePath.getServerNodePath(jobName, executorName, "status")); return ServerStatus.getServerStatus(status); } private JobServer getJobServer(String jobName, String leaderIp, String executorName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { JobServer result = new JobServer(); result.setExecutorName(executorName); result.setIp(curatorFrameworkOp.getData(JobNodePath.getServerNodePath(jobName, executorName, "ip"))); result.setVersion(curatorFrameworkOp.getData(JobNodePath.getServerNodePath(jobName, executorName, "version"))); String processSuccessCount = curatorFrameworkOp .getData(JobNodePath.getServerNodePath(jobName, executorName, "processSuccessCount")); result.setProcessSuccessCount(null == processSuccessCount ? 0 : Integer.parseInt(processSuccessCount)); String processFailureCount = curatorFrameworkOp .getData(JobNodePath.getServerNodePath(jobName, executorName, "processFailureCount")); result.setProcessFailureCount(null == processFailureCount ? 0 : Integer.parseInt(processFailureCount)); result.setSharding( curatorFrameworkOp.getData(JobNodePath.getServerNodePath(jobName, executorName, "sharding"))); result.setStatus(getJobServerStatus0(jobName, executorName, curatorFrameworkOp)); result.setLeader(executorName.equals(leaderIp)); result.setJobVersion(getJobVersion(jobName, executorName, curatorFrameworkOp)); result.setContainer(curatorFrameworkOp.checkExists(ExecutorNodePath.getExecutorTaskNodePath(executorName))); return result; } private String getJobVersion(String jobName, String executorName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { String jobVersion = curatorFrameworkOp .getData(JobNodePath.getServerNodePath(jobName, executorName, "jobVersion")); return jobVersion == null ? "" : jobVersion; } @Override public void runAtOnce(String namespace, String jobName) throws SaturnJobConsoleException { JobStatus jobStatus = getJobStatus(namespace, jobName); if (!JobStatus.READY.equals(jobStatus)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该作业(%s)不处于READY状态,不能立即执行", jobName)); } List jobServersStatus = getJobServersStatus(namespace, jobName); if (jobServersStatus != null && !jobServersStatus.isEmpty()) { boolean hasOnlineExecutor = false; CuratorFrameworkOp curatorFrameworkOp = registryCenterService.getCuratorFrameworkOp(namespace); for (JobServerStatus jobServerStatus : jobServersStatus) { if (ServerStatus.ONLINE.equals(jobServerStatus.getServerStatus())) { hasOnlineExecutor = true; String executorName = jobServerStatus.getExecutorName(); String path = JobNodePath.getRunOneTimePath(jobName, executorName); if (curatorFrameworkOp.checkExists(path)) { curatorFrameworkOp.delete(path); } curatorFrameworkOp.create(path, "null"); log.info("runAtOnce namespace:{}, jobName:{}, executorName:{}", namespace, jobName, executorName); } } if (!hasOnlineExecutor) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "没有ONLINE的executor,不能立即执行"); } } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("没有executor接管该作业(%s),不能立即执行", jobName)); } } @Override public void stopAtOnce(String namespace, String jobName) throws SaturnJobConsoleException { JobStatus jobStatus = getJobStatus(namespace, jobName); if (!JobStatus.STOPPING.equals(jobStatus)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("该作业(%s)不处于STOPPING状态,不能立即终止", jobName)); } List jobServerList = getJobServerList(namespace, jobName); if (jobServerList != null && !jobServerList.isEmpty()) { CuratorFrameworkOp curatorFrameworkOp = registryCenterService.getCuratorFrameworkOp(namespace); for (String executorName : jobServerList) { String path = JobNodePath.getStopOneTimePath(jobName, executorName); if (curatorFrameworkOp.checkExists(path)) { curatorFrameworkOp.delete(path); } curatorFrameworkOp.create(path); log.info("stopAtOnce namespace:{}, jobName:{}, executorName:{}", namespace, jobName, executorName); } } else { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("没有executor接管该作业(%s),不能立即终止", jobName)); } } @Override public List getExecutionStatus(String namespace, String jobName) throws SaturnJobConsoleException { CuratorFrameworkOp curatorFrameworkOp = registryCenterService.getCuratorFrameworkOp(namespace); JobConfig jobConfig = getJobConfig(namespace, jobName); if (!jobConfig.getEnabled() && JobStatus.STOPPED.equals(getJobStatus(jobName, curatorFrameworkOp, false))) { return Lists.newArrayList(); } // update report node and sleep for 500ms updateReportNodeAndWait(jobName, curatorFrameworkOp, 500L); // 如果execution节点不存在则返回空List String executionNodePath = JobNodePath.getExecutionNodePath(jobName); List shardItems = curatorFrameworkOp.getChildren(executionNodePath); if (shardItems == null || shardItems.isEmpty()) { return Lists.newArrayList(); } List result = Lists.newArrayList(); Map itemExecutorMap = buildItem2ExecutorMap(jobName, curatorFrameworkOp); for (Map.Entry itemExecutorEntry : itemExecutorMap.entrySet()) { result.add(buildExecutionInfo(jobName, itemExecutorEntry.getKey(), itemExecutorEntry.getValue(), curatorFrameworkOp, jobConfig)); } // 可能有漏掉的running分片,比如新的机器接管了failover分片 for (String shardItem : shardItems) { if (itemExecutorMap.containsKey(shardItem)) { // 已经在之前的步骤计算了 continue; } String runningNodePath = JobNodePath.getExecutionNodePath(jobName, shardItem, "running"); boolean running = curatorFrameworkOp.checkExists(runningNodePath); if (running) { result.add(buildExecutionInfo(jobName, shardItem, null, curatorFrameworkOp, jobConfig)); } } Collections.sort(result); return result; } @Override public String getExecutionLog(String namespace, String jobName, String jobItem) throws SaturnJobConsoleException { CuratorFrameworkOp curatorFrameworkOp = registryCenterService.getCuratorFrameworkOp(namespace); String jobLogNodePath = JobNodePath.getExecutionNodePath(jobName, jobItem, "jobLog"); Stat stat = curatorFrameworkOp.getStat(jobLogNodePath); if (stat.getDataLength() > getMaxZnodeDataLength()) { log.warn("job log of job={} item={} exceed max length, will not display the original log", jobName, jobItem); return ERR_MSG_TOO_LONG_TO_DISPLAY; } return curatorFrameworkOp.getData(jobLogNodePath); } @Override public List getJobsByQueue(String queue) { return currentJobConfigService.findConfigByQueue(queue); } private void updateReportNodeAndWait(String jobName, CuratorFrameworkOp curatorFrameworkOp, long sleepInMill) { curatorFrameworkOp.update(JobNodePath.getReportPath(jobName), System.currentTimeMillis()); try { Thread.sleep(sleepInMill); } catch (Exception e) { log.error(e.getMessage(), e); } } private ExecutionInfo buildExecutionInfo(String jobName, String shardItem, String executorName, CuratorFrameworkOp curatorFrameworkOp, JobConfig jobConfig) { ExecutionInfo executionInfo = new ExecutionInfo(); executionInfo.setJobName(jobName); executionInfo.setItem(Integer.parseInt(shardItem)); setExecutorNameAndStatus(jobName, shardItem, executorName, curatorFrameworkOp, executionInfo, jobConfig); // jobMsg String jobMsg = curatorFrameworkOp.getData(JobNodePath.getExecutionNodePath(jobName, shardItem, "jobMsg")); executionInfo.setJobMsg(jobMsg); // timeZone String timeZoneStr = jobConfig.getTimeZone(); if (StringUtils.isBlank(timeZoneStr)) { timeZoneStr = SaturnConstants.TIME_ZONE_ID_DEFAULT; } executionInfo.setTimeZone(timeZoneStr); TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr); // last begin time String lastBeginTime = curatorFrameworkOp .getData(JobNodePath.getExecutionNodePath(jobName, shardItem, "lastBeginTime")); executionInfo.setLastBeginTime(SaturnConsoleUtils.parseMillisecond2DisplayTime(lastBeginTime, timeZone)); // next fire time, ignore if jobType is Msg JobType jobType = JobType.getJobType(jobConfig.getJobType()); if (JobType.isCron(jobType)) { String nextFireTime = curatorFrameworkOp .getData(JobNodePath.getExecutionNodePath(jobName, shardItem, "nextFireTime")); executionInfo.setNextFireTime(SaturnConsoleUtils.parseMillisecond2DisplayTime(nextFireTime, timeZone)); } else { executionInfo.setNextFireTime(null); } // last complete time String lastCompleteTime = curatorFrameworkOp .getData(JobNodePath.getExecutionNodePath(jobName, shardItem, "lastCompleteTime")); if (lastCompleteTime != null) { long lastCompleteTimeLong = Long.parseLong(lastCompleteTime); if (lastBeginTime == null) { executionInfo.setLastCompleteTime( SaturnConsoleUtils.parseMillisecond2DisplayTime(lastCompleteTime, timeZone)); } else { long lastBeginTimeLong = Long.parseLong(lastBeginTime); if (lastCompleteTimeLong >= lastBeginTimeLong) { executionInfo.setLastCompleteTime( SaturnConsoleUtils.parseMillisecond2DisplayTime(lastCompleteTime, timeZone)); executionInfo.setLastTimeConsumedInSec((lastCompleteTimeLong - lastBeginTimeLong) / 1000d); } } } return executionInfo; } private void setExecutorNameAndStatus(String jobName, String shardItem, String executorName, CuratorFrameworkOp curatorFrameworkOp, ExecutionInfo executionInfo, JobConfig jobConfig) { boolean isEnabledReport = jobConfig.getEnabledReport(); if (!isEnabledReport) { executionInfo.setExecutorName(executorName); executionInfo.setStatus(ExecutionStatus.BLANK); return; } boolean isCompleted = false; String completedNodePath = JobNodePath.getCompletedNodePath(jobName, shardItem); String completedData = curatorFrameworkOp.getData(completedNodePath); if (completedData != null) { isCompleted = true; executionInfo.setExecutorName(StringUtils.isNotBlank(completedData) ? completedData : executorName); // 不能立即返回还是要看看是否failed或者timeout } String failedNodePath = JobNodePath.getFailedNodePath(jobName, shardItem); if (curatorFrameworkOp.checkExists(failedNodePath)) { if (isCompleted) { executionInfo.setStatus(ExecutionStatus.FAILED); } else { log.warn(ERR_MSG_PENDING_STATUS, jobName, shardItem, executorName, "no completed node found but only failed node"); executionInfo.setExecutorName(executorName); executionInfo.setStatus(ExecutionStatus.PENDING); } return; } String timeoutNodePath = JobNodePath.getTimeoutNodePath(jobName, shardItem); if (curatorFrameworkOp.checkExists(timeoutNodePath)) { if (isCompleted) { executionInfo.setStatus(ExecutionStatus.TIMEOUT); } else { log.warn(ERR_MSG_PENDING_STATUS, jobName, shardItem, executorName, "no completed node found but only timeout node"); executionInfo.setExecutorName(executorName); executionInfo.setStatus(ExecutionStatus.PENDING); } return; } // 只有completed节点没有timeout/failed意味着成功,于是立即返回 if (isCompleted) { executionInfo.setStatus(ExecutionStatus.COMPLETED); return; } boolean isRunning = false; String runningNodePath = JobNodePath.getRunningNodePath(jobName, shardItem); String runningData = curatorFrameworkOp.getData(runningNodePath); if (runningData != null) { isRunning = true; executionInfo.setExecutorName(StringUtils.isBlank(runningData) ? executorName : runningData); long mtime = curatorFrameworkOp.getMtime(runningNodePath); executionInfo.setTimeConsumed((new Date().getTime() - mtime) / 1000); executionInfo.setStatus(ExecutionStatus.RUNNING); // 不能立即返回还是要看看是否正在failover } String failoverNodePath = JobNodePath.getFailoverNodePath(jobName, shardItem); String failoverData = curatorFrameworkOp.getData(failoverNodePath); if (failoverData != null) { // 设置为failover节点的真是executorName executionInfo.setExecutorName(failoverData); executionInfo.setFailover(true); // 如果有failover节点,running应该配对出现,否则显示pending状态 if (!isRunning) { log.warn(ERR_MSG_PENDING_STATUS, jobName, shardItem, executorName, "no running node found but only failover node"); executionInfo.setStatus(ExecutionStatus.PENDING); } return; } if (!isRunning) { log.warn(ERR_MSG_PENDING_STATUS, jobName, shardItem, executorName, "no running node or completed node found"); executionInfo.setStatus(ExecutionStatus.PENDING); } } private Map buildItem2ExecutorMap(String jobName, CuratorRepository.CuratorFrameworkOp curatorFrameworkOp) { String serverNodePath = JobNodePath.getServerNodePath(jobName); List servers = curatorFrameworkOp.getChildren(serverNodePath); if (servers == null || servers.isEmpty()) { return Maps.newHashMap(); } Map resultMap = new HashMap<>(); for (String server : servers) { resolveShardingData(jobName, curatorFrameworkOp, resultMap, server); } return resultMap; } private void resolveShardingData(String jobName, CuratorFrameworkOp curatorFrameworkOp, Map resultMap, String server) { String shardingData = curatorFrameworkOp.getData(JobNodePath.getServerSharding(jobName, server)); if (StringUtils.isBlank(shardingData)) { return; } String[] shardingValues = shardingData.split(","); for (String value : shardingValues) { if (StringUtils.isBlank(value)) { continue; } resultMap.put(value.trim(), server); } } private void validateShardingItemFormat(JobConfig jobConfig) throws SaturnJobConsoleException { String parameters = jobConfig.getShardingItemParameters(); String[] kvs = parameters.trim().split(","); for (int i = 0; i < kvs.length; i++) { String keyAndValue = kvs[i]; if (!keyAndValue.contains("=")) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("分片参数'%s'格式有误", keyAndValue)); } String key = keyAndValue.trim().split("=")[0].trim(); boolean isNumeric = StringUtils.isNumeric(key); if (!isNumeric) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, String.format("分片参数'%s'格式有误", jobConfig.getShardingItemParameters())); } } } /** * 批量设置作业的分组 * * @param namespace * @param jobNames 待设置分组的作业名集合 * @param oldGroupNames 修改前的分组名集合 * @param newGroupNames 修改后的分组名集合 * @param userName 操作者 */ @Transactional(rollbackFor = Exception.class) @Override public void batchSetGroups(String namespace, List jobNames, List oldGroupNames, List newGroupNames, String userName) throws SaturnJobConsoleException { if (CollectionUtils.isEmpty(jobNames)) { throw new SaturnJobConsoleException(ERROR_CODE_BAD_REQUEST, "请选择要分组的作业"); } if (CollectionUtils.isEmpty(oldGroupNames) && CollectionUtils.isEmpty(newGroupNames)) { return; } List oldGroupNamesTemp = null; List newGroupNamesTemp = null; Pattern pattern = Pattern.compile("[`!@#$%^!@#¥%……&*()|{}【】‘;:”“’。,、? ]"); if (!CollectionUtils.isEmpty(oldGroupNames)) { for (String groupName : oldGroupNames) { validateGroupName(groupName, pattern); } oldGroupNamesTemp = new ArrayList<>(oldGroupNames); } if (!CollectionUtils.isEmpty(newGroupNames)) { for (String groupName : newGroupNames) { validateGroupName(groupName, pattern); } newGroupNamesTemp = new ArrayList<>(newGroupNames); } // 新旧分组集合元素相同,无需处理 if (!CollectionUtils.isEmpty(oldGroupNames) && !CollectionUtils.isEmpty(newGroupNames)) { Collections.sort(oldGroupNames); Collections.sort(newGroupNames); if (oldGroupNames.toString().equals(newGroupNames.toString())) { return; } } // 求 oldGroupNamesTemp 与 newGroupNamesTemp 的差集(oldGroupNamesTemp // 中存在,newGroupNamesTemp 中不存在的元素),即删除的分组 if (!CollectionUtils.isEmpty(oldGroupNamesTemp) && !CollectionUtils.isEmpty(newGroupNamesTemp)) { oldGroupNamesTemp.removeAll(newGroupNamesTemp); } // 前端操作有删除分组,则将删除的分组从数据库中删除 if (!CollectionUtils.isEmpty(oldGroupNamesTemp)) { for (String groupName : oldGroupNamesTemp) { currentJobConfigService.batchSetGroups(namespace, jobNames, groupName, userName); } } // 求 newGroupNames 与 oldGroupNames 的差集(newGroupNames 中存在,oldGroupNames // 中不存在的元素),即新增的分组 if (!CollectionUtils.isEmpty(newGroupNames) && !CollectionUtils.isEmpty(oldGroupNames)) { newGroupNames.removeAll(oldGroupNames); } // 前端操作有新增分组,则将新增的分组追加到原有分组后面,用英文逗号连接 if (!CollectionUtils.isEmpty(newGroupNames)) { for (String groupName : newGroupNames) { currentJobConfigService.addToGroups(namespace, jobNames, groupName, userName); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy