游客发表

服务器监控运维spring

发帖时间:2026-10-17 05:04:54

凌晨两点,服务Spring Boot 应用突然开始疯狂打印 `Connection refused`。器监不是控运数据库,是服务 Redis。监控面板上那个本该平稳的器监折线图,像被人拽了一把,控运直直往下掉。服务值班群里没人说话,器监只有告警机器人在刷屏。控运我爬起来打开电脑,服务第一反应不是器监看日志,而是控运先扫了一眼 `/actuator/health`——返回 `DOWN`,但细节里 Redis 的服务状态是 `UNKNOWN`。这就怪了,器监连接池明明还活着,控运只是所有命令都超时。

后来发现是 Redis 主节点被运维误重启,哨兵切换花了 47 秒。Spring 的 Lettuce 连接池默认没开自动重连,那 47 秒里所有请求全堵在 `RedisConnectionFactory` 上。监控系统只报了“Redis 不可用”,但没告诉我们连接池的 `waiters` 数量已经飙到 200 多。这就是典型的监控盲区:你监控了服务,没监控连接本身。

别只盯着 CPU 和内存

Spring 应用的监控,很多人第一反应是接 Prometheus + Grafana,看 JVM 堆、GC 次数、线程数。这些当然要。但真正让我半夜爬起来的事故,往往出在“中间件连接”上。数据库连接池的 `active` 连接数、HTTP 客户端的 `pending` 请求数、Redis 的 `command latency`——这些指标比 CPU 飙到 90% 更致命。CPU 高可能只是某个定时任务在跑,连接池满了就是直接拒绝服务。

我习惯在 `application.yml` 里把 `management.endpoint.health.show-details` 设成 `always`。然后写个定时任务,每 30 秒拉一次 `/actuator/health`,把每个组件的 `status` 和 `details` 里的关键数字推到内部监控。比如 HikariCP 的 `activeConnections`、`idleConnections`、`threadsAwaitingConnection`。`threadsAwaitingConnection` 大于 0 超过 10 秒,直接发告警。这比等用户报 500 错误快得多。

日志里的线索比指标更直接

有次线上订单接口偶尔超时,监控显示 TP99 从 200ms 跳到 2s,但 CPU、内存、GC 全正常。翻日志发现 `RestTemplate` 调用外部风控服务时,偶尔出现 `Read timed out`。风控那边说他们没问题。后来抓包才看到,Spring 的 `SimpleClientHttpRequestFactory` 默认没有连接池,每次请求新建 TCP 连接,遇到对方防火墙的 SYN 队列满就丢包。换成 `HttpComponentsClientHttpRequestFactory` 并设置 `maxTotal=200` 后,超时消失。

这件事让我养成了一个习惯:在 `logback-spring.xml` 里给所有 HTTP 客户端和数据库连接池单独配一个 logger,级别调到 `DEBUG`,但只在慢请求时输出。比如 HikariCP 的 `com.zaxxer.hikari.pool.HikariPool` 在 `DEBUG` 下会打印连接获取时间,超过 50ms 就记一条。不用改代码,日志里就能看到哪个连接等得最久。

用 Spring Boot Admin 做轻量级聚合

不是所有团队都愿意搭一套完整的 Prometheus + Alertmanager。如果只有几个 Spring Boot 实例,Spring Boot Admin 够用了。它直接读 Actuator 端点,能看健康状态、环境变量、日志级别,还能动态改日志级别。有次线上突然大量 `NullPointerException`,但日志级别是 `INFO`,堆栈打不全。我通过 SBA 把 `com.example.service` 的日志级别临时调到 `DEBUG`,两分钟就定位到是某个 `@Cacheable` 方法返回了 null 导致后续拆箱失败。改完再调回去,不用重启。

不过 SBA 的告警能力弱,得自己写个 `Notifier`。我实现了一个简单的 `AbstractStatusChangeNotifier`,当实例状态从 `UP` 变成 `DOWN` 时,往钉钉机器人发消息,带上 `health` 里的 `details`。这样至少能知道是数据库挂了还是磁盘满了。

监控本身也会挂

最讽刺的一次:监控系统自己 OOM 了。因为采集指标时用了 `Gauge.builder` 但没设过期时间,每个请求都往 `MeterRegistry` 里塞一个新的 `Timer`,标签里带了 `requestId`。三天后内存爆了。后来我把所有动态标签都去掉,改用固定标签 + 日志关联。监控代码也是代码,得 review,得压测。别让监控变成故障源。

现在我的做法很简单:Actuator 暴露必要端点,Prometheus 抓关键指标,日志里埋慢操作钩子,再加一个每五分钟跑一次的“健康巡检”脚本,用 `curl` 打 `/actuator/health` 并解析 JSON。三层下来,大部分问题在用户感知前就能发现。剩下的,靠经验——以及半夜那杯咖啡。

    热门排行

    友情链接