SpringBootSQL监控

零、人在地球

一个成熟的生产环境会存在很多sql,测试环境不能完全复现出生产环境的情况。因此我们需要一些数据监控内容

一般用于监控:

①一些重点sql是否正常(某些极端数据导致查询异常)

②一些跑批任务的执行结果

③一些表是否有数据(比如按日更新监控增量)

全部的sql都可以监控,出现异常直接发送预警消息

一、代码

1、依赖

  implementation 'org.springframework.boot:spring-boot-starter-quartz'

2、SQL表结构

T_JIANKONG 监控主表

CREATE TABLE T_JIANKONG(
  `id` char(32) NOT NULL COMMENT '主键',
  `childTaskName` varchar(64) NOT NULL COMMENT '子任务名称',
  `taskID` varchar(32) NOT NULL COMMENT '主任务ID 用户关联 推送、数据接收等需求的ID',
  `schema` varchar(255) DEFAULT NULL COMMENT '数据库名',
  `tableName` varchar(255) DEFAULT NULL COMMENT '表明,单表监控时填写',
  `cron` varchar(32) DEFAULT NULL COMMENT '执行时间',
  `executeSql` text COMMENT '执行sql',
  `excuteResult` text COMMENT '执行结果',
  `excuteTime` varchar(32) DEFAULT NULL COMMENT '执行时间',
  `principal` varchar(255) DEFAULT NULL COMMENT '推送地址:1,个人企微ID,2,企微群',
  `principalName` varchar(255) DEFAULT NULL COMMENT '负责人名称',
  `sendType` varchar(4) NOT NULL COMMENT '发送类型 1: 个人  2 发送群',
  `formula` varchar(255) DEFAULT NULL COMMENT '判断公式',
  `compareResult` varchar(255) DEFAULT NULL COMMENT '比较结果',
  `checkResult` varchar(32) DEFAULT NULL COMMENT '结果判断',
  `stateCode` varchar(100) DEFAULT '0' COMMENT '是否删除 0正常 1删除',
  PRIMARY KEY (`id`,`childTaskName`) USING BTREE,
  KEY `INX_TASKID` (`taskID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据监控表';

T_JIANKONG_LOG 监控日志表

CREATE TABLE T_JIANKONG_LOG(
  `id` char(32) NOT NULL COMMENT '主键',
  `childTaskName` varchar(64) DEFAULT NULL COMMENT '子任务名称',
  `excuteResult` text COMMENT '执行结果',
  `excuteTime` datetime DEFAULT NULL COMMENT '执行时间',
  `checkResult` varchar(32) DEFAULT NULL COMMENT '结果判断',
  `isSend` varchar(32) DEFAULT NULL COMMENT '是否发送告警信息',
  `stateCode` varchar(100) DEFAULT '0' COMMENT '是否删除 1正常 2删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `INX_TASKID` (`childTaskName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据监控历史表';

3、监控代码

主体类 EntityJob.java

public class EntityJob {
    private String id;
    private String cron;
    private String taskName;
    private String childTaskName;
    private String executeResult;
    private String executeSql;
    private String excuteTime;
    private String principal;
    private String sendType;
    private String formula;
    private String compareResult;
    private String checkResult;
    private String stateCode;
    private String principalName;
    private String schema;
    private String beanName;
}

监控服务Service DatamonitorService.java

@Service
public class DatamonitorService {

    public static final Logger logger = LoggerFactory.getLogger(DatamonitorService.class);

    @Resource
    private SqlSession sqlSession;

    @Resource
    private Scheduler scheduler ;

    private static final String DEFAULT_JOB_GROUP = "default_job_group";

    @PostConstruct
    public void init(){
        Map<String,String> params = org.elasticsearch.common.collect.Map.of("stateCode","0");
        List<EntityJob> list = sqlSession.selectList("com.longze.gsh.dao.getDataMonitor",params);
        for(EntityJob m : list){
            createJob(m);
        }

    }


    public void createJob (EntityJob entityJob){
        try {
            if (!CronExpression.isValidExpression(entityJob.getCron())){
                XxlJobHelper.log("cron is invalid");
                return ;
            }
            String sql =  entityJob.getExecuteSql().toUpperCase(Locale.ROOT);
            if(sql.contains("UPDATE") || sql.contains("DELETE") ||  sql.contains("DROP") ||  sql.contains("TRUNCATE") ||  sql.contains("INSERT") ){
                throw new SchedulerException("非法sql语句,只能使用select 语句");
            }
            if(!sql.contains("COUNT") && !sql.contains(" LIMIT ") ){
                throw new SchedulerException("数据监控只能使用count 语句");
            }
            // 构建任务
            JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(JobKey.jobKey(entityJob.getId(),DEFAULT_JOB_GROUP)).build() ;
            // 构建Cron调度器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                    .cronSchedule(entityJob.getCron())
                    .withMisfireHandlingInstructionDoNothing() ;// 不补偿
            // 任务触发器
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(entityJob.getId())
                    .withSchedule(scheduleBuilder).build() ;
            jobDetail.getJobDataMap().put(entityJob.getId(),entityJob);
            scheduler.scheduleJob(jobDetail,trigger) ;
            logger.info("添加新任务:"+ entityJob.getChildTaskName());
        } catch (SchedulerException e){
            logger.error("添加任务失败,任务名称:"+entityJob.getTaskName()+" : "+ e.getMessage());
        }
    }

    public void updateJob(EntityJob entityJob) {
        try {
            // 查询触发器Key
            if (!CronExpression.isValidExpression(entityJob.getCron())){
                XxlJobHelper.log("cron is invalid");
                throw new SchedulerException("cron is invalid");
            }
            //JobKey jobKey = getJobKey(entityJob);
            // 可能会更新 sql 和  cron  简单处理直接重建任务
            deleteJob(entityJob);
            createJob(entityJob);

//            TriggerKey triggerKey = new TriggerKey(jobKey.getName(), jobKey.getGroup());
//            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(entityJob.getCron());
//            CronTrigger trigger = TriggerBuilder.newTrigger()
//                    .withIdentity(entityJob.getId())
//                    .withSchedule(cronScheduleBuilder)
//                    .build();
//            scheduler.rescheduleJob(triggerKey, trigger);

        } catch (SchedulerException e) {
            XxlJobHelper.log("updateJob Fail,{}",e) ;
        }
    }

    public void deleteJob(EntityJob job) throws SchedulerException {
        JobKey key = getJobKey(job);
        scheduler.pauseJob(key);
        scheduler.unscheduleJob(TriggerKey.triggerKey(key.getName(), key.getGroup()));
        scheduler.deleteJob(key);
    }

    //获取JobDetail,JobDetail是任务的定义,而Job是任务的执行逻辑,JobDetail里会引用一个Job Class来定义
    public JobDetail getJobDetail(JobKey jobKey, String description, JobDataMap map) {
        return JobBuilder.newJob(QuartzJob.class)
                .withIdentity(jobKey)
                .withDescription(description)
                .setJobData(map)
                .storeDurably()
                .build();
    }

    //获取Trigger (Job的触发器,执行规则)
    public Trigger getTrigger(String triggerID) {
        return TriggerBuilder.newTrigger().withIdentity(triggerID)
                .build();
    }

    //获取JobKey,包含Name和Group
    public JobKey getJobKey(EntityJob job) {
        return JobKey.jobKey(job.getId());
    }

}

4、定时器类

public class QuartzJob implements Job {

    @Resource
    private com.alibaba.druid.pool.DruidDataSource dataSource;
    @Autowired
    private Map<String, MonitorProcessFactory> factoryMap;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String updateSql = "update T_JIANKONG SET excuteResult='%s',excuteTime=NOW(),checkResult='%s' where id='%s' ";
        String insertSql = "INSERT INTO `T_JIANKONG_LOG`(`id`, `childTaskName`, `excuteResult`, `excuteTime`, " +
                "`checkResult`, `isSend`, `stateCode`) VALUES (UUID_FACTORY(), '%s', '%s', NOW(), '%s', '%s', '0')";
        String content = "监控任务:%s \n" +
                "监控结果:%s \n" +
                "期待结果:%s \n" +
                "负责人:%s \n" +
                "执行时间:%s";
        JobDetail jobDetail = context.getJobDetail();
        JobKey jobKey = context.getTrigger().getJobKey();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        EntityJob entityJob = (EntityJob) jobDataMap.get(jobKey.getName());


        XxlJobHelper.log("执行检测 :" + entityJob.getTaskName());
        long r = 0;
        String checkResult = "";
        String isSend = "0";
        MonitorProcessFactory processFactory = factoryMap.get(entityJob.getBeanName());
        if (!StringUtils.isEmpty(entityJob.getBeanName()) && null != processFactory) {
            checkResult = processFactory.check(entityJob) ? "1" : "0";
        }
        try {
                String excuteSql = entityJob.getExecuteSql().replaceAll("\\{schema}", entityJob.getSchema());
                r = jdbcTemplate.query(excuteSql, rs -> {
                    if (rs.next()) {
                        return rs.getLong(1);
                    } else {
                        return 0l;
                    }

                });
                checkResult = check(entityJob.getFormula(), entityJob.getCompareResult(), r) ? "1" : "0";

            //发送预警
            if (checkResult.equals("0") && !StringUtils.isEmpty(entityJob.getPrincipal())) {
                if (entityJob.getSendType().equals("2")) {
                    // 发送企微群
                    RobotText robotText = new RobotText();
                    robotText.setContent(String.format(content, entityJob.getChildTaskName(), r, entityJob.getFormula()+entityJob.getCompareResult(), entityJob.getPrincipalName(),
                            DateUtil.formatDate(new Date(), DateUtil.DEFAULT_DATETIME_PATTERN)));
                    robotPushService.sendRobotText("QuartzJob", entityJob.getPrincipal(), robotText, "1", null, null);
                } else {
                    Text text = new Text();
                    text.setContent(String.format(content, entityJob.getChildTaskName(), r, entityJob.getCompareResult(), entityJob.getPrincipalName(),
                            DateUtil.formatDate(new Date(), DateUtil.DEFAULT_DATETIME_PATTERN)));
                    wechatOfficService.sendTemplateText(entityJob.getPrincipal(), Arrays.asList(entityJob.getPrincipal()), text, entityJob.getChildTaskName(), null);
                }
            }

        } catch (Exception e) {
            // GET MSG
            checkResult = "-1";
        } finally {
            //UPDATE
            String executeSql = String.format(updateSql, r, checkResult, entityJob.getId());
            jdbcTemplate.execute(executeSql);
            //插入历史记录表
            String executeSql2 = String.format(insertSql, entityJob.getChildTaskName(), r, checkResult, isSend);
            jdbcTemplate.execute(executeSql2);
        }

    }

    private boolean check(String formula, String value, long result) {
        if (formula.equals("等于")) {
            return Long.parseLong(value) == (result);
        }
        if (formula.equals("大于")) {
            return Long.parseLong(value) < (result);
        }
        if (formula.equals("小于")) {
            return Long.parseLong(value) > (result);
        }
        return true;
    }
}

5、比较方法工厂

可以根据设置的监控配置,设置自定义比较规则

复制代码
MonitorCheckFactory.java
@Component
public interface MonitorCheckFactory {
    // 特殊判断处理方法
    Boolean check(EntityJob entityJob);

}
复制代码
Test1Monitor.java
@Service("Test1Monitor")
public class Test1Monitor implements MonitorProcessFactory {
//    @Resource
//    private DruidDataSource dataSource;

    @Override
    public Boolean check(EntityJob entityJob) {
//        String executeSql = entityJob.getExecuteSql().replaceAll("\\{schema}", entityJob.getSchema());
//        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//        jdbcTemplate.execute(executeSql);

        return null;
    }
}

6、每次启动项目,会加载所有配置中监控sql,如果在项目启动中修改了数据库中配置的sql,正在执行的监控不会及时更新,需要一个任务单独更改跑批

复制代码
DataMonitorSchedule.java
@Component
public class DataMonitorSchedule {

    @Resource
    private SqlSession sqlSession;
    @Resource
    private DatamonitorService datamonitorService;

    @Resource
    private com.alibaba.druid.pool.DruidDataSource dataSource;
    @XxlJob("updateDataMonitor")
    public void updateJob() {
        String childTaskName ="";
        if(!StringUtils.isEmpty(XxlJobHelper.getJobParam())){
            childTaskName = XxlJobHelper.getJobParam();
        }
        Map<String,String> params = org.elasticsearch.common.collect.Map.of("stateCode","0","childTaskName",childTaskName);
        List<EntityJob> list = sqlSession.selectList("com.longze.gsh.dao.getDataMonitor",params);
        for(EntityJob m : list){
            datamonitorService.updateJob(m);
        }
    }
}

7、sql mapper文件

 <select id="getDataMonitor" resultType="com.longze.gsh.dataMonitor.EntityJob" parameterType="map">
          select * from T_JIANKONG where 1=1
          <if test="stateCode!=null and stateCode!=''">
            and stateCode=#{stateCode}
          </if>
          <if test="taskID!=null and taskID!=''">
              and taskID = #{taskID}
          </if>
            <if test="childTaskName!=null and childTaskName!=''">
                and childTaskName = #{childTaskName}
            </if>
    </select>
相关推荐
逊嘘4 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
morris13111 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
十叶知秋24 分钟前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试
七星静香36 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员37 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU38 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie641 分钟前
在IDEA中使用Git
java·git
Elaine2023911 小时前
06 网络编程基础
java·网络
G丶AEOM1 小时前
分布式——BASE理论
java·分布式·八股
落落鱼20131 小时前
tp接口 入口文件 500 错误原因
java·开发语言