检测中心
最近又有一个新的构思,就是通过SpringBoot Actuator,完成对于所有的service模块的监控,并且在admin显示数据,这个思路来源于阿里云
类似于这样显示一个折线图
思路:
1.这个监测功能整合到sta模块,建立一个数据库service_sta,里面建立一个表ramMonitor,字段(id,服务名称,ram占用,时间),再建立一个表cpuMonitor(id,服务名称,cpu占用率,时间)
2.所有模块引入actuator依赖,并且配置使sta模块可以通过http请求获取服务的运行状态(这里我计划用cloud的openfeign写内部掉用,整好可以借助gateway中的路由)
3.在task模块创建一个每小时执行一次的任务,通过mq传给sta模块
4.sta模块接收到消息之后开始逐个发送请求,获取每一个服务的数据,(初始的构思是一分钟一次)
5.当服务内存占用特别多的时候,msm模块会发送邮件告诉管理员,服务坏了.
6.未来展望(画饼),现在监测中心只是可以对服务进行一个监测功能,这里我希望他在监测到服务异常之后会自动上线新的服务,减少线上的损失
Actuator
这里我主要是通过请求这两个来获取参数
堆内存
localhost:端口号/actuator/metrics/jvm.memory.used?tag=area:heap
系统cpu
localhost:端口号/actuator/metrics/system.cpu.usage
之前有写过一个springboot整合Actuator,这是之前的博客
指标监控 - 小岚 (zhaodapiaoliang.top)
这里面用到了一个叫spring-boot-admin的第三方组件,这里我分析了他的路由
获取了上面两个url,下一步就是,下面是spring-boot-admin的效果图
接下来就是写代码的过程了我会在b站直播:https://space.bilibili.com/443685731
在我们的所有service中添加依赖同时配置所有模块的openFeign依赖
<!--服务监测-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
/**
* order模块堆内存占用情况
*/
@GetMapping("/actuator/metrics/jvm.memory.used?tag=area:heap")
public String getRam();
/**
* order模块内存占用率
* @return
*/
@GetMapping("/actuator/metrics/system.cpu.usage")
public String getCpu();
数据库
建表语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for cpu
-- ----------------------------
DROP TABLE IF EXISTS `cpu`;
CREATE TABLE `cpu` (
`id` int(16) NOT NULL AUTO_INCREMENT,
`service_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`cpu_size` double(4, 2) NULL DEFAULT NULL,
`time` datetime NULL DEFAULT NULL,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(3) NULL DEFAULT 0 COMMENT '逻辑删除(1:已删除,0:未删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 260 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for ram
-- ----------------------------
DROP TABLE IF EXISTS `ram`;
CREATE TABLE `ram` (
`id` int(16) NOT NULL AUTO_INCREMENT,
`service_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ram_size` int(11) NULL DEFAULT NULL,
`time` datetime NULL DEFAULT NULL,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '逻辑删除(1:已删除,0:未删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 244 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
model模块
@Data
public class ActuatorVo {
private String name;
private String description;
private String baseUnit;
private List<Measurements> measurements ;
private List<AvailableTags> availableTags ;
}
@Data
public class CpuVo {
@ApiModelProperty(value = "时间")
private String time;
@ApiModelProperty(value = "cpu占用率")
private double cpuSize;
@ApiModelProperty(value = "服务名称")
private String serviceName;
}
@Data
public class Measurements {
private String statistic;
private String value;
}
@Data
public class RamVo {
@ApiModelProperty(value = "时间")
private String time;
@ApiModelProperty(value = "占用内存大小")
private int ramSize;
@ApiModelProperty(value = "服务名称")
private String serviceName;
}
@TableName("cpu")
@Data
public class CpuEntity extends BaseEntity {
@TableField("service_name")
private String serviceName;
@TableField("cpu_size")
private Double cpuSize;
@TableField("time")
private Date time;
}
@TableName("ram")
@Data
public class RamEntity extends BaseEntity {
@TableField("service_name")
private String serviceName;
@TableField("ram_size")
private int ramSize;
@TableField("time")
private Date time;
}
sta模块
集成mybatis-plus
<?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.example.yygh.sta.mapper.ramMapper">
<select id="selectActuator" resultType="com.example.yygh.vo.sta.RamVo">
SELECT
time AS time,
ram_size AS RamSize,
service_name AS serviceName
FROM
ram
ORDER BY
id DESC
LIMIT 8
</select>
</mapper>
建立controller
@RestController
@RequestMapping("/admin/sta")
public class ActuatorController {
@Autowired
private cpuService cpuService;
@Autowired
private ramService ramService;
@GetMapping("/armTest")
public void armTest() {
ramService.save();
}
@GetMapping("/cpuTest")
public void cpuTest() {
cpuService.save();
}
@GetMapping("getCpu")
public Result showCpu() {
return Result.ok(cpuService.show());
}
@GetMapping("getRam")
public Result showRam() {
return Result.ok(ramService.show());
}
}
建立service
@Service
public class cpuServiceImpl extends ServiceImpl<cpuMapper, CpuEntity> implements cpuService {
@Autowired
private List<cpuActuatorCore> cpuActuatorCores;
@Autowired
private RabbitService rabbitService;
@Override
public void save() {
cpuActuatorCores.forEach(i -> {
CpuEntity cpuEntity = i.saveCpu();
if (cpuEntity.getCpuSize() > 95) {
MsmVo msmVo = new MsmVo();
HashMap<String, Object> map = new HashMap<>();
map.put("title", cpuEntity.getServiceName() + "cpu");
map.put("service", cpuEntity.getServiceName() + "cpu");
msmVo.setParam(map);
msmVo.setPhone("2590416618@qq.com");
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
}
baseMapper.insert(cpuEntity);
});
}
@Cacheable(value = "cpu", keyGenerator = "keyGenerator")
@Override
public Map<String, Object> show() {
List<CpuVo> cpuVos = baseMapper.selectActuator();
Map<String, Object> map = new HashMap<>();
List<String> servers = cpuVos.stream().map(CpuVo::getServiceName).collect(Collectors.toList());
List<Double> dataList = cpuVos.stream().map(CpuVo::getCpuSize).collect(Collectors.toList());
map.put("servers", servers);
map.put("dataList", dataList);
return map;
}
}
@Service
public class ramServiceImpl extends ServiceImpl<ramMapper, RamEntity> implements ramService {
@Autowired
private List<ramActuatorCore> ramActuatorCores;
@Autowired
private RabbitService rabbitService;
@Override
public void save() {
ramActuatorCores.forEach(i -> {
RamEntity ramEntity = i.saveRam();
if (ramEntity.getRamSize() > 1000) {
MsmVo msmVo = new MsmVo();
HashMap<String, Object> map = new HashMap<>();
map.put("static", "actuator");
map.put("title", ramEntity.getServiceName() + "ram");
map.put("service", ramEntity.getServiceName() + "ram");
msmVo.setParam(map);
msmVo.setPhone("2590416618@qq.com");
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);
}
baseMapper.insert(ramEntity);
});
}
@Cacheable(value = "ram", keyGenerator = "keyGenerator")
@Override
public Map<String, Object> show() {
List<RamVo> cpuVos = baseMapper.selectActuator();
Map<String, Object> map = new HashMap<>();
List<String> servers = cpuVos.stream().map(RamVo::getServiceName).collect(Collectors.toList());
List<Integer> dataList = cpuVos.stream().map(RamVo::getRamSize).collect(Collectors.toList());
map.put("servers", servers);
map.put("dataList", dataList);
return map;
}
}
public interface cpuActuatorCore {
CpuEntity saveCpu();
}
@Service
@Slf4j
public class staCpuActuatorCore implements cpuActuatorCore {
@Autowired
private staFeignClient staFeignClient;
@Override
public CpuEntity saveCpu() {
String cpu = staFeignClient.getCpu();
log.info(cpu);
ActuatorVo jsonObject = JSON.parseObject(cpu, ActuatorVo.class);
BigDecimal bigDecimal = new BigDecimal(jsonObject.getMeasurements().get(0).getValue());
Double cpuValue = bigDecimal.doubleValue() * 100;
log.info(String.valueOf(cpuValue));
CpuEntity cpuEntity = new CpuEntity();
cpuEntity.setCpuSize(cpuValue);
cpuEntity.setTime(new Date());
cpuEntity.setServiceName("service_sta");
return cpuEntity;
}
}
其他服务都是大同小异
建立和msm模块通过rabbitmq实现异步通信的生产者
@Component
@Slf4j
public class StaReceiver {
@Autowired
private cpuService cpuService;
@Autowired
private ramService ramService;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = MqConst.QUEUE_TASK_6, durable = "true"),
exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
key = {MqConst.ROUTING_TASK_6}
))
public void startActuator(String str) {
log.info("收到信息" + str);
cpuService.save();
ramService.save();
}
}
msm模块
进行了一个判断,找出是监控中心要发送的邮件
@Override
@Async
public void send(MsmVo msmVo) {
log.info(msmVo.getPhone());
//判断邮箱是否为空
if (StringUtils.isEmpty(msmVo.getPhone())) {
return;
}
if (!ObjectUtils.isEmpty(msmVo.getParam().get("static")) && msmVo.getParam().get("static").equals("actuator")) {
sendActuator(msmVo);
}
//1.创建一个简单的的消息邮件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject(msmVo.getParam().get("title") + "预约成功");
simpleMailMessage.setText(msmVo.getParam().get("name") + "的预约成功请于" + msmVo.getParam().get("reserveDate") + "到医院就诊");
simpleMailMessage.setTo(msmVo.getPhone());
simpleMailMessage.setFrom("2590416618@qq.com");
javaMailSenderImpl.send(simpleMailMessage);
}
private void sendActuator(MsmVo msmVo) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject(msmVo.getParam().get("title") + "服务器报警");
simpleMailMessage.setText(msmVo.getParam().get("service") + "服务器报警请即使处理");
simpleMailMessage.setTo(msmVo.getPhone());
simpleMailMessage.setFrom("2590416618@qq.com");
javaMailSenderImpl.send(simpleMailMessage);
}
}
前端
{
path: '/actuator',
component: Layout,
redirect: '/actuator',
name: 'BasesInfo',
meta: { title: '监测中心', icon: 'table' },
alwaysShow: true,
children: [
{
path: 'cpu',
name: 'CPU监测',
component: () => import('@/views/statistics/actuator/cpu'),
meta: { title: 'CPU监测' }
},
{
path: 'ram',
name: '内存监测',
component: () => import('@/views/statistics/actuator/ram'),
meta: { title: '内存监测' }
}
]
},
getCpu() {
return request({
url: `${api_name}/getCpu`,
method: 'get'
})
},
getRam() {
return request({
url: `${api_name}/getRam`,
method: 'get'
})
}
<template>
<div className="app-container">
<div className="chart-container">
<div id="chart" ref="chart" className="chart" style="height:500px;width:100%"/>
</div>
</div>
</template>
<script>
import echarts from 'echarts'
import statisticsApi from '@/api/orderStatistics'
export default {
data() {
return {
btnDisabled: false,
chart: null,
title: '',
xData: [], // x轴数据
yData: [] // y轴数据
}
},
created() {
this.showChart()
},
methods: {
// 初始化图表数据
showChart() {
statisticsApi.getRam().then(response => {
this.yData = response.data.dataList
this.xData = response.data.servers
this.setChartData()
})
},
setChartData() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('chart'))
// 指定图表的配置项和数据
var option = {
title: {
text: 'CPU性能图'
},
tooltip: {},
legend: {
data: [this.title]
},
xAxis: {
data: this.xData
},
yAxis: {
minInterval: 1
},
series: [{
name: this.title,
type: 'line',
data: this.yData
}]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
}
}
</script>
<template>
<div className="app-container">
<div className="chart-container">
<div id="chart" ref="chart" className="chart" style="height:500px;width:100%"/>
</div>
</div>
</template>
<script>
import echarts from 'echarts'
import statisticsApi from '@/api/orderStatistics'
export default {
data() {
return {
btnDisabled: false,
chart: null,
title: '',
xData: [], // x轴数据
yData: [] // y轴数据
}
},
created() {
this.showChart()
},
methods: {
// 初始化图表数据
showChart() {
statisticsApi.getRam().then(response => {
this.yData = response.data.dataList
this.xData = response.data.servers
this.setChartData()
})
},
setChartData() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('chart'))
// 指定图表的配置项和数据
var option = {
title: {
text: 'CPU性能图'
},
tooltip: {},
legend: {
data: [this.title]
},
xAxis: {
data: this.xData
},
yAxis: {
minInterval: 1
},
series: [{
name: this.title,
type: 'line',
data: this.yData
}]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
}
}
</script>