| @ -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='告警记录表'; | |||||