服务器监控功能(3种方案)
@
目录
一、Actuator监控
Actuator是Springboot提供的用来对应用系统进行自省和监控的功能模块,借助于Actuator开发者可以很方便地对应用系统某些监控指标进行查看、统计等。
部署简单、直接调接口拿值、数据较分散,需处理
1. 添加依赖
<!-- 引入Actuator监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2. application.yaml配置
#actuator监控配置
management:
endpoints:
web:
exposure:
#默认值访问health,info端点 用*可以包含全部端点
include: '*'
#配置路径
base-path: /system/actuator
endpoint:
health:
#获得健康检查中所有指标的详细信息
show-details: always
3. 启动项目,访问
以下显示的路径,都是可以被健康检查的指标
其中谷歌浏览器显示格式为 JSON,是因为下载了 JSON-handle 插件
-
访问路径:
项目路径
+actuator配置路径
: -
Actuator健康项:
二、SpringBoot Admin(单体)
可监控的信息包含:应用状态、内存、线程、堆栈等等,比较全面的监控了 Spring Boot 应用的整个生命周期。
在Spring Boot Actuator的基础上提供简洁的可视化WEB UI
SpringBoot Admin 分为服务端(spring-boot-admin-server)和客户端(spring-boot-admin-client),客户端就是指需要被监控的应用端(项目启动位置)
1. Admin Server 端
-
新建一个项目模块,项目依赖
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
application.yaml 配置文件
# 配置tomcat访问端口 server: port: 8000
-
启动类
import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Configuration; /** * Springboot admin 服务器监控 -- 服务端 * * @Author: changge * @date 2021/8/25 17:43 * @return null **/ @Configuration @EnableAutoConfiguration @EnableAdminServer public class AdminServerApplication { public static void main(String[] args) { SpringApplication.run(AdminServerApplication.class, args); } }
-
访问浏览器
http://localhost:8000
2. Admin Client 端
-
在原本项目基础上,添加依赖
<dependencies> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
配置文件
# 配置访问端口 server: port: 20211 spring: application: name: Admin Client # 配置AdminServer的地址 boot: admin: client: url: http://localhost:8000 # 打开客户端的监控 management: endpoints: web: exposure: include: *
-
客户端启动类
@SpringBootApplication public class AdminClientApplication { public static void main(String[] args) { SpringApplication.run(AdminClientApplication.class, args); } }
-
先启动服务端,再启动客户端
启动 Client 端,Admin 服务端会自动检查到客户端的变化,并展示其应用
页面会展示被监控的服务列表,点击详项目名称会进入此应用的详细监控信息
三、OSHI
基于JNA的免费的本地操作系统和Java的硬件信息库,可以跨平台,获取监控信息简单
1. pom.xml依赖
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>5.6.0</version>
</dependency>
2. 接口返回数据
3. 代码
-
Controller
import com.lzby.tqj.dto.AjaxResult; import com.lzby.tqj.entity.Server; import io.swagger.*; import org.springframework.web.bind.annotation.*; /** * 服务器监控 **/ @CrossOrigin(origins = "*", maxAge = 3600) @Api(tags = "系统-服务器监控") @RestController @RequestMapping("/monitor/server") public class ServerController { @ApiOperation(value = "获得服务器相关信息", notes = "获得服务器相关信息") @PostMapping(value = "/getInfo") public AjaxResult getInfo() throws Exception { Server server = new Server(); server.copyTo(); return AjaxResult.success(server); } }
-
entity - Server
package com.lzby.tqj.entity; import com.lzby.tqj.common.IpUtils; import com.lzby.tqj.common.util.Arith; import com.lzby.tqj.entity.server.*; import oshi.*; import java.net.InetAddress; import java.util.*; /** * 服务器相关信息 **/ public class Server { private static final int OSHI_WAIT_SECOND = 1000; // CPU相关信息 private Cpu cpu = new Cpu(); // 內存相关信息 private Mem mem = new Mem(); // JVM相关信息 private Jvm jvm = new Jvm(); // 服务器相关信息 private Sys sys = new Sys(); // 磁盘相关信息 private List<SysFile> sysFiles = new LinkedList<SysFile>(); public Cpu getCpu() { return cpu; } public void setCpu(Cpu cpu) { this.cpu = cpu; } public Mem getMem() { return mem; } public void setMem(Mem mem) { this.mem = mem; } public Jvm getJvm() { return jvm; } public void setJvm(Jvm jvm) { this.jvm = jvm; } public Sys getSys() { return sys; } public void setSys(Sys sys) { this.sys = sys; } public List<SysFile> getSysFiles() { return sysFiles; } public void setSysFiles(List<SysFile> sysFiles) { this.sysFiles = sysFiles; } public void copyTo() throws Exception { SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); setCpuInfo(hal.getProcessor()); setMemInfo(hal.getMemory()); setSysInfo(); setJvmInfo(); setSysFiles(si.getOperatingSystem()); } /** * 设置CPU信息 */ private void setCpuInfo(CentralProcessor processor) { // CPU信息 long[] prevTicks = processor.getSystemCpuLoadTicks(); Util.sleep(OSHI_WAIT_SECOND); long[] ticks = processor.getSystemCpuLoadTicks(); long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; cpu.setCpuNum(processor.getLogicalProcessorCount()); cpu.setTotal(totalCpu); cpu.setSys(cSys); cpu.setUsed(user); cpu.setWait(iowait); cpu.setFree(idle); } /** * 设置内存信息 */ private void setMemInfo(GlobalMemory memory) { mem.setTotal(memory.getTotal()); mem.setUsed(memory.getTotal() - memory.getAvailable()); mem.setFree(memory.getAvailable()); } /** * 设置服务器信息 */ private void setSysInfo() { Properties props = System.getProperties(); sys.setComputerName(getHostName()); sys.setComputerIp(getHostIp()); sys.setOsName(props.getProperty("os.name")); sys.setOsArch(props.getProperty("os.arch")); sys.setUserDir(props.getProperty("user.dir")); } /** * 设置Java虚拟机 */ private void setJvmInfo() throws UnknownHostException { Properties props = System.getProperties(); jvm.setTotal(Runtime.getRuntime().totalMemory()); jvm.setMax(Runtime.getRuntime().maxMemory()); jvm.setFree(Runtime.getRuntime().freeMemory()); jvm.setVersion(props.getProperty("java.version")); jvm.setHome(props.getProperty("java.home")); } /** * 设置磁盘信息 */ private void setSysFiles(OperatingSystem os) { FileSystem fileSystem = os.getFileSystem(); List<OSFileStore> fsArray = fileSystem.getFileStores(); for (OSFileStore fs : fsArray) { long free = fs.getUsableSpace(); long total = fs.getTotalSpace(); long used = total - free; SysFile sysFile = new SysFile(); sysFile.setDirName(fs.getMount()); sysFile.setSysTypeName(fs.getType()); sysFile.setTypeName(fs.getName()); sysFile.setTotal(convertFileSize(total)); sysFile.setFree(convertFileSize(free)); sysFile.setUsed(convertFileSize(used)); sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); sysFiles.add(sysFile); } } /** * 字节转换 * * @param size 字节大小 * @return 转换后值 */ public String convertFileSize(long size) { long kb = 1024; long mb = kb * 1024; long gb = mb * 1024; if (size >= gb) { return String.format("%.1f GB", (float) size / gb); } else if (size >= mb) { float f = (float) size / mb; return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); } else if (size >= kb) { float f = (float) size / kb; return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); } else { return String.format("%d B", size); } } public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } return "127.0.0.1"; } public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { } return "未知"; } }
-
entity - Cpu
package com.lzby.tqj.entity.server; import com.lzby.tqj.common.util.Arith; /** * CPU相关信息 */ public class Cpu { // 核心数 private int cpuNum; // CPU总的使用率 private double total; // CPU系统使用率 private double sys; // CPU用户使用率 private double used; // CPU当前等待率 private double wait; // CPU当前空闲率 private double free; public int getCpuNum(){ return cpuNum; } public void setCpuNum(int cpuNum){ this.cpuNum = cpuNum; } public double getTotal(){ return Arith.round(Arith.mul(total, 100), 2); } public void setTotal(double total){ this.total = total; } public double getSys(){ return Arith.round(Arith.mul(sys / total, 100), 2); } public void setSys(double sys){ this.sys = sys; } public double getUsed(){ return Arith.round(Arith.mul(used / total, 100), 2); } public void setUsed(double used){ this.used = used; } public double getWait(){ return Arith.round(Arith.mul(wait / total, 100), 2); } public void setWait(double wait){ this.wait = wait; } public double getFree(){ return Arith.round(Arith.mul(free / total, 100), 2); } public void setFree(double free){ this.free = free; } }
-
entity - Mem
package com.lzby.tqj.entity.server; import com.lzby.tqj.common.util.Arith; /** * 內存相关信息 */ public class Mem{ // 内存总量 private double total; // 已用内存 private double used; // 剩余内存 private double free; public double getTotal(){ return Arith.div(total, (1024 * 1024 * 1024), 2); } public void setTotal(long total){ this.total = total; } public double getUsed(){ return Arith.div(used, (1024 * 1024 * 1024), 2); } public void setUsed(long used){ this.used = used; } public double getFree(){ return Arith.div(free, (1024 * 1024 * 1024), 2); } public void setFree(long free){ this.free = free; } public double getUsage(){ return Arith.mul(Arith.div(used, total, 4), 100); } }
-
entity - Jvm
package com.lzby.tqj.entity.server; import java.lang.management.ManagementFactory; import com.lzby.tqj.common.util.Arith; import com.lzby.tqj.common.util.DateUtils; /** * JVM相关信息 **/ public class Jvm{ // 当前JVM占用的内存总数(M) private double total; // JVM最大可用内存总数(M) private double max; // JVM空闲内存(M) private double free; // JDK版本 private String version; // JDK路径 private String home; public double getTotal(){ return Arith.div(total, (1024 * 1024), 2); } public void setTotal(double total){ this.total = total; } public double getMax(){ return Arith.div(max, (1024 * 1024), 2); } public void setMax(double max){ this.max = max; } public double getFree(){ return Arith.div(free, (1024 * 1024), 2); } public void setFree(double free){ this.free = free; } public double getUsed(){ return Arith.div(total - free, (1024 * 1024), 2); } public double getUsage(){ return Arith.mul(Arith.div(total - free, total, 4), 100); } // 获取JDK名称 public String getName(){ return ManagementFactory.getRuntimeMXBean().getVmName(); } public String getVersion(){ return version; } public void setVersion(String version){ this.version = version; } public String getHome(){ return home; } public void setHome(String home){ this.home = home; } // JDK启动时间 public String getStartTime(){ return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); } // JDK运行时间 public String getRunTime() { return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate()); } }
-
entity - Jvm
package com.lzby.tqj.entity.server; import lombok.Data; /** * 系统相关信息 */ @Data public class Sys{ // 服务器名称 private String computerName; // 服务器Ip private String computerIp; // 项目路径 private String userDir; // 操作系统 private String osName; // 系统架构 private String osArch; }
-
工具类 Arith
package com.lzby.tqj.common.util; import java.math.BigDecimal; import java.math.RoundingMode; /** * 精确的浮点数运算 **/ public class Arith{ /** 默认除法运算精度 */ private static final int DEF_DIV_SCALE = 10; /** 这个类不能实例化 */ private Arith(){} /** * 提供精确的加法运算。 * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public static double add(double v1, double v2){ BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2).doubleValue(); } /** * 提供精确的乘法运算。 * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */ public static double mul(double v1, double v2){ BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2).doubleValue(); } /** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */ public static double div(double v1, double v2, int scale){ if (scale < 0){ throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); if (b1.compareTo(BigDecimal.ZERO) == 0){ return BigDecimal.ZERO.doubleValue(); } return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); } /** * 提供精确的小数位四舍五入处理。 * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */ public static double round(double v, int scale){ if (scale < 0){ throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(Double.toString(v)); BigDecimal one = new BigDecimal("1"); return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); } }