业务背景使用定时任务,去不同业务数据库,根据配置规则定时取数据,存入druid数据库
新建表存储corn表达式,任务业务,使用定时任务扫描数据表,动态添加删除task。
package com.zto.monitor.service.monitor;
import com.google.common.collect.Lists;
import com.zto.monitor.api.model.SqlCacheModel;
import com.zto.monitor.common.monitor.BusinessException;
import com.zto.monitor.common.monitor.DruidDataSourceConfig;
import com.zto.monitor.common.response.ResultResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.CollectionUtils;
import javax.annotation.PreDestroy;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Auther: T
* @Date: 2019/11/8 16:57
* @Description: 动态定时任务 修改 数据库 自动更新启动
*/
@Configuration
public class DynamicTask implements SchedulingConfigurer {
private static Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);
//taskRegistrar 任务注册模块
private volatile ScheduledTaskRegistrar registrar;
//任务 map
private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
//cron map
private final ConcurrentHashMap<String, CronTask> cronTasks = new ConcurrentHashMap<>();
//sql map
private final ConcurrentHashMap<String, String> sqlMap = new ConcurrentHashMap<>();
@Autowired
private MonitorService monitorService;
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
this.registrar = registrar;
this.registrar.addTriggerTask(() -> {
//查询所有sql
List<SqlCacheModel> taskConstants = monitorService.listSql();
if (!CollectionUtils.isEmpty(taskConstants)) {
// LOGGER.info("检测动态定时任务列表...");
List<TimingTask> tts = new ArrayList<>();
taskConstants
.forEach(taskConstant -> {
TimingTask tt = new TimingTask();
tt.setSql(taskConstant.getSql());
tt.setExpression(taskConstant.getCron());
tt.setTaskId("dynamic-task-" + taskConstant.getSqlStId());
tt.setUsername(taskConstant.getUsername());
tt.setPassword(taskConstant.getPassword());
tt.setUrl(taskConstant.getUrl());
tts.add(tt);
});
this.refreshTasks(tts);
}
}
, triggerContext -> new PeriodicTrigger(20L, TimeUnit.SECONDS).nextExecutionTime(triggerContext));
//新建 任务触发器 20s扫描一次数据库
}
private void refreshTasks(List<TimingTask> tasks) {
//取消已经删除的策略任务
Set<String> taskIds = scheduledFutures.keySet();
for (String taskId : taskIds) {
if (!exists(tasks, taskId)) {
scheduledFutures.get(taskId).cancel(false);
}
}
for (TimingTask tt : tasks) {
String expression = tt.getExpression();
String sql = tt.getSql();
if (StringUtils.isBlank(expression) || !CronSequenceGenerator.isValidExpression(expression)) {
LOGGER.error("定时任务DynamicTask cron表达式不合法: " + expression);
continue;
}
//如果配置一致,则不需要重新创建定时任务
if (scheduledFutures.containsKey(tt.getTaskId())
&& cronTasks.get(tt.getTaskId()).getExpression().equals(expression)
&& sqlMap.get(tt.getTaskId()).equals(sql)) {
continue;
}
//如果策略执行时间或sql发生了变化,则取消当前策略的任务
if (scheduledFutures.containsKey(tt.getTaskId())
&& (!cronTasks.get(tt.getTaskId()).getExpression().equals(expression) || !sqlMap.get(tt.getTaskId()).equals(sql))) {
scheduledFutures.remove(tt.getTaskId()).cancel(false);
cronTasks.remove(tt.getTaskId());
}
CronTask task = new CronTask(tt, expression);
ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
cronTasks.put(tt.getTaskId(), task);
scheduledFutures.put(tt.getTaskId(), future);
sqlMap.put(tt.getTaskId(), sql);
}
}
private boolean exists(List<TimingTask> tasks, String taskId) {
for (TimingTask task : tasks) {
if (task.getTaskId().equals(taskId)) {
return true;
}
}
return false;
}
@PreDestroy
public void destroy() {
this.registrar.destroy();
}
private class TimingTask implements Runnable {
private String username;
private String password;
private String url;
private String sql;
private String expression;
private String taskId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
// 定时任务 具体内容
@Override
public void run() {
final Connection connection = DruidDataSourceConfig.getConnection(
this.getUsername(),
this.getPassword(),
this.getUrl()
);
String sql = this.getSql();
final Object result = monitorService.executeSql(connection, sql);
ResultResponse resultResponse = null;
if (result instanceof ResultResponse) {
resultResponse = (ResultResponse) result;
}
assert resultResponse != null;
Object data = resultResponse.getData();
if (data == null) {
// throw new BusinessException(sql+"获取sql数据失败");
}
try {
monitorService.saveSqlData(Integer.parseInt(this.getTaskId().replace("dynamic-task-","")), data);
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
// log.info("成功保存sql数据:{}, sql: {}", data, sql);
DruidDataSourceConfig.close(connection);
}
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, TimingTask.class);
}
}
}