| @ -0,0 +1,20 @@ | |||
| package com.topsail.scheduletask.mapper; | |||
| import com.topsail.scheduletask.pojo.AlertRecord; | |||
| import org.apache.ibatis.annotations.Mapper; | |||
| import org.apache.ibatis.annotations.Param; | |||
| import java.util.List; | |||
| @Mapper | |||
| public interface AlertRecordMapper { | |||
| void insert(AlertRecord alertRecord); | |||
| List<AlertRecord> selectByStatus(String status); | |||
| List<AlertRecord> selectByType(String alertType); | |||
| int updateStatus(@Param("id") Long id, @Param("status") String status, @Param("handleTime") java.util.Date handleTime); | |||
| List<AlertRecord> selectRecentAlerts(@Param("minutes") int minutes); | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package com.topsail.scheduletask.mapper; | |||
| import com.topsail.scheduletask.pojo.ServiceHeartbeat; | |||
| import org.apache.ibatis.annotations.Mapper; | |||
| import org.apache.ibatis.annotations.Param; | |||
| import java.util.List; | |||
| @Mapper | |||
| public interface HeartBeatMapper { | |||
| void upsertHeartbeat(ServiceHeartbeat heartbeat); | |||
| List<ServiceHeartbeat> selectTimeoutService(@Param("timeout") int timeout); | |||
| List<ServiceHeartbeat> selectAll(); | |||
| int deleteByServiceAndIp(@Param("serviceName") String serviceName, @Param("instanceIp") String instanceIp); | |||
| } | |||
| @ -0,0 +1,15 @@ | |||
| package com.topsail.scheduletask.mapper; | |||
| import com.topsail.scheduletask.pojo.MqMonitor; | |||
| import org.apache.ibatis.annotations.Mapper; | |||
| import java.util.List; | |||
| @Mapper | |||
| public interface MqMonitorMapper { | |||
| void insert(MqMonitor mqMonitor); | |||
| List<MqMonitor> selectByTopic(String topic); | |||
| List<MqMonitor> selectRecentByTopic(String topic); | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package com.topsail.scheduletask.pojo; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| @Data | |||
| public class AlertRecord { | |||
| private Long id; | |||
| private String alertType; | |||
| private String alertLevel; | |||
| private String alertMessage; | |||
| private String serviceName; | |||
| private String topic; | |||
| private String status; | |||
| private Date createTime; | |||
| private Date handleTime; | |||
| } | |||
| @ -0,0 +1,17 @@ | |||
| package com.topsail.scheduletask.pojo; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| @Data | |||
| public class MqMonitor { | |||
| private Long id; | |||
| private String mqType; | |||
| private String topic; | |||
| private String groupName; | |||
| private Long accumulationCount; | |||
| private Long delayTime; | |||
| private Date monitorTime; | |||
| private Date createTime; | |||
| } | |||
| @ -0,0 +1,15 @@ | |||
| package com.topsail.scheduletask.pojo; | |||
| import lombok.Data; | |||
| import java.util.Date; | |||
| @Data | |||
| public class ServiceHeartbeat { | |||
| private Long id; | |||
| private String serviceName; | |||
| private String instanceIp; | |||
| private Date lastHeartbeat; | |||
| private Date createTime; | |||
| private Date updateTime; | |||
| } | |||
| @ -0,0 +1,176 @@ | |||
| package com.topsail.scheduletask.service; | |||
| import com.topsail.scheduletask.pojo.AlertRecord; | |||
| import com.topsail.scheduletask.pojo.MqMonitor; | |||
| import com.topsail.scheduletask.mapper.AlertRecordMapper; | |||
| import com.topsail.scheduletask.mapper.MqMonitorMapper; | |||
| import com.topsail.scheduletask.util.DingDingUtil; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.stereotype.Service; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| @Service | |||
| public class MqMonitorService { | |||
| public static final Logger LOG = LoggerFactory.getLogger(MqMonitorService.class); | |||
| private final MqMonitorMapper mqMonitorMapper; | |||
| private final AlertRecordMapper alertRecordMapper; | |||
| private final DingDingUtil dingDingUtil; | |||
| private final AmqpService amqpService; | |||
| @Value("${monitor.mq.max-accumulation:1000}") | |||
| private long maxAccumulation; | |||
| @Value("${monitor.mq.max-delay:30000}") | |||
| private long maxDelay; | |||
| private static final Map<String, String> MONITOR_QUEUES = new HashMap<>(); | |||
| static { | |||
| MONITOR_QUEUES.put("order_queue", "order"); | |||
| MONITOR_QUEUES.put("pay_queue", "pay"); | |||
| } | |||
| private Map<String, Long> lastAlertTime = new ConcurrentHashMap<>(); | |||
| private static final long ALERT_INTERVAL = 60000; | |||
| @Autowired | |||
| public MqMonitorService(MqMonitorMapper mqMonitorMapper, AlertRecordMapper alertRecordMapper, | |||
| DingDingUtil dingDingUtil, AmqpService amqpService) { | |||
| this.mqMonitorMapper = mqMonitorMapper; | |||
| this.alertRecordMapper = alertRecordMapper; | |||
| this.dingDingUtil = dingDingUtil; | |||
| this.amqpService = amqpService; | |||
| } | |||
| public void monitorAllQueues() { | |||
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |||
| for (Map.Entry<String, String> entry : MONITOR_QUEUES.entrySet()) { | |||
| String queueName = entry.getKey(); | |||
| String queueType = entry.getValue(); | |||
| try { | |||
| monitorQueue(queueName, queueType, sdf); | |||
| } catch (Exception e) { | |||
| LOG.error("监控队列失败 - Queue: {}, Type: {}", queueName, queueType, e); | |||
| } | |||
| } | |||
| } | |||
| private void monitorQueue(String queueName, String queueType, SimpleDateFormat sdf) { | |||
| long accumulation = amqpService.getQueueMessageCount(queueName); | |||
| if (accumulation < 0) { | |||
| LOG.debug("队列 [{}] 不存在或获取消息数量失败", queueName); | |||
| return; | |||
| } | |||
| long delay = estimateDelay(queueName, accumulation); | |||
| saveMonitorRecord(queueName, queueType, accumulation, delay); | |||
| LOG.debug("RabbitMQ监控 - Queue: {}, Type: {}, 堆积: {}, 延迟估计: {}ms", | |||
| queueName, queueType, accumulation, delay); | |||
| String key = queueName; | |||
| Long lastTime = lastAlertTime.get(key); | |||
| if (accumulation > maxAccumulation) { | |||
| handleAccumulationAlert(key, lastTime, queueName, queueType, accumulation, sdf); | |||
| } else if (delay > maxDelay) { | |||
| handleDelayAlert(key, lastTime, queueName, queueType, delay, sdf); | |||
| } else { | |||
| lastAlertTime.remove(key); | |||
| } | |||
| } | |||
| private long estimateDelay(String queueName, long accumulation) { | |||
| if (accumulation == 0) { | |||
| return 0; | |||
| } | |||
| long avgConsumeTimePerMsg = 100; | |||
| return accumulation * avgConsumeTimePerMsg; | |||
| } | |||
| private void saveMonitorRecord(String queueName, String queueType, long accumulation, long delay) { | |||
| MqMonitor mqMonitor = new MqMonitor(); | |||
| mqMonitor.setMqType("RabbitMQ"); | |||
| mqMonitor.setTopic(queueName); | |||
| mqMonitor.setGroupName(queueType); | |||
| mqMonitor.setAccumulationCount(accumulation); | |||
| mqMonitor.setDelayTime(delay); | |||
| mqMonitor.setMonitorTime(new Date()); | |||
| mqMonitorMapper.insert(mqMonitor); | |||
| } | |||
| private void handleAccumulationAlert(String key, Long lastTime, String queueName, String queueType, | |||
| long accumulation, SimpleDateFormat sdf) { | |||
| if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) { | |||
| String msg = String.format( | |||
| "【RabbitMQ堆积告警】\n队列:%s\n类型:%s\n堆积数量:%d\n阈值:%d\n监控时间:%s", | |||
| queueName, queueType, accumulation, maxAccumulation, sdf.format(new Date()) | |||
| ); | |||
| LOG.error(msg); | |||
| dingDingUtil.sendMarkdownAlert("RabbitMQ堆积告警", msg); | |||
| saveAlertRecord("MQ_ACCUMULATION", "WARN", msg, queueName, null); | |||
| lastAlertTime.put(key, System.currentTimeMillis()); | |||
| } | |||
| } | |||
| private void handleDelayAlert(String key, Long lastTime, String queueName, String queueType, | |||
| long delay, SimpleDateFormat sdf) { | |||
| if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) { | |||
| String msg = String.format( | |||
| "【RabbitMQ延迟告警】\n队列:%s\n类型:%s\n延迟时间:%dms\n阈值:%dms\n监控时间:%s", | |||
| queueName, queueType, delay, maxDelay, sdf.format(new Date()) | |||
| ); | |||
| LOG.error(msg); | |||
| dingDingUtil.sendMarkdownAlert("RabbitMQ延迟告警", msg); | |||
| saveAlertRecord("MQ_ACCUMULATION", "WARN", msg, queueName, null); | |||
| lastAlertTime.put(key, System.currentTimeMillis()); | |||
| } | |||
| } | |||
| private void saveAlertRecord(String alertType, String alertLevel, String alertMessage, | |||
| String topic, String serviceName) { | |||
| AlertRecord alert = new AlertRecord(); | |||
| alert.setAlertType(alertType); | |||
| alert.setAlertLevel(alertLevel); | |||
| alert.setAlertMessage(alertMessage); | |||
| alert.setTopic(topic); | |||
| alert.setServiceName(serviceName); | |||
| alert.setStatus("UNHANDLED"); | |||
| alertRecordMapper.insert(alert); | |||
| } | |||
| public Map<String, String> getMonitorQueues() { | |||
| return MONITOR_QUEUES; | |||
| } | |||
| public long getMaxAccumulation() { | |||
| return maxAccumulation; | |||
| } | |||
| public long getMaxDelay() { | |||
| return maxDelay; | |||
| } | |||
| public void addMonitorQueue(String queueName, String queueType) { | |||
| MONITOR_QUEUES.put(queueName, queueType); | |||
| } | |||
| public void removeMonitorQueue(String queueName) { | |||
| MONITOR_QUEUES.remove(queueName); | |||
| } | |||
| } | |||
| @ -0,0 +1,38 @@ | |||
| package com.topsail.scheduletask.task; | |||
| import com.topsail.scheduletask.pojo.ServiceHeartbeat; | |||
| import com.topsail.scheduletask.mapper.HeartBeatMapper; | |||
| import com.topsail.scheduletask.util.ApplicationContextUtil; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.scheduling.annotation.Scheduled; | |||
| import org.springframework.stereotype.Component; | |||
| import java.net.InetAddress; | |||
| import java.util.Date; | |||
| @Slf4j | |||
| @Component | |||
| public class HeartBeatTask { | |||
| @Autowired | |||
| private HeartBeatMapper heartBeatMapper; | |||
| @Scheduled(fixedRate = 10000) | |||
| public void heartbeat() { | |||
| try { | |||
| String serviceName = ApplicationContextUtil.getApplicationName(); | |||
| String ip = InetAddress.getLocalHost().getHostAddress(); | |||
| ServiceHeartbeat heartbeat = new ServiceHeartbeat(); | |||
| heartbeat.setServiceName(serviceName); | |||
| heartbeat.setInstanceIp(ip); | |||
| heartbeat.setLastHeartbeat(new Date()); | |||
| heartBeatMapper.upsertHeartbeat(heartbeat); | |||
| log.debug("心跳上报成功 - 服务: {}, IP: {}", serviceName, ip); | |||
| } catch (Exception e) { | |||
| log.error("心跳上报失败", e); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,28 @@ | |||
| package com.topsail.scheduletask.task; | |||
| import com.topsail.scheduletask.service.MqMonitorService; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.scheduling.annotation.Scheduled; | |||
| import org.springframework.stereotype.Component; | |||
| @Slf4j | |||
| @Component | |||
| public class MqAccumulationMonitor { | |||
| private final MqMonitorService mqMonitorService; | |||
| @Autowired | |||
| public MqAccumulationMonitor(MqMonitorService mqMonitorService) { | |||
| this.mqMonitorService = mqMonitorService; | |||
| } | |||
| @Scheduled(fixedRate = 30000) | |||
| public void monitorMq() { | |||
| try { | |||
| mqMonitorService.monitorAllQueues(); | |||
| } catch (Exception e) { | |||
| log.error("RabbitMQ监控任务执行失败", e); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,81 @@ | |||
| package com.topsail.scheduletask.task; | |||
| import com.topsail.scheduletask.pojo.AlertRecord; | |||
| import com.topsail.scheduletask.pojo.ServiceHeartbeat; | |||
| import com.topsail.scheduletask.mapper.AlertRecordMapper; | |||
| import com.topsail.scheduletask.mapper.HeartBeatMapper; | |||
| import com.topsail.scheduletask.util.DingDingUtil; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.scheduling.annotation.Scheduled; | |||
| import org.springframework.stereotype.Component; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| @Slf4j | |||
| @Component | |||
| public class ServiceDownMonitor { | |||
| @Autowired | |||
| private HeartBeatMapper heartBeatMapper; | |||
| @Autowired | |||
| private AlertRecordMapper alertRecordMapper; | |||
| @Autowired | |||
| private DingDingUtil dingDingUtil; | |||
| @Value("${monitor.service.timeout:30}") | |||
| private int timeout; | |||
| private Map<String, Long> lastAlertTime = new ConcurrentHashMap<>(); | |||
| private static final long ALERT_INTERVAL = 60000; | |||
| @Scheduled(fixedRate = 60000) | |||
| public void checkServiceDown() { | |||
| try { | |||
| List<ServiceHeartbeat> deadList = heartBeatMapper.selectTimeoutService(timeout); | |||
| if (deadList.isEmpty()) { | |||
| log.debug("所有服务心跳正常"); | |||
| return; | |||
| } | |||
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |||
| for (ServiceHeartbeat dead : deadList) { | |||
| String key = dead.getServiceName() + "_" + dead.getInstanceIp(); | |||
| Long lastTime = lastAlertTime.get(key); | |||
| if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) { | |||
| String msg = String.format( | |||
| "【服务宕机告警】\n服务名:%s\n实例IP:%s\n最后心跳:%s\n超时时间:%d秒", | |||
| dead.getServiceName(), | |||
| dead.getInstanceIp(), | |||
| sdf.format(dead.getLastHeartbeat()), | |||
| timeout | |||
| ); | |||
| log.error(msg); | |||
| dingDingUtil.sendMarkdownAlert("服务宕机告警", msg); | |||
| AlertRecord alert = new AlertRecord(); | |||
| alert.setAlertType("SERVICE_DOWN"); | |||
| alert.setAlertLevel("ERROR"); | |||
| alert.setAlertMessage(msg); | |||
| alert.setServiceName(dead.getServiceName()); | |||
| alert.setStatus("UNHANDLED"); | |||
| alertRecordMapper.insert(alert); | |||
| lastAlertTime.put(key, System.currentTimeMillis()); | |||
| } | |||
| } | |||
| } catch (Exception e) { | |||
| log.error("服务宕机监控异常", e); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| package com.topsail.scheduletask.util; | |||
| import org.springframework.beans.BeansException; | |||
| import org.springframework.context.ApplicationContext; | |||
| import org.springframework.context.ApplicationContextAware; | |||
| import org.springframework.stereotype.Component; | |||
| @Component | |||
| public class ApplicationContextUtil implements ApplicationContextAware { | |||
| private static ApplicationContext applicationContext; | |||
| @Override | |||
| public void setApplicationContext(ApplicationContext ctx) throws BeansException { | |||
| applicationContext = ctx; | |||
| } | |||
| public static ApplicationContext getApplicationContext() { | |||
| return applicationContext; | |||
| } | |||
| public static String getApplicationName() { | |||
| if (applicationContext != null) { | |||
| return applicationContext.getEnvironment().getProperty("spring.application.name", "unknown-service"); | |||
| } | |||
| return "unknown-service"; | |||
| } | |||
| public static <T> T getBean(Class<T> clazz) { | |||
| return applicationContext.getBean(clazz); | |||
| } | |||
| public static Object getBean(String name) { | |||
| return applicationContext.getBean(name); | |||
| } | |||
| } | |||
| @ -0,0 +1,92 @@ | |||
| package com.topsail.scheduletask.util; | |||
| import cn.hutool.http.HttpRequest; | |||
| import cn.hutool.http.HttpResponse; | |||
| import com.alibaba.fastjson.JSON; | |||
| import com.alibaba.fastjson.JSONObject; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.stereotype.Component; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| @Slf4j | |||
| @Component | |||
| public class DingDingUtil { | |||
| @Value("${monitor.dingding.webhook:}") | |||
| private String webhook; | |||
| @Value("${monitor.dingding.enabled:true}") | |||
| private boolean enabled; | |||
| public void sendAlert(String message) { | |||
| if (!enabled || webhook == null || webhook.isEmpty()) { | |||
| log.info("钉钉告警已禁用或未配置,消息: {}", message); | |||
| return; | |||
| } | |||
| try { | |||
| Map<String, Object> body = new HashMap<>(); | |||
| Map<String, Object> text = new HashMap<>(); | |||
| text.put("content", message); | |||
| body.put("msgtype", "text"); | |||
| body.put("text", text); | |||
| HttpResponse response = HttpRequest.post(webhook) | |||
| .header("Content-Type", "application/json;charset=utf-8") | |||
| .body(JSON.toJSONString(body)) | |||
| .execute(); | |||
| if (response.isOk()) { | |||
| String result = response.body(); | |||
| JSONObject jsonResult = JSON.parseObject(result); | |||
| if ("0".equals(jsonResult.getString("errcode"))) { | |||
| log.info("钉钉告警发送成功"); | |||
| } else { | |||
| log.error("钉钉告警发送失败: {}", result); | |||
| } | |||
| } else { | |||
| log.error("钉钉告警请求失败,状态码: {}", response.getStatus()); | |||
| } | |||
| } catch (Exception e) { | |||
| log.error("钉钉告警发送异常", e); | |||
| } | |||
| } | |||
| public void sendMarkdownAlert(String title, String text) { | |||
| if (!enabled || webhook == null || webhook.isEmpty()) { | |||
| log.info("钉钉告警已禁用或未配置,标题: {}, 内容: {}", title, text); | |||
| return; | |||
| } | |||
| try { | |||
| Map<String, Object> body = new HashMap<>(); | |||
| Map<String, Object> markdown = new HashMap<>(); | |||
| markdown.put("title", title); | |||
| markdown.put("text", text); | |||
| body.put("msgtype", "markdown"); | |||
| body.put("markdown", markdown); | |||
| HttpResponse response = HttpRequest.post(webhook) | |||
| .header("Content-Type", "application/json;charset=utf-8") | |||
| .body(JSON.toJSONString(body)) | |||
| .execute(); | |||
| if (response.isOk()) { | |||
| String result = response.body(); | |||
| JSONObject jsonResult = JSON.parseObject(result); | |||
| if ("0".equals(jsonResult.getString("errcode"))) { | |||
| log.info("钉钉Markdown告警发送成功"); | |||
| } else { | |||
| log.error("钉钉Markdown告警发送失败: {}", result); | |||
| } | |||
| } else { | |||
| log.error("钉钉Markdown告警请求失败,状态码: {}", response.getStatus()); | |||
| } | |||
| } catch (Exception e) { | |||
| log.error("钉钉Markdown告警发送异常", e); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,30 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.topsail.scheduletask.mapper.AlertRecordMapper"> | |||
| <insert id="insert" parameterType="com.topsail.scheduletask.pojo.AlertRecord"> | |||
| INSERT INTO alert_record (alert_type, alert_level, alert_message, service_name, topic, status) | |||
| VALUES (#{alertType}, #{alertLevel}, #{alertMessage}, #{serviceName}, #{topic}, #{status}) | |||
| </insert> | |||
| <select id="selectByStatus" resultType="com.topsail.scheduletask.pojo.AlertRecord"> | |||
| SELECT * FROM alert_record WHERE status = #{status} ORDER BY create_time DESC | |||
| </select> | |||
| <select id="selectByType" resultType="com.topsail.scheduletask.pojo.AlertRecord"> | |||
| SELECT * FROM alert_record WHERE alert_type = #{alertType} ORDER BY create_time DESC | |||
| </select> | |||
| <update id="updateStatus"> | |||
| UPDATE alert_record | |||
| SET status = #{status}, handle_time = #{handleTime} | |||
| WHERE id = #{id} | |||
| </update> | |||
| <select id="selectRecentAlerts" resultType="com.topsail.scheduletask.pojo.AlertRecord"> | |||
| SELECT * FROM alert_record | |||
| WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{minutes} MINUTE) | |||
| ORDER BY create_time DESC | |||
| </select> | |||
| </mapper> | |||
| @ -0,0 +1,27 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.topsail.scheduletask.mapper.HeartBeatMapper"> | |||
| <insert id="upsertHeartbeat" parameterType="com.topsail.scheduletask.pojo.ServiceHeartbeat"> | |||
| INSERT INTO service_heartbeat (service_name, instance_ip, last_heartbeat) | |||
| VALUES (#{serviceName}, #{instanceIp}, #{lastHeartbeat}) | |||
| ON DUPLICATE KEY UPDATE | |||
| last_heartbeat = VALUES(last_heartbeat), | |||
| update_time = CURRENT_TIMESTAMP | |||
| </insert> | |||
| <select id="selectTimeoutService" resultType="com.topsail.scheduletask.pojo.ServiceHeartbeat"> | |||
| SELECT * FROM service_heartbeat | |||
| WHERE TIMESTAMPDIFF(SECOND, last_heartbeat, NOW()) > #{timeout} | |||
| </select> | |||
| <select id="selectAll" resultType="com.topsail.scheduletask.pojo.ServiceHeartbeat"> | |||
| SELECT * FROM service_heartbeat ORDER BY service_name, instance_ip | |||
| </select> | |||
| <delete id="deleteByServiceAndIp"> | |||
| DELETE FROM service_heartbeat | |||
| WHERE service_name = #{serviceName} AND instance_ip = #{instanceIp} | |||
| </delete> | |||
| </mapper> | |||
| @ -0,0 +1,21 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
| <mapper namespace="com.topsail.scheduletask.mapper.MqMonitorMapper"> | |||
| <insert id="insert" parameterType="com.topsail.scheduletask.pojo.MqMonitor"> | |||
| INSERT INTO mq_monitor (mq_type, topic, group_name, accumulation_count, delay_time, monitor_time) | |||
| VALUES (#{mqType}, #{topic}, #{groupName}, #{accumulationCount}, #{delayTime}, #{monitorTime}) | |||
| </insert> | |||
| <select id="selectByTopic" resultType="com.topsail.scheduletask.pojo.MqMonitor"> | |||
| SELECT * FROM mq_monitor WHERE topic = #{topic} ORDER BY monitor_time DESC | |||
| </select> | |||
| <select id="selectRecentByTopic" resultType="com.topsail.scheduletask.pojo.MqMonitor"> | |||
| SELECT * FROM mq_monitor | |||
| WHERE topic = #{topic} | |||
| AND monitor_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR) | |||
| ORDER BY monitor_time DESC | |||
| </select> | |||
| </mapper> | |||
| @ -0,0 +1,39 @@ | |||
| CREATE TABLE IF NOT EXISTS `service_heartbeat` ( | |||
| `id` bigint(20) NOT NULL AUTO_INCREMENT, | |||
| `service_name` varchar(64) NOT NULL COMMENT '服务名', | |||
| `instance_ip` varchar(32) NOT NULL COMMENT '实例IP', | |||
| `last_heartbeat` datetime NOT NULL COMMENT '最后心跳时间', | |||
| `create_time` datetime DEFAULT CURRENT_TIMESTAMP, | |||
| `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |||
| PRIMARY KEY (`id`), | |||
| UNIQUE KEY `uk_service_instance` (`service_name`,`instance_ip`) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务心跳表'; | |||
| CREATE TABLE IF NOT EXISTS `mq_monitor` ( | |||
| `id` bigint(20) NOT NULL AUTO_INCREMENT, | |||
| `mq_type` varchar(32) NOT NULL DEFAULT 'RocketMQ' COMMENT 'MQ类型', | |||
| `topic` varchar(128) NOT NULL COMMENT '队列主题', | |||
| `group_name` varchar(128) DEFAULT NULL COMMENT '消费组', | |||
| `accumulation_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '堆积数量', | |||
| `delay_time` bigint(20) DEFAULT 0 COMMENT '延迟时间(ms)', | |||
| `monitor_time` datetime NOT NULL COMMENT '监控时间', | |||
| `create_time` datetime DEFAULT CURRENT_TIMESTAMP, | |||
| PRIMARY KEY (`id`), | |||
| KEY `idx_topic` (`topic`), | |||
| KEY `idx_monitor_time` (`monitor_time`) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MQ堆积监控表'; | |||
| CREATE TABLE IF NOT EXISTS `alert_record` ( | |||
| `id` bigint(20) NOT NULL AUTO_INCREMENT, | |||
| `alert_type` varchar(32) NOT NULL COMMENT '告警类型(SERVICE_DOWN/MQ_ACCUMULATION)', | |||
| `alert_level` varchar(16) NOT NULL DEFAULT 'WARN' COMMENT '告警级别', | |||
| `alert_message` text COMMENT '告警消息', | |||
| `service_name` varchar(64) DEFAULT NULL COMMENT '服务名', | |||
| `topic` varchar(128) DEFAULT NULL COMMENT 'MQ主题', | |||
| `status` varchar(16) NOT NULL DEFAULT 'UNHANDLED' COMMENT '处理状态', | |||
| `create_time` datetime DEFAULT CURRENT_TIMESTAMP, | |||
| `handle_time` datetime DEFAULT NULL, | |||
| PRIMARY KEY (`id`), | |||
| KEY `idx_alert_type` (`alert_type`), | |||
| KEY `idx_status` (`status`) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警记录表'; | |||