Browse Source

新增服务和队列监控方法

master
bgy 3 months ago
parent
commit
08eb5f4587
19 changed files with 736 additions and 92 deletions
  1. +56
    -91
      pom.xml
  2. +2
    -0
      src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java
  3. +20
    -0
      src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java
  4. +18
    -0
      src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java
  5. +15
    -0
      src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java
  6. +18
    -0
      src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java
  7. +17
    -0
      src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java
  8. +15
    -0
      src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java
  9. +176
    -0
      src/main/java/com/topsail/scheduletask/service/MqMonitorService.java
  10. +38
    -0
      src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java
  11. +28
    -0
      src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java
  12. +81
    -0
      src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java
  13. +35
    -0
      src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java
  14. +92
    -0
      src/main/java/com/topsail/scheduletask/util/DingDingUtil.java
  15. +8
    -1
      src/main/resources/application.properties
  16. +30
    -0
      src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml
  17. +27
    -0
      src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml
  18. +21
    -0
      src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml
  19. +39
    -0
      src/main/resources/schema.sql

+ 56
- 91
pom.xml View File

@ -1,97 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.topsail</groupId> <groupId>com.topsail</groupId>
<artifactId>scheduletask</artifactId> <artifactId>scheduletask</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>scheduletask</name> <name>scheduletask</name>
<description>scheduletask</description>
<description>定时任务调度服务</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<mybatis.version>2.1.4</mybatis.version>
<hutool.version>5.8.10</hutool.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
<artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>1.8.0</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency> </dependency>
<!-- 添加junit测试单元包 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
<!-- <version> 1.5.1.RELEASE</version>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.6.RELEASE</version>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<!-- 分页pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
<optional>true</optional>
</dependency> </dependency>
<!-- import spring mail sender -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@ -100,45 +94,16 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.8.RELEASE</version>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<dependencies>
<!-- jdbc 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>com.itfsw</groupId>
<artifactId>mybatis-generator-plugin</artifactId>
<version>1.2.20</version>
</dependency>
</dependencies>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build> </build>
</project>
</project>

+ 2
- 0
src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java View File

@ -3,11 +3,13 @@ package com.topsail.scheduletask;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.TimeZone; import java.util.TimeZone;
@SpringBootApplication @SpringBootApplication
@MapperScan("com.topsail.scheduletask.mapper") @MapperScan("com.topsail.scheduletask.mapper")
@EnableScheduling
public class ScheduleTaskApplication { public class ScheduleTaskApplication {
public static void main(String[] args) { public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));


+ 20
- 0
src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java View File

@ -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);
}

+ 18
- 0
src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java View File

@ -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);
}

+ 15
- 0
src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java View File

@ -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);
}

+ 18
- 0
src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java View File

@ -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;
}

+ 17
- 0
src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java View File

@ -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;
}

+ 15
- 0
src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java View File

@ -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;
}

+ 176
- 0
src/main/java/com/topsail/scheduletask/service/MqMonitorService.java View File

@ -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);
}
}

+ 38
- 0
src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java View File

@ -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);
}
}
}

+ 28
- 0
src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java View File

@ -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);
}
}
}

+ 81
- 0
src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java View File

@ -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);
}
}
}

+ 35
- 0
src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java View File

@ -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);
}
}

+ 92
- 0
src/main/java/com/topsail/scheduletask/util/DingDingUtil.java View File

@ -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);
}
}
}

+ 8
- 1
src/main/resources/application.properties View File

@ -49,4 +49,11 @@ spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.port=994 spring.mail.properties.mail.smtp.socketFactory.port=994
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
### 监控配置 ###
monitor.service.timeout=30
monitor.mq.max-accumulation=1000
monitor.mq.max-delay=30000
monitor.dingding.webhook=
monitor.dingding.enabled=false

+ 30
- 0
src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml View File

@ -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>

+ 27
- 0
src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml View File

@ -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>

+ 21
- 0
src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml View File

@ -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>

+ 39
- 0
src/main/resources/schema.sql View File

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

Loading…
Cancel
Save