ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Tomcat进程监控与自动重启:Shell脚本实现三层健康检查

Tomcat进程监控与自动重启:Shell脚本实现三层健康检查 我相信不少朋友都遇到过这种场景线上Tomcat服务下午三点就挂了结果直到五点钟用户投诉、业务方打电话过来你才冷汗直冒地登录服务器看到ps里压根没有Java进程了。这种“服务挂了没人知道”的被动局面是所有跑Tomcat的团队都躲不开的痛。所以这次我把自己常用的“Tomcat进程监控脚本”完整拆开来讲。这套脚本的核心能力很简单定时检查Tomcat进程是否存在端口是否在监听HTTP接口是否真的能通一旦发现异常就自动拉起并告警。它不是单独一条命令而是一个从判断到处置的完整闭环。适合运维、负责部署的后端开发、以及一切“顺手管着几台服务器”的朋友直接抄作业也能帮刚接触shell脚本的新人理解监控这件事应该怎么分层去做。1. 监控脚本的整体设计思路1.1 为什么先盯进程而不是只盯端口很多人写监控脚本的第一个动作就是ps -ef | grep java这个方向没错但我建议你别只盯这一层。进程是“根”端口是“果”。Tomcat进程死了端口必然消失可端口还在不代表Tomcat就是健康的——JVM卡死、线程池耗尽、GC频繁导致应用无响应这些情况下8080端口可能照样处于LISTEN状态你拿ss -ltn一看全是监听可业务实际已经“假死”了。所以真正靠得住的监控应该按三层来做第一层进程层确认java进程还活着且没有被标记成僵尸状态。第二层端口层确认Tomcat配置的HTTP端口和关闭端口还在正常监听。第三层应用层用curl去请求一个具体页面判断业务是否能正常返回。三层全绿才算健康任何一层出问题都应该触发告警和修复动作。这套思路不仅仅适用于TomcatNginx、MySQL、Redis等所有长驻服务都能套用。我觉得这是整个脚本里最值得你带走的设计理念。1.2 监控分层与脚本选型为什么用shell脚本你要说监控方案市面上能选的太多了Zabbix、Prometheus、夜莺、Grafana全家桶都能做得很漂亮。但我在大多数项目里第一反应还是先写一个shell脚本跑着原因有三点第一部署成本极低。shell脚本不需要装Agent、不需要配Server、不需要申请监控系统中控权限扔到服务器上配一条crontab就能用。对于只有几台服务器、不想为监控引入一堆新组件的小团队来说这是性价比最高的方案。第二排障链路短。脚本跑挂了、没跑了直接看日志、看crontab状态就能定位没有额外链路要查。Zabbix/Prometheus的架构里Agent异常、网络不通、存储打满、告警规则配置错误任何一环出问题都会让你排查半天反而耽误处理生产故障。第三处置动作直接。脚本发现Tomcat挂了可以立刻调用/opt/tomcat/bin/startup.sh拉起来而标准监控系统的职责边界是“发现问题并通知人”自动恢复通常要借助额外的自愈组件。说实话很多中小规模场景根本还没到上完整监控平台的时候先用脚本把“活了没、挂了拉起来、通知我”这件事跑通才是重点。当然这也意味着shell脚本有短板它没有历史指标、没有趋势图、无法分析线程数增长曲线。所以我的定位是脚本做兜底监控平台做趋势分析两者不冲突。等你的服务规模大到需要动态伸缩、容量预测时再平滑迁移到完整监控体系一点都不亏。2. 核心监控逻辑的细节实现2.1 用 ps / pgrep 判断进程是否真的健康检查进程最朴素的是ps -ef | grep tomcat | grep -v grep但我更推荐你用pgrep它本身就是干这个活的不需要再额外排除grep自己。# 判断进程数量 count$(pgrep -f org.apache.catalina.startup.Bootstrap | wc -l) if [ $count -lt 1 ]; then echo Tomcat进程不存在 fi注意我在pgrep -f里匹配的不是java也不是笼统的tomcat而是org.apache.catalina.startup.Bootstrap。这个类是Tomcat的实际启动入口匹配它有两个好处一是能准确区分这台机器上跑着的多个Java进程不至于把别的Java程序误当成Tomcat二是能匹配到通过startup.sh启动的Tomcat进程。如果你用ps -ef看实际进程的长命令行里就会带有这个类名。只判断“进程存在”还不够还需要检查进程状态。如果进程变成了僵尸进程Zombie状态标记为Z说明它实际上已经死了只是没被父进程回收。这种状态要当作异常处理zombie_cnt$(ps -eo pid,stat,cmd | grep org.apache.catalina.startup.Bootstrap | grep -v grep | grep -c Z)另外我习惯顺带确认进程的启动用户。有些项目线上要求Tomcat不能用root跑如果哪次误用了root启动脚本里应该提示风险ps_user$(ps -o user -p $tomcat_pid 2/dev/null) if [ $ps_user ! tomcat ]; then echo 警告当前进程用户是 $ps_user不是期望的tomcat用户 fi2.2 端口检查的几个坑进程活着端口未必正常。Tomcat默认有三个端口8005是关闭端口shutdown port8080是HTTP服务端口8009是AJP端口。生产环境通常只开放HTTP端口对外所以我脚本里重点盯的就是8080具体端口以你的server.xml为准。检查端口我推荐用ss而不是netstat。现代Linux发行版很多默认不装netstat了而ss来自iproute2基本是标配port_count$(ss -ltn | grep -c :8080 ) if [ $port_count -lt 1 ]; then echo 8080端口未监听 fi注意grep :8080 后面这个空格很关键。如果不加空格:80803、:80801这种相似端口也会被匹配进去造成误判。我早期就在这个细节上吃过亏一个服务占着80803端口结果脚本误以为8080还活着愣是漏报了一晚上。还有一种情况进程在但端口监听在127.0.0.1而不是0.0.0.0。这时候ss -ltn能看到端口但外部业务可能已经访问不到了或者配置的IP不对。这种问题进程监控发现不了必须依赖HTTP探测层才能暴露出来。2.3 健康检查curl 探测 HTTP 应用层进程层和端口层都过了最后一定要做应用层探测。我的做法是请求Tomcat的首页或者一个专门写的健康检查接口判断HTTP返回码。http_code$(curl -s -o /dev/null -w %{http_code} \ --connect-timeout 5 --max-time 10 \ http://127.0.0.1:8080/healthcheck) if [ $http_code ! 200 ]; then echo HTTP健康检查失败返回码$http_code fi几个参数说明一下--connect-timeout 5建立连接超时5秒。连接超时通常意味着服务根本接受不了新连接这比请求超时更严重。--max-time 10整个请求最多跑10秒。防止接口因为线程阻塞而一直挂着拖死脚本。-s静默模式不输出进度条-o /dev/null丢弃响应体我们只关心状态码。健康检查URL的选择有讲究。别只请求首页很多项目首页全是静态资源或者干脆返回404不能代表业务进程健康。最好是让开发提供一个无业务逻辑依赖、能反映Spring容器已加载完毕的接口比如/healthcheck返回固定JSON或者OK。如果项目没有这种接口退而求其次请求一个真正的业务接口也行但要注意这个接口不能太重否则每次探测都压一次数据库反而是给生产添乱。实测下来有个简单经验健康检查接口的返回时间超过3秒这个服务基本已经处于亚健康状态了等它超时再告警其实已经晚了可以考虑追加响应时间阈值判断。2.4 日志和告警别让脚本白忙活监控脚本最怕的是什么是它发现问题了但没人知道。所以告警这块必须做扎实。我的告警通道按优先级分了几个层次本地日志文件所有判断和动作都记录到/var/log/tomcat_monitor.log方便事后追溯。Webhook消息通过curl调用钉钉/企业微信/飞书的机器人接口把异常摘要直接推到手机上。邮件告警如果企业内网没有Webhook就用mail命令发邮件。Webhook这块我现在用得比较多因为手机提醒比邮件及时。简单示例如下send_webhook() { local msg$1 curl -s -X POST \ -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\$msg\}} \ https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你自己的key }消息内容不要只写一句“Tomcat挂了”要把关键信息带上哪个服务器、哪个端口、当前进程数、HTTP返回码、脚本动作重启了还是只告警。这样人拿到告警就能判断严重程度而不是又要登录服务器看一遍。日志格式我也建议固定字段后续grep起来方便log() { echo $(date %Y-%m-%d %H:%M:%S) [$$] $* /var/log/tomcat_monitor.log }$$是脚本进程PID多个crontab任务重跑时能靠它区分日志来源。这个细节平时不起眼但排查告警风暴时特别有用。3. 完整脚本与部署步骤3.1 一个可直接落地的监控与自愈脚本下面是我在Linux服务器上实践过的一个通用版本。它把前面讲到的进程检查、端口检查、HTTP探测、自动重启、告警集中到了一起。你拿到手后需要重点修改的是几个TOMCAT_*开头的变量。#!/bin/bash # # Tomcat进程监控脚本 # 功能检测Tomcat进程/端口/HTTP健康状态异常时自动重启并告警 # 建议部署crontab每分钟执行一次 # # ------------------------- 可配置区域 ------------------------- TOMCAT_HOME/opt/tomcat # Tomcat安装目录 TOMCAT_USERtomcat # 启动Tomcat的系统用户 TOMCAT_PORT8080 # HTTP服务端口 HEALTH_CHECK_URLhttp://127.0.0.1:${TOMCAT_PORT}/healthcheck # 健康检查地址 PROCESS_PATTERNorg.apache.catalina.startup.Bootstrap # 进程匹配关键字 LOG_FILE/var/log/tomcat_monitor.log # 监控日志 ALARM_WEBHOOK_URLhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyyour_key # 告警Webhook RESTART_LIMIT3 # 连续重启次数上限防止频繁拉起 RC_FILE/var/run/tomcat_monitor.rc # 重启计数状态文件 # ------------------------- 基础函数 ------------------------- log() { echo $(date %Y-%m-%d %H:%M:%S) [$$] $* $LOG_FILE } send_alarm() { local message$1 log 发送告警$message curl -s -X POST \ -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\$message\}} \ $ALARM_WEBHOOK_URL /dev/null 21 } # ------------------------- 三层健康检查 ------------------------- # 1. 进程层检查 process_count$(pgrep -f $PROCESS_PATTERN | wc -l) if [ $process_count -lt 1 ]; then log 进程层异常未找到Tomcat进程 send_alarm Tomcat进程不存在准备自动重启 need_restart1 else need_restart0 fi # 2. 端口层检查 if [ $need_restart -eq 0 ]; then port_count$(ss -ltn | grep -c :${TOMCAT_PORT} ) if [ $port_count -lt 1 ]; then log 端口层异常${TOMCAT_PORT}端口未监听 send_alarm Tomcat端口${TOMCAT_PORT}未监听准备自动重启 need_restart1 fi fi # 3. 应用层检查 if [ $need_restart -eq 0 ]; then http_code$(curl -s -o /dev/null -w %{http_code} \ --connect-timeout 5 --max-time 10 $HEALTH_CHECK_URL) if [ $http_code ! 200 ]; then log 应用层异常HTTP探测返回码 $http_code send_alarm Tomcat健康检查失败HTTP返回码 ${http_code}准备自动重启 need_restart1 fi fi # ------------------------- 处置逻辑 ------------------------- if [ $need_restart -eq 1 ]; then # 读取当前连续重启次数 restart_count0 if [ -f $RC_FILE ]; then restart_count$(cat $RC_FILE) fi if [ $restart_count -ge $RESTART_LIMIT ]; then log 连续重启次数超过${RESTART_LIMIT}次停止自动尝试请人工介入 send_alarm Tomcat连续重启${restart_count}次仍未恢复已停止自动处理请立即人工介入 echo 0 $RC_FILE exit 1 fi log 执行重启操作当前连续重启次数$restart_count # 优雅关闭优先使用shutdown.sh su - $TOMCAT_USER -c $TOMCAT_HOME/bin/shutdown.sh 2/dev/null sleep 5 # 如果优雅关闭后进程还在强制杀掉 remaining_pids$(pgrep -f $PROCESS_PATTERN) if [ -n $remaining_pids ]; then log shutdown.sh未完全停止进程执行kill pkill -f $PROCESS_PATTERN sleep 3 fi # 清理可能存在的PID文件和临时缓存 if [ -f $TOMCAT_HOME/bin/pid.txt ]; then rm -f $TOMCAT_HOME/bin/pid.txt log 清理PID文件 fi rm -rf $TOMCAT_HOME/work/Catalina 2/dev/null # 启动Tomcat chown -R $TOMCAT_USER:$TOMCAT_USER $TOMCAT_HOME 2/dev/null su - $TOMCAT_USER -c $TOMCAT_HOME/bin/startup.sh $LOG_FILE 21 log Tomcat启动命令已执行等待健康检查确认 # 重启后等待90秒再做一次HTTP探测确认 sleep 90 confirm_code$(curl -s -o /dev/null -w %{http_code} \ --connect-timeout 5 --max-time 10 $HEALTH_CHECK_URL) if [ $confirm_code 200 ]; then log 重启成功健康检查通过 echo 0 $RC_FILE else log 重启后健康检查仍然失败 echo $((restart_count 1)) $RC_FILE fi else # 一切正常重置重启计数 if [ -f $RC_FILE ] [ $(cat $RC_FILE) -ne 0 ]; then echo 0 $RC_FILE log 服务恢复正常清零重启计数 fi fi exit 0脚本的核心设计是need_restart这个标志位。三层检查中任何一层失败就置为1最后统一走处置逻辑。我不建议每层检查失败时直接各自去重启这样容易重复触发逻辑上也乱。统一在最后做“先判断、再处置”你会更容易控制重启频率和排查问题。3.2 部署到 crontab 的正确姿势脚本写完了部署到定时任务里才算真正生效。chmod x /opt/scripts/tomcat_monitor.sh crontab -e在打开的crontab文件里加入这一行* * * * * /opt/scripts/tomcat_monitor.sh /var/log/tomcat_monitor_cron.log 21* * * * *表示每分钟执行一次。为什么定1分钟因为Tomcat冷启动通常需要30秒到1分钟如果监控周期太短比如每10秒一次重启动作还没完成下一次检查又开始了很容易把“正在启动”误判成“启动失败”进而触发连续多次重启。1分钟这个间隔既能保证发现问题后恢复及时又不会让监控本身成为性能负担。crontab环境变量这里有个大坑cron执行时用的PATH很精简可能只有/usr/bin:/bin而你的java可能装在/usr/local/java/bin里。如果脚本里用了相对路径或者依赖JAVA_HOME在命令行的终端里跑得好好的放进crontab就莫名其妙失败。我的做法是在脚本开头显式定义环境变量export JAVA_HOME/usr/local/java export PATH$JAVA_HOME/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这个export要放在脚本最前面。很多人排查“crontab不执行”查了半天最后发现就是PATH问题非常冤枉。3.3 自动重启的注意事项自动重启是本脚本“自愈”能力的关键但也是风险最高的地方。我在脚本里做了几个保护措施这里单独展开讲。第一优先优雅关闭。shutdown.sh会向Tomcat发送关闭指令让活跃请求有处理完的时间。直接kill -9会导致请求中断、数据库连接池来不及释放严重时可能造成数据不一致。我的做法是先调用shutdown.sh等5秒检查进程还在不在还在再用pkill补刀。第二防止重启风暴。如果Tomcat因为配置错误、磁盘满、端口被占用而反复启动失败脚本每分钟拉一次服务器CPU会一直被打满日志疯狂增长反而放大了故障。所以脚本里加了RESTART_LIMIT连续重启次数限制超过3次就停下来只告警不动作。状态文件/var/run/tomcat_monitor.rc用来记住当前连续重启次数服务恢复后自动清零。这是我从几次故障中总结出的教训自愈脚本断不能比故障本身还危险。第三启动前清理work目录。JSP编译缓存、临时文件损坏是Tomcat重启后依然404或者500的常见原因。强制清理work/Catalina目录能让Tomcat重新编译JSP避免“重启了还是坏的”这种尴尬。这个操作在开发环境没问题但如果生产环境用了自定义的临时文件目录要谨慎处理最好先确认是不是缓存引起的问题再清理。4. 常见问题与排查手册4.1 告警频繁误报的根源监控脚本上线后最常见的抱怨就是明明服务好的你凭什么告警我排查下来误报多半出在三个地方。第一个是健康检查URL选错了。请求首页、请求一个需要登录态的接口、请求一个响应很慢的业务接口都容易导致返回码不是200或者超时。解决办法是跟开发确认一个稳定、无副作用的健康检查接口。如果没有现成的自定义一个也不难。第二个是超时参数太短。有些接口正常就要8秒你--max-time设成5秒那它每次都“超时”。这个参数要基于实际响应时间设定。我的建议是先手动跑几轮看P99响应时间然后超时设置成P99的3倍以上才能既灵敏又不误伤。第三个是服务启动中的窗口期。重启Tomcat后启动过程需要几十秒甚至更久如果脚本在这个窗口期检查大概率是失败的。所以我特意在重启动作后sleep 90再探测就是为了回避这个启动窗口。同理如果你在crontab里不仅配了这个脚本还配了其他监控项也要考虑服务重启期间的联动误报。4.2 漏报的处理进程活着但服务已经不行了误报烦人漏报更要命。最常见的漏报场景就是进程没死、端口在听但业务已经处理不了请求了。这种情况只靠三层检查里的前两层根本发现不了必须依赖HTTP层的返回码和响应时间。我后来给脚本加了一个“慢响应”判断curl里用-w输出耗时超过3秒就报“服务响应过慢”。虽然脚本逻辑会复杂一些但实战价值很高。因为很多Tomcat故障不是突然崩溃而是内存泄漏、线程耗尽慢慢拖死响应时间会先飙升。等到进程彻底没了再告警故障影响早就扩散了。另外如果应用有JVM监控条件建议配合看FGC频率和堆内存使用率。视图层堆内存持续增长接近上限、FGC频繁到每秒几次基本可以判定内存泄漏这种问题光靠重启只能续命不能根治还是得安排开发排查代码。4.3 crontab里跑不出结果的排查顺序“手动执行脚本正常但crontab就是不生效”这可能是监控脚本上线第一天遇到最多的怪事。排查顺序我建议固定成这么几步第一步确认crontab真的被调度了。在脚本第一行加一句日志输出比如echo $(date) 脚本开始执行 /var/log/tomcat_monitor.log如果日志一直不更新就是crontab本身没生效更新了说明调度正常问题在脚本内部。第二步检查PATH和JAVA_HOME。在脚本里加一个env /tmp/cron_env.txt对比手动执行和crontab执行时环境变量的差异。90%的“crontab不执行”都是这个原因。第三步检查脚本权限。crontab执行用户如果是root但脚本没有可执行权限也会失败。chmod x解决。另外脚本里用到了相对路径而cron工作目录默认是当前用户家目录两者不一致也会导致找不到文件。最保险的做法是脚本内部所有路径都写绝对路径。4.4 多实例与容器化场景的调整如果你的服务器上不只跑一个Tomcat实例而是用CATALINA_BASE隔离了多个应用脚本需要按端口来区分。我的做法是把“进程匹配端口匹配”组合使用先pgrep拿到所有Tomcat进程再按端口:8080、:8081分别做检查这样一个脚本循环处理多个实例。千万别只靠grep tomcat判断否则一个实例挂了另一个还在你也会误判。容器化场景又不一样。Docker容器里的Tomcat进程本身由容器托管直接在容器里跑kill/startup很容易让Docker重新调度容器反而造成混乱。容器环境我更推荐用Docker原生能力HEALTHCHECK指令配合docker inspect --format {{.State.Health.Status}}来做健康检查容器服务挂了由容器编排平台负责拉起。脚本主要负责宿主机层面、容器外部的访问探测和告警。如果你还在用传统方式把Tomcat装在物理机或虚拟机上那我上面这套脚本就是最顺手的选择。5. 脚本的扩展方向5.1 接入 Prometheus 和 Grafana脚本解决了“服务挂了要立刻知道”的实时问题但它存不住历史数据没法回答“上个星期这个服务到底稳不稳定”。这个问题可以用Prometheus来补。最简单的接入方式是安装node_exporter采集主机指标配合JMX exporter采集Tomcat内部的JVM指标再用Grafana画面板。脚本这边呢可以把每次检查的结果输出成一个Prometheus格式的文本文件# 生成文本文件供 node_exporter 的 textfile collector 读取 cat EOF /var/lib/node_exporter/textfile/tomcat_monitor.prom # HELP tomcat_up Tomcat是否存活 1存活 0异常 # TYPE tomcat_up gauge tomcat_up 1 # HELP tomcat_health_check_http_code HTTP健康检查返回码 # TYPE tomcat_health_check_http_code gauge tomcat_health_check_http_code 200 EOF这样监控平台就能直接读取脚本检查的结果与JVM指标合在同一张Grafana面板上展示。脚本负责“动作”重启、告警Prometheus负责“记录”趋势、历史两者配合运维能力就完整多了。5.2 结合守护进程工具systemd 与 supervisor如果你用的是systemd管理的Linux发行版并且Tomcat是作为systemd服务注册的那其实自愈逻辑可以直接交给systemd[Unit] DescriptionApache Tomcat Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/local/java ExecStart/opt/tomcat/bin/startup.sh ExecStop/opt/tomcat/bin/shutdown.sh Restarton-failure RestartSec30 [Install] WantedBymulti-user.targetRestarton-failure会让systemd在进程异常退出时自动拉起。这比我脚本里的重启逻辑更底层、更可靠。为何我还保留shell脚本因为systemd只管“进程挂了没”管不了“进程活着但HTTP不通”。systemd守护进程层脚本守护应用层两者并不矛盾。我建议的组合是systemd负责进程拉起脚本负责HTTP探测和告警各管各的、互不干扰。至于supervisor适合用虚拟环境、Python服务等场景管理Tomcat这类自身就带完善启停脚本的Java服务反而多绕了一层。不过团队如果已经统一用supervisor管理所有服务那也不妨把Tomcat纳入其中关键在于“统一管理”比“各自为政”更容易让团队形成习惯。写到这里我还是想多说一句实际体会监控脚本这个事单纯看进程存不存在真的不够。从我踩过的坑来看进程监控只是兜底真正能让服务保持健康的是把端口检查、HTTP探测、日志告警、自动拉起这一整套链路串起来而且要非常克制地设计重启策略避免监控脚本本身成为故障放大器。如果你第一次上手建议先把这个脚本部署到测试环境跑一周观察误报和日志格式是否满意再放到生产。有条件的话给健康检查接口加上响应时间探测会比只看返回码更早发现问题。脚本再完善也只是发现问题的手段沉淀故障后的复盘和改进才是稳定性的真正来源。
返回列表