从 0 到 1 实战:
本文为 Java 后端入门级实战项目,全程包含完整可运行代码、环境配置、踩坑记录、容器化部署全流程,跟着步骤操作即可 100% 跑通项目。项目覆盖 Spring Boot 核心开发、Redis 轻量级消息队列、3σ 异常检测算法、WebSocket 实时推送、Docker 容器化部署等核心技能,,也适合 Java 新手巩固后端技术栈。
【本文目录】
- 项目介绍与技术栈选型
- 前置环境准备
- 项目整体架构设计
- 核心代码全量开发(含逐行注释)
- 本地开发调试与踩坑全记录
- Docker 容器化一键部署全流程
- 项目进阶优化方向
- 简历优化与面试高频问题
- 项目总结
1. 项目介绍与技术栈选型
1.1 项目背景与功能
在后端开发、运维监控场景中,实时监控系统是最常见的实战项目,既能覆盖主流后端技术栈,又能解决实际业务问题。本项目从零开发一套轻量级服务器实时监控告警系统,核心功能如下:
- 实时数据采集:每秒生成模拟的服务器 CPU、内存、磁盘使用率监控数据
- 消息队列解耦:基于 Redis 实现轻量级消息队列,解耦数据生产与消费,实现削峰填谷
- 智能异常检测:基于统计学 3σ 正态分布算法,自动识别监控数据中的异常波动
- 实时前端推送:基于 WebSocket 实现服务端到前端的毫秒级数据推送,页面无刷新实时更新
- 异常可视化告警:前端页面对异常数据标红高亮,直观展示监控状态
- 容器化部署:基于 Docker+Docker Compose 实现项目一键打包部署,适配服务器环境
1.2 核心技术栈选型
本项目全部采用当前行业主流稳定版本:
表格
| 技术 / 工具 | 版本 / 选型 | 作用 |
|---|---|---|
| 开发框架 | Spring Boot 3.2.x | 后端核心开发框架,自动配置、简化开发 |
| 缓存 / 消息队列 | Redis 7.x | 实现轻量级消息队列、数据缓存 |
| 连接池 | Lettuce | Spring Data Redis 默认连接池,高性能异步非阻塞 |
| 实时推送 | WebSocket + STOMP 协议 | 实现服务端到前端的双向实时通信 |
| 前端 | HTML5 + 原生 JS + SockJS | 轻量化前端页面,无需复杂框架,新手易上手 |
| 代码简化 | Lombok | 自动生成 get/set/ 构造器,减少冗余代码 |
| 部署工具 | Docker + Docker Compose | 实现项目容器化打包、一键部署、环境隔离 |
| 开发环境 | IDEA 2023+、JDK 17、Maven 3.8+ | 本地开发环境 |
| 服务器环境 | AlmaLinux 9(CentOS 替代版) | 部署服务器环境,兼容 RHEL 生态 |
1.3 项目最终效果
- 项目启动后,控制台每秒输出「数据生成→队列写入→数据消费→异常检测」全流程日志
- 浏览器访问服务地址,即可看到实时刷新的监控大屏,异常数据自动标红告警
- 基于 Docker 实现一键部署,服务器上一条命令即可启动全套服务,无需复杂环境配置
2. 前置环境准备
2.1 本地开发环境配置
- JDK 17 安装 :Spring Boot 3.x 要求最低 JDK 17,Oracle 官网或 OpenJDK 下载安装,配置
JAVA_HOME环境变量,cmd 执行java -version验证安装成功。 - IDEA 安装:下载 IntelliJ IDEA 社区版 / 旗舰版,安装后配置 Maven、JDK 环境。
- Maven 配置:下载 Maven 3.8+,配置阿里云镜像源,避免依赖下载缓慢。
- FinalShell 安装:用于连接 Linux 虚拟机,传输文件、执行服务器命令。
- 虚拟机安装:VMware 安装 AlmaLinux 9 虚拟机,配置网络为桥接模式,确保 Windows 能 ping 通虚拟机 IP。
2.2 服务器 Docker 环境配置
虚拟机中执行以下命令,安装 Docker 与 Docker Compose:
bash
运行
# 1. 卸载旧版本Docker
yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
# 2. 安装yum工具
yum install -y yum-utils
# 3. 配置阿里云Docker镜像源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 4. 安装Docker CE
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 5. 启动Docker并设置开机自启
systemctl start docker
systemctl enable docker
# 6. 验证安装成功
docker --version
docker compose version
2.3 Redis 容器启动
虚拟机中执行以下命令,启动 Redis 容器,用于后续开发与部署:
bash
运行
# 拉取Redis最新镜像
docker pull redis:latest
# 启动Redis容器,设置密码、关闭保护模式、允许外部访问
docker run -d --name if-redis -p 6379:6379 redis:latest redis-server --requirepass 123456aq@ --protected-mode no --bind 0.0.0.0
# 验证容器启动成功
docker ps | grep if-redis
3. 项目整体架构设计
本项目采用分层架构设计,各模块职责单一、解耦性强,符合后端开发规范,整体架构分为 5 层,数据流转全链路如下:
plaintext
数据采集层(定时任务生成监控数据)
↓
消息队列层(Redis List实现轻量级队列,解耦生产消费)
↓
异常检测层(消费者消费数据,3σ算法检测异常)
↓
实时推送层(WebSocket将结果实时推送到前端)
↓
前端展示层(页面实时渲染数据,异常标红告警)
各层核心职责说明:
- 数据采集层:基于 Spring Schedule 定时任务,每秒生成模拟的服务器监控数据,序列化为 JSON 后写入 Redis 消息队列。
- 消息队列层:基于 Redis List 数据结构实现轻量级消息队列,采用「左进右出」模式,通过阻塞读实现消息实时消费,解耦生产与消费逻辑,避免生产速度与消费速度不匹配导致的数据丢失。
- 异常检测层:项目启动后自动开启异步消费线程,阻塞读取队列中的监控数据,基于 3σ 正态分布算法判断数据是否异常,维护历史数据滑动窗口,保证检测准确性。
- 实时推送层:基于 WebSocket+STOMP 协议实现发布 - 订阅模式,异常检测完成后,将结果推送到指定主题,前端订阅该主题即可实时接收数据,无需轮询请求。
- 前端展示层:轻量化 HTML 页面,通过 SockJS+STOMP 客户端连接 WebSocket,实时接收服务端推送的数据,渲染到页面中,异常数据自动标红高亮。
4. 核心代码全量开发(含逐行注释)
4.1 项目初始化与 POM 依赖
打开 IDEA,通过 Spring Initializr 创建项目,项目名称insightflow-lite,包名com.example.insightflow_lite,选择对应依赖,最终pom.xml完整内容如下:
xml
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>insightflow-lite</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>insightflow-lite</name>
<description>轻量级实时监控告警系统</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Web核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- WebSocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Lombok代码简化 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jackson JSON序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 项目启动类
Spring Boot 项目入口,无需额外配置,自动扫描同包下的所有组件:
java
运行
package com.example.insightflow_lite;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class InsightFlowLiteApplication {
public static void main(String[] args) {
SpringApplication.run(InsightFlowLiteApplication.class, args);
}
}
4.3 核心实体类开发
4.3.1 监控数据实体类 MonitorData.java
用于封装服务器监控数据,路径src/main/java/com/example/insightflow_lite/model/MonitorData.java:
java
运行
package com.example.insightflow_lite.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 服务器监控数据实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MonitorData {
/**
* 服务器ID
*/
private String serverId = "server-01";
/**
* 数据采集时间
*/
private String collectTime;
/**
* CPU使用率(百分比)
*/
private double cpuUsage;
/**
* 内存使用率(百分比)
*/
private double memUsage;
/**
* 磁盘使用率(百分比)
*/
private double diskUsage;
/**
* 构造器:自动生成采集时间
*/
public MonitorData(double cpuUsage, double memUsage, double diskUsage) {
this.cpuUsage = Math.round(cpuUsage * 100) / 100.0;
this.memUsage = Math.round(memUsage * 100) / 100.0;
this.diskUsage = Math.round(diskUsage * 100) / 100.0;
// 格式化采集时间
this.collectTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}
4.3.2 异常检测结果实体类
在异常检测服务中定义内部类,用于封装异常检测结果,后续详细代码见 4.6 节。
4.4 数据采集生成器开发
基于 Spring Schedule 定时任务实现,每秒生成监控数据并写入 Redis 队列,路径src/main/java/com/example/insightflow_lite/task/MonitorDataGenerator.java:
java
运行
package com.example.insightflow_lite.task;
import com.example.insightflow_lite.model.MonitorData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* 监控数据生成器:定时生成模拟监控数据,写入Redis消息队列
*/
@Slf4j
@Component
@EnableScheduling // 开启定时任务
public class MonitorDataGenerator {
// Redis消息队列的Key,必须和消费者保持一致
private static final String QUEUE_KEY = "monitor:queue:server-data";
// 随机数生成器,模拟监控数据波动
private final Random random = new Random();
// JSON序列化工具
private final ObjectMapper objectMapper = new ObjectMapper();
// Redis操作模板,构造器注入(消除字段注入警告)
private final StringRedisTemplate redisTemplate;
/**
* 构造器注入:Spring推荐的注入方式,避免字段注入的不规范问题
*/
public MonitorDataGenerator(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 定时任务:fixedRate=1000 表示每秒执行一次
* 生成模拟监控数据,序列化为JSON后写入Redis List队列
*/
@Scheduled(fixedRate = 1000)
public void generateMonitorData() {
try {
// 生成模拟监控数据:模拟正常波动范围
// CPU:20%-80%随机波动
double cpu = 20 + random.nextDouble() * 60;
// 内存:60%-90%随机波动
double mem = 60 + random.nextDouble() * 30;
// 磁盘:70%-95%随机波动
double disk = 70 + random.nextDouble() * 25;
// 封装为监控数据实体
MonitorData data = new MonitorData(cpu, mem, disk);
// 序列化为JSON字符串
String dataJson = objectMapper.writeValueAsString(data);
// 写入Redis List队列:leftPush从左侧入队,消费者从右侧出队,实现先进先出
redisTemplate.opsForList().leftPush(QUEUE_KEY, dataJson);
log.info("生成监控数据并写入队列:{}", dataJson);
} catch (JsonProcessingException e) {
log.error("监控数据JSON序列化失败", e);
} catch (Exception e) {
log.error("生成监控数据失败", e);
}
}
}
4.5 消息消费者开发
项目启动后自动开启异步线程,阻塞读取 Redis 队列中的数据,交给异常检测服务处理,路径src/main/java/com/example/insightflow_lite/consumer/MonitorDataConsumer.java:
java
运行
package com.example.insightflow_lite.consumer;
import com.example.insightflow_lite.model.MonitorData;
import com.example.insightflow_lite.service.AnomalyDetectionService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 监控数据消费者:阻塞读取Redis队列中的数据,进行异常检测处理
*/
@Slf4j
@Component
public class MonitorDataConsumer {
// 队列Key,必须和生成器保持一致
private static final String QUEUE_KEY = "monitor:queue:server-data";
// 单线程池:异步消费,不阻塞Spring主线程启动
private final ExecutorService executor = Executors.newSingleThreadExecutor();
// JSON反序列化工具
private final ObjectMapper objectMapper = new ObjectMapper();
// 构造器注入
private final StringRedisTemplate redisTemplate;
private final AnomalyDetectionService anomalyDetectionService;
/**
* 构造器注入
*/
public MonitorDataConsumer(StringRedisTemplate redisTemplate, AnomalyDetectionService anomalyDetectionService) {
this.redisTemplate = redisTemplate;
this.anomalyDetectionService = anomalyDetectionService;
}
/**
* @PostConstruct 注解:Spring Bean初始化完成后自动执行该方法
* 作用:项目启动后自动开启消费线程
*/
@PostConstruct
public void startConsume() {
executor.submit(this::consumeLoop);
log.info("监控数据消费者已启动,开始监听队列:{}", QUEUE_KEY);
}
/**
* 核心消费循环:阻塞式读取队列新消息
* 采用Redis BRPOP命令,阻塞5秒等待新消息,无数据则返回null,避免CPU空轮询
*/
private void consumeLoop() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 阻塞读取队列:从队列右侧弹出数据,超时时间5秒
// 有新数据立即返回,无数据则阻塞等待,超时返回null
String dataJson = redisTemplate.opsForList()
.rightPop(QUEUE_KEY, 5, TimeUnit.SECONDS);
// 只有读取到数据才进行处理
if (dataJson != null) {
// 反序列化为监控数据实体
MonitorData monitorData = objectMapper.readValue(dataJson, MonitorData.class);
// 交给异常检测服务处理
anomalyDetectionService.processMonitorData(monitorData);
log.info("成功消费监控数据:{}", monitorData);
}
} catch (JsonProcessingException e) {
log.error("监控数据JSON解析失败", e);
} catch (Exception e) {
log.error("消费消息异常", e);
}
}
}
}
4.6 异常检测服务开发
基于 3σ 正态分布算法实现异常检测,核心原理:在正态分布中,99.73% 的数据会落在均值 ±3 倍标准差的范围内,超出该范围的数据可判定为异常波动,非常适合监控数据的异常识别。
路径src/main/java/com/example/insightflow_lite/service/AnomalyDetectionService.java:
java
运行
package com.example.insightflow_lite.service;
import com.example.insightflow_lite.model.MonitorData;
import com.example.insightflow_lite.websocket.WebSocketPushService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 异常检测服务:基于3σ正态分布算法实现监控数据异常检测
*/
@Slf4j
@Service
public class AnomalyDetectionService {
/**
* 滑动窗口大小:保留最近50条历史数据,用于计算均值和标准差
*/
private static final int WINDOW_SIZE = 50;
/**
* 历史数据存储:key为指标类型,value为历史数据列表
*/
private final Map<String, List<Double>> historyData = new HashMap<>();
/**
* WebSocket推送服务
*/
private final WebSocketPushService webSocketPushService;
/**
* 构造器注入
*/
public AnomalyDetectionService(WebSocketPushService webSocketPushService) {
this.webSocketPushService = webSocketPushService;
// 初始化历史数据容器
historyData.put("cpu", new ArrayList<>());
historyData.put("mem", new ArrayList<>());
historyData.put("disk", new ArrayList<>());
}
/**
* 核心处理方法:处理监控数据,执行异常检测,推送结果到前端
*/
public void processMonitorData(MonitorData data) {
// 1. 收集历史数据,维护滑动窗口
collectHistoryData("cpu", data.getCpuUsage());
collectHistoryData("mem", data.getMemUsage());
collectHistoryData("disk", data.getDiskUsage());
// 2. 对每个指标执行异常检测
AnomalyResult cpuResult = detectAnomaly("cpu", data.getCpuUsage());
AnomalyResult memResult = detectAnomaly("mem", data.getMemUsage());
AnomalyResult diskResult = detectAnomaly("disk", data.getDiskUsage());
// 3. 封装检测结果
MonitorAnomaly anomaly = new MonitorAnomaly(data, cpuResult, memResult, diskResult);
// 4. 通过WebSocket推送到前端
webSocketPushService.pushMonitorData(anomaly);
// 5. 异常数据打印错误日志
if (anomaly.isAnyAnomaly()) {
log.error("检测到监控数据异常:{}", anomaly);
}
}
/**
* 收集历史数据,维护滑动窗口:超过窗口大小则删除最旧的数据
*/
private void collectHistoryData(String key, double value) {
List<Double> list = historyData.get(key);
list.add(value);
// 滑动窗口:超过最大长度,删除最旧的数据
if (list.size() > WINDOW_SIZE) {
list.remove(0);
}
}
/**
* 3σ异常检测核心算法
* @param key 指标类型
* @param currentValue 当前检测值
* @return 异常检测结果
*/
private AnomalyResult detectAnomaly(String key, double currentValue) {
List<Double> dataList = historyData.get(key);
// 历史数据不足10条时,不进行异常检测,避免冷启动误差
if (dataList.size() < 10) {
return new AnomalyResult(false, 0.0, 0.0);
}
// 计算历史数据的均值
double avg = dataList.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
// 计算历史数据的标准差
double std = calculateStandardDeviation(dataList, avg);
// 计算3σ上下限
double lowerBound = avg - 3 * std;
double upperBound = avg + 3 * std;
// 超出上下限则判定为异常
boolean isAnomaly = currentValue < lowerBound || currentValue > upperBound;
return new AnomalyResult(isAnomaly, avg, std);
}
/**
* 计算标准差
*/
private double calculateStandardDeviation(List<Double> dataList, double avg) {
double sum = 0.0;
for (double value : dataList) {
sum += Math.pow(value - avg, 2);
}
return Math.sqrt(sum / dataList.size());
}
/**
* 异常检测结果实体类
*/
@Data
@AllArgsConstructor
public static class AnomalyResult {
/**
* 是否异常
*/
private boolean isAnomaly;
/**
* 历史数据均值
*/
private double avg;
/**
* 历史数据标准差
*/
private double std;
}
/**
* 监控异常结果封装类
*/
@Data
@AllArgsConstructor
public static class MonitorAnomaly {
/**
* 原始监控数据
*/
private MonitorData rawData;
/**
* CPU异常检测结果
*/
private AnomalyResult cpuResult;
/**
* 内存异常检测结果
*/
private AnomalyResult memResult;
/**
* 磁盘异常检测结果
*/
private AnomalyResult diskResult;
/**
* 判断是否有任意指标异常
*/
public boolean isAnyAnomaly() {
return cpuResult.isAnomaly() || memResult.isAnomaly() || diskResult.isAnomaly();
}
}
}
4.7 WebSocket 配置与推送服务开发
4.7.1 WebSocket 配置类
注册 STOMP 端点,配置消息代理,路径src/main/java/com/example/insightflow_lite/websocket/WebSocketConfig.java:
java
运行
package com.example.insightflow_lite.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket配置类:启用STOMP消息代理
*/
@Configuration
@EnableWebSocketMessageBroker // 启用WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 配置消息代理
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启用简单内存消息代理,推送消息的前缀为/topic
config.enableSimpleBroker("/topic");
// 客户端发送消息的前缀,这里我们只做服务端推送,无需配置
config.setApplicationDestinationPrefixes("/app");
}
/**
* 注册STOMP端点,客户端通过该端点连接WebSocket
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册端点/ws/monitor,允许SockJS降级(浏览器不支持WebSocket时用HTTP模拟)
registry.addEndpoint("/ws/monitor").withSockJS();
}
}
4.7.2 WebSocket 推送服务
将异常检测结果推送到前端订阅的主题,路径src/main/java/com/example/insightflow_lite/websocket/WebSocketPushService.java:
java
运行
package com.example.insightflow_lite.websocket;
import com.example.insightflow_lite.service.AnomalyDetectionService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
/**
* WebSocket推送服务:向客户端推送监控数据
*/
@Slf4j
@Service
public class WebSocketPushService {
/**
* 前端订阅的主题地址
*/
private static final String DESTINATION = "/topic/monitor/data";
/**
* Spring提供的WebSocket消息发送模板
*/
private final SimpMessagingTemplate messagingTemplate;
/**
* JSON序列化工具
*/
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 构造器注入
*/
public WebSocketPushService(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
/**
* 推送监控异常数据到前端
*/
public void pushMonitorData(AnomalyDetectionService.MonitorAnomaly anomaly) {
try {
// 序列化为JSON字符串,推送到指定主题
String json = objectMapper.writeValueAsString(anomaly);
messagingTemplate.convertAndSend(DESTINATION, json);
log.debug("WebSocket推送数据成功:{}", json);
} catch (JsonProcessingException e) {
log.error("WebSocket推送数据序列化失败", e);
} catch (Exception e) {
log.error("WebSocket推送数据失败", e);
}
}
}
4.8 控制器开发
提供健康检查接口、根路径重定向,路径src/main/java/com/example/insightflow_lite/controller/HealthController.java:
java
运行
package com.example.insightflow_lite.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 健康检查与页面跳转控制器
*/
@Controller
public class HealthController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 根路径:重定向到监控大屏页面
*/
@GetMapping("/")
public String index() {
return "redirect:/index.html";
}
/**
* 健康检查接口:用于验证项目是否启动成功
*/
@GetMapping("/health")
@ResponseBody
public String health() {
return "✅ InsightFlow_lite 项目启动成功!当前运行环境: "
+ System.getProperty("spring.profiles.active", "local");
}
/**
* Redis连通性测试接口:验证Redis连接是否正常
*/
@GetMapping("/test/redis")
@ResponseBody
public String testRedis() {
try {
redisTemplate.opsForValue().set("project:test:key", "InsightFlow连接成功");
String value = redisTemplate.opsForValue().get("project:test:key");
return "✅ Redis 连接成功!写入并读取到数据: " + value;
} catch (Exception e) {
return "❌ Redis 连接失败!错误信息: " + e.getMessage();
}
}
}
4.9 前端监控大屏开发
路径src/main/resources/static/index.html,实现实时数据渲染、异常标红告警:
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>InsightFlow 实时监控大屏</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
body {
background-color: #f5f7fa;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
.monitor-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 30px;
max-width: 800px;
margin: 0 auto;
}
.server-title {
font-size: 22px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 20px;
border-bottom: 2px solid #e4e7ed;
padding-bottom: 10px;
}
.data-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
font-size: 18px;
}
.data-label {
color: #606266;
font-weight: 500;
}
.data-value {
color: #303133;
font-weight: bold;
}
.anomaly {
color: #f56c6c;
font-weight: bold;
}
.normal {
color: #67c23a;
}
</style>
<!-- 引入SockJS和STOMP客户端,用于连接WebSocket -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
<div class="header">
<h1>InsightFlow 实时监控大屏</h1>
</div>
<div class="monitor-card">
<div class="server-title">服务器:server-01</div>
<div class="data-item">
<span class="data-label">采集时间</span>
<span class="data-value" id="collectTime">--</span>
</div>
<div class="data-item">
<span class="data-label">CPU使用率</span>
<div>
<span class="data-value" id="cpuUsage">--</span>%
<span id="cpuAnomaly" class="normal">(正常)</span>
</div>
</div>
<div class="data-item">
<span class="data-label">内存使用率</span>
<div>
<span class="data-value" id="memUsage">--</span>%
<span id="memAnomaly" class="normal">(正常)</span>
</div>
</div>
<div class="data-item">
<span class="data-label">磁盘使用率</span>
<div>
<span class="data-value" id="diskUsage">--</span>%
<span id="diskAnomaly" class="normal">(正常)</span>
</div>
</div>
</div>
<script>
// 连接WebSocket端点
const socket = new SockJS('/ws/monitor');
// 创建STOMP客户端
const stompClient = Stomp.over(socket);
// 连接成功后的回调
stompClient.connect({}, function (frame) {
console.log('WebSocket连接成功:' + frame);
// 订阅监控数据主题
stompClient.subscribe('/topic/monitor/data', function (message) {
// 解析服务端推送的JSON数据
const data = JSON.parse(message.body);
console.log('收到监控数据:', data);
// 渲染数据到页面
document.getElementById('collectTime').textContent = data.rawData.collectTime;
document.getElementById('cpuUsage').textContent = data.rawData.cpuUsage;
document.getElementById('memUsage').textContent = data.rawData.memUsage;
document.getElementById('diskUsage').textContent = data.rawData.diskUsage;
// 更新异常状态
updateAnomalyStatus('cpuAnomaly', data.cpuResult.isAnomaly);
updateAnomalyStatus('memAnomaly', data.memResult.isAnomaly);
updateAnomalyStatus('diskAnomaly', data.diskResult.isAnomaly);
});
});
/**
* 更新异常状态显示
* @param {string} elementId 元素ID
* @param {boolean} isAnomaly 是否异常
*/
function updateAnomalyStatus(elementId, isAnomaly) {
const element = document.getElementById(elementId);
if (isAnomaly) {
element.textContent = '(异常!)';
element.className = 'anomaly';
} else {
element.textContent = '(正常)';
element.className = 'normal';
}
}
// 页面关闭时断开WebSocket连接
window.onbeforeunload = function() {
stompClient.disconnect();
};
</script>
</body>
</html>
4.10 多环境配置文件开发
Spring Boot 多环境配置,分为本地开发环境和 Docker 部署环境,避免硬编码配置,实现环境隔离。
4.10.1 主配置文件 application.yml
路径src/main/resources/application.yml,仅负责环境激活,不写具体业务配置:
yaml
# 项目全局主配置:仅负责激活环境,不写具体业务配置
spring:
# 环境激活开关:local(本地开发) / docker(虚拟机部署)
profiles:
active: local
# 项目名称
application:
name: InsightFlowLite
# 日志配置:简化启动日志,方便排查问题
logging:
level:
# 只打印项目包下的日志,屏蔽无关依赖日志
com.example.insightflow_lite: INFO
# Redis连接日志(调试时开启,上线后关闭)
org.springframework.data.redis: WARN
4.10.2 本地开发环境配置 application-local.yml
路径src/main/resources/application-local.yml,适配 Windows 本地开发,连接虚拟机 Redis:
yaml
# 本地开发环境:连接虚拟机的Redis,使用内置Tomcat 8080端口
server:
port: 8080
# 关闭Tomcat访问日志(本地开发无需记录)
tomcat:
accesslog:
enabled: false
# Redis配置:对接虚拟机中运行的Redis容器
spring:
data:
redis:
host: 192.168.242.128 # 替换为你的虚拟机真实IP
port: 6379
# 匹配虚拟机Redis启动时设置的密码
password: 123456aq@
# 连接超时时间(防止网络波动导致连接失败)
timeout: 5000ms
# Redis连接池配置(性能优化)
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 2 # 最小空闲连接
4.10.3 Docker 部署环境配置 application-docker.yml
路径src/main/resources/application-docker.yml,适配容器化部署,容器间通过服务名访问 Redis:
yaml
# Docker部署环境:通过容器名连接Redis
server:
port: 8080
address: 0.0.0.0 # 容器内必须监听所有IP,否则外部无法访问
# 开启Tomcat访问日志(部署后便于排查访问问题)
tomcat:
accesslog:
enabled: true
directory: /app/logs
prefix: access_log
suffix: .log
# Redis配置:对接Docker网络中的redis容器
spring:
data:
redis:
host: if-redis # Docker中Redis容器名,容器间通过名称互通
port: 6379
password: 123456aq@
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
# 日志配置:容器内日志输出到控制台,便于docker logs查看
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
5. 本地开发调试与踩坑全记录
5.1 本地调试步骤
- 虚拟机启动 Redis 容器 :确保虚拟机中
if-redis容器处于运行状态,防火墙放行 6379 端口。 - 配置本地环境 :修改
application-local.yml中的host为你的虚拟机真实 IP,application.yml中profiles.active设置为local。 - 启动项目 :IDEA 中运行
InsightFlowLiteApplication启动类,控制台无报错,看到「监控数据消费者已启动」日志即为启动成功。 - 功能验证 :
- 控制台每秒输出「生成监控数据」「成功消费监控数据」日志;
- 浏览器访问
http://localhost:8080,自动跳转到监控大屏,数据每秒刷新; - 访问
http://localhost:8080/health,查看项目运行环境; - 访问
http://localhost:8080/test/redis,验证 Redis 连接是否正常。
5.2 本地开发踩坑全记录(重点!新手必看)
坑 1:RedisConnectionFailureException 无法连接 Redis
报错信息 :org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis问题原因:
- 虚拟机防火墙未放行 6379 端口;
- Redis 容器启动时未关闭保护模式,仅允许本地访问;
- 虚拟机 IP 配置错误,Windows 无法 ping 通虚拟机;
- Redis 密码配置不匹配。
解决方案:
-
虚拟机放行 6379 端口: bash
运行
firewall-cmd --add-port=6379/tcp --permanent firewall-cmd --reload -
重新启动 Redis 容器,关闭保护模式: bash
运行
docker stop if-redis && docker rm if-redis docker run -d --name if-redis -p 6379:6379 redis:latest redis-server --requirepass 123456aq@ --protected-mode no --bind 0.0.0.0 -
虚拟机执行
ip addr确认真实 IP,修改application-local.yml中的host配置; -
核对 Redis 密码,确保配置文件与启动命令中的密码完全一致。
坑 2:Spring Data Redis Stream API 无法解析方法
报错信息 :无法解析方法 'read(StreamOffset<String>, Duration)'问题原因 :Spring Boot 3.x 版本中,Spring Data Redis 的 Stream API 发生了破坏性变更,不同小版本的方法签名不一致,导致版本兼容问题。解决方案:放弃 Redis Stream,改用 Redis List 实现轻量级消息队列。List 是 Redis 最基础的数据结构,API 十年未变,全版本兼容,且完全满足本项目的消息队列需求,代码更简单,无兼容问题。
坑 3:消费者报「消费消息异常」,无详细日志
报错信息 :消费消息异常,无具体异常栈问题原因 :数据生成器写入 Redis Stream,消费者读取 Redis List,数据结构完全不匹配,导致消费失败;同时日志中仅打印了异常信息,未打印异常栈,无法定位问题。解决方案:
- 统一数据结构:生成器和消费者都使用 Redis List,队列 Key 完全一致;
- 完善日志打印:
log.error("消费消息异常", e),必须传入异常对象e,才能打印完整异常栈。
坑 4:切换profiles.active=docker后项目启动报错
报错信息 :Redis 连接失败,配置未加载问题原因 :缺少application-docker.yml配置文件,或配置文件中 Redis host 配置错误。解决方案 :补全application-docker.yml配置文件,容器内访问 Redis 不能用虚拟机 IP,必须用 Redis 容器名,或使用host网络模式。
坑 5:前端页面无法连接 WebSocket
报错信息 :浏览器控制台报错SockJS connection failed问题原因 :WebSocket 端点配置错误,或前端引入的 SockJS/STOMP 库地址失效。解决方案:
- 核对 WebSocket 配置类中的端点
/ws/monitor与前端连接地址一致; - 使用 jsdelivr 的 CDN 地址,确保库能正常加载;
- 检查 Spring Boot 是否有拦截器拦截了 WebSocket 端点。
6. Docker 容器化一键部署全流程
6.1 部署前准备
-
修改环境配置 :将
application.yml中的profiles.active改为docker。 -
本地打包项目 :IDEA 终端执行打包命令,生成可执行 JAR 包:
bash
运行
mvn clean package -DskipTests打包成功后,在项目
target目录下会生成insightflow-lite-0.0.1-SNAPSHOT.jar。
6.2 编写 Docker 相关配置文件
6.2.1 Dockerfile
在项目根目录新建Dockerfile,用于构建项目镜像:
dockerfile
# 基础镜像:轻量的OpenJDK 17,匹配Spring Boot 3.x
FROM openjdk:17-jdk-slim
# 容器内工作目录
WORKDIR /app
# 复制本地打包好的JAR包到容器内,重命名为insightflow.jar
# 注意:JAR名称必须和本地target目录下的名称完全一致
COPY target/insightflow-lite-0.0.1-SNAPSHOT.jar /app/insightflow.jar
# 暴露容器内的8080端口
EXPOSE 8080
# 容器启动命令:运行JAR包,激活docker环境
ENTRYPOINT ["java", "-jar", "insightflow.jar", "--spring.profiles.active=docker"]
6.2.2 docker-compose.yml
在项目根目录新建docker-compose.yml,实现应用 + Redis 一键启动:
yaml
version: '3.8'
services:
# 监控应用服务
insightflow-app:
build: . # 基于当前目录的Dockerfile构建镜像
image: insightflow:v1.0
container_name: insightflow-app
ports:
- "8080:8080" # 端口映射:宿主机8080 → 容器8080
volumes:
- ./app-logs:/app/logs # 挂载日志目录到宿主机,避免日志丢失
depends_on:
- if-redis # 依赖Redis服务,先启动Redis再启动应用
restart: always # 异常崩溃自动重启
networks:
- insightflow-network
# Redis服务,和本地开发用同一个容器名
if-redis:
image: redis:7-alpine
container_name: if-redis
ports:
- "6379:6379"
# 启动命令:设置密码、关闭保护模式、允许所有IP访问
command: redis-server --requirepass 123456aq@ --protected-mode no --bind 0.0.0.0
volumes:
- redis-data:/data # 数据持久化,重启容器不丢失数据
restart: always
networks:
- insightflow-network
# 自定义网络:容器间通过网络互通
networks:
insightflow-network:
driver: bridge
# 数据卷:持久化Redis数据
volumes:
redis-data:
6.3 服务器部署步骤
-
传输文件到服务器 :通过 FinalShell 将以下文件传输到虚拟机的
/root/insightflow目录:- 本地
target目录下的 JAR 包; - 项目根目录的
Dockerfile; - 项目根目录的
docker-compose.yml。
- 本地
-
服务器创建目录:
bash
运行
mkdir -p /root/insightflow cd /root/insightflow -
构建并启动服务:
bash
运行
# 构建镜像并启动所有服务,-d表示后台运行 docker compose up -d --build -
验证容器启动状态:
bash
运行
# 查看容器运行状态,确保两个容器都是Up状态 docker compose ps -
查看应用启动日志:
bash
运行
# 实时查看应用日志,确认启动成功无报错 docker compose logs -f insightflow-app日志中看到
Started InsightFlowLiteApplication in X seconds,且无 Redis 连接报错,即为部署成功。
6.4 部署验证
-
服务器内验证:
bash
运行
# 访问健康检查接口 curl http://localhost:8080/health # 正常返回:✅ InsightFlow_lite 项目启动成功!当前运行环境: docker -
外部访问验证 :浏览器访问
http://虚拟机IP:8080,即可看到实时监控大屏,数据每秒刷新,异常自动标红。 -
Redis 数据验证:
bash
运行
# 进入Redis容器 docker exec -it if-redis redis-cli -a 123456aq@ # 查看队列长度,正常应接近0,说明生产消费速度匹配 LLEN monitor:queue:server-data
6.5 常用运维命令
bash
运行
# 停止所有服务
docker compose down
# 重启应用服务
docker compose restart insightflow-app
# 查看实时日志
docker compose logs -f insightflow-app
# 删除镜像(如需重新构建)
docker rmi insightflow:v1.0
7. 项目进阶优化方向
7.1 可视化升级
引入 ECharts 可视化库,将监控数据从简单的数值展示升级为折线图、仪表盘、柱状图,展示 CPU / 内存 / 磁盘的历史趋势,提升页面的专业度和可视化效果。
7.2 多服务器监控支持
修改数据生成器,支持多服务器实例的数据采集,前端页面通过多卡片的形式展示不同服务器的监控状态,实现集群监控能力。
7.3 异常告警通知
新增告警服务,集成钉钉 / 企业微信 / 邮件机器人,当检测到异常数据时,自动推送告警消息到手机 / 企业微信,实现真正的监控告警能力。
7.4 数据持久化与历史查询
引入 MySQL/InfluxDB 时序数据库,将监控数据持久化存储,新增历史数据查询接口,前端页面支持按时间范围查询历史监控数据,生成趋势报表。
7.5 权限管理与用户系统
引入 Spring Security + JWT,实现用户登录、权限管理,不同用户只能查看对应权限的服务器监控数据,提升系统的安全性。
7.6 集群化与高可用
实现 Redis 集群部署,避免单点故障;应用服务多实例部署,通过 Nginx 实现负载均衡,提升系统的并发能力和可用性。
8. 项目总结
本项目从零到一完成了一套完整的实时监控告警系统,覆盖了 Spring Boot 后端开发、Redis 中间件使用、WebSocket 实时通信、Docker 容器化部署等后端开发核心技能,全程包含完整的代码实现、踩坑记录、部署流程,非常适合 Java 后端新手入门实战
通过本项目的开发,你可以掌握:
- Spring Boot 项目的分层架构设计、规范开发流程;
- Redis 的核心使用场景,消息队列的实现原理;
- 实时通信场景的技术选型,WebSocket 的开发与使用;
- 统计学算法在业务场景中的落地应用;
- Docker 容器化开发、部署的全流程;
- 项目开发中的问题排查、踩坑解决能力。
后续可以基于本项目继续扩展优化,不断提升项目的复杂度和技术深度,逐步掌握企业级后端开发的核心能力。如果在项目开发过程中遇到任何问题,欢迎在评论区留言交流。