半夜两点半手机铃声跟催命一样响起来我迷迷糊糊摸到手机屏幕上显示的是机房值班同事的号码。刚接通那边就是一连串急切的汇报“TongWeb彻底不响应了页面全部502应用日志疯狂报错重启一次撑不过十分钟又挂……”挂了电话我一边穿外套一边在大脑里过了一遍可能的原因——这类问题在国产中间件圈子里太典型了TongWeb作为东方通的当家产品在政企、金融、能源这些对自主可控要求高的行业里部署量非常大它一旦踢踏影响面往往是整条业务线。这篇文章我不打算写什么高深的理论就老实复盘一下我处理TongWeb异常宕机的完整思路、排查步骤还有那些坑——包括maxsizecache这类JVM参数引发的诡异问题以及授权license失效导致的假宕机希望能给同样在用TongWeb的朋友们一个参考。1. 宕机问题定位先分清“假死”和“真挂”很多人一看到服务不可用第一反应就是“重启大法”先重启了再说。但TongWeb这类中间件宕机如果连现象都没确认清楚就盲目重启很可能把现场给破坏了后面再想排查就难上加难。我的习惯是接到故障通知的第一个动作永远是先把现象分类。1.1 现象确认与信息收集TongWeb“宕机”在实际运维中存在两种完全不同的表现进程真挂了用ps -ef | grep tongweb查不到Java进程或者进程存在但CPU占用为0、端口完全无响应任你怎么连都连不上。这种情况多半是JVM崩溃比如本地内存溢出、JIT编译异常、系统OOM Killer下手或者外部因素比如误kill、机房断电。进程假死Java进程还在jps也能看到进程号但80端口/8080端口死活连不上应用日志也不再往里写东西。这种情况通常是线程池耗尽、死锁、或者Full GC长到离谱导致整个JVM进入“世界暂停”状态。分清楚这两类后续的处理方向就完全不同。真挂要查系统层和JVM层的崩溃日志假死则优先做线程转储分析。在确认现象的同时必须立刻开始收集现场信息。我在团队里给运维同事下过一个死命令任何宕机先抓现场再谈重启。需要抓的东西按照优先级排列优先级收集内容获取方式P0JVM崩溃日志hs_err_pid*.log默认在TongWeb的bin或工作目录下文件名以hs_err_pid开头P0系统日志/内核日志/var/log/messages或journalctl -xe看有没有OOM Killer记录P1堆转储文件heap dump启动参数里配了-XX:HeapDumpOnOutOfMemoryError的话会生成.hprof文件P1线程转储thread dump宕机前如果还能登录立刻执行jstack pid保存P2TongWeb运行日志默认在安装目录的logs/下宕机时刻附近的异常堆栈最有用P2最近变更记录有没有发布过新代码、改过JVM参数、动过数据库连接池配置这里面有个很关键的经验P0级别的崩溃日志和系统日志是需要在宕机后第一时间去拷贝的因为很多环境重启之后临时目录被清理hs_err文件可能就没了。我见过太多团队因为急着恢复业务重启完才发现“忘了保存现场”后面只能靠猜。1.2 第一时间保留现场证据除了系统层面的日志我强烈建议在TongWeb启动脚本里提前把JVM崩溃时的转储工具打开。具体来说在bin/目录下的启动脚本比如startserver.sh里JAVA_OPTS那段加上以下参数JAVA_OPTS-Xms4g -Xmx4g -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt/tongweb/logs-XX:HeapDumpOnOutOfMemoryError这个参数非常关键它会在JVM发生堆内存溢出的时候自动生成堆转储文件而且不会影响正常运行。配合-XX:HeapDumpPath指定生成路径方便事后用MAT或者jvisualvm分析。另外线程转储在假死场景下几乎是救命稻草。如果你还能登录到服务器执行命令一定不要犹豫立刻执行jstack -l pid /opt/tongweb/logs/thread_dump_$(date %Y%m%d_%H%M%S).txt连续抓三次每次间隔10秒。为什么要抓三次因为单次线程转储可能捕捉不到瞬态的锁竞争三次转储才能看出线程状态是持续阻塞还是短暂等待。这个细节在后面的死锁分析中会反复用到。还有一招是查看系统级的资源限制和负载状态# 查看文件句柄数使用情况 ls -l /proc/pid/fd | wc -l # 查看系统内存与CPU free -g top -H -p pid # 查看进程启动时间判断是不是被人为kill过 ps -o pid,lstart,etime,cmd -p pid/proc/pid/fd这个目录里是进程打开的所有文件描述符数量如果超过了系统限制的80%就要警惕“too many open files”了。我之前遇到过一次TongWeb连接不上、重启后立刻又挂的情况最后就是靠ls -l /proc/pid/fd | wc -l发现文件句柄到了10万多超了默认的1024软限制好几倍进程直接被系统判了死刑。2. 常见宕机原因排查内存、线程、磁盘逐个过TongWeb底层是标准的Java技术体系所以它的大部分宕机原因跟其他Java应用服务器如Tomcat、WebLogic高度重合。但TongWeb又有自己的特殊性——部署方式多种多样有的跑在物理机上有的跑在虚拟机里还有大量的信创环境用的是国产CPU和国产操作系统JVM在不同架构上的行为差异也会放大某些问题。所以排查的时候我习惯按“内存→线程→磁盘”这个顺序逐个排除。2.1 内存溢出堆内存与元空间的两类典型内存溢出是TongWeb宕机里最常见的原因没有之一。但同样是OOM堆内存溢出和元空间溢出在现象上有明显区别排查方向也完全不同。堆内存溢出Java heap space堆内存溢出的典型表现是应用日志里出现java.lang.OutOfMemoryError: Java heap space伴随而来的是系统响应变慢、GC频繁、最后彻底无响应。这种情况要么是应用本身有内存泄漏比如缓存只进不出、数据库查询结果集过大、文件流没关闭要么是堆大小配置根本不够用。排查的时候先看启动参数里的-Xms和-Xmx是多少。有一个很常见的配置误区是-Xms和-Xmx设置不一致导致JVM在运行过程中频繁申请和释放内存反而加剧Full GC。我的建议是直接让这两个值相等避免动态扩容带来的性能损耗。元空间溢出Metaspace元空间溢出跟TongWeb的插件热部署、页面动态编译这类机制密切相关。日志中的报错是java.lang.OutOfMemoryError: Metaspace这类问题在反复发布应用、或者应用中大量使用反射生成代理类的场景下特别容易出现。这里就牵涉到搜索热词里提到的maxsizecache了它和JVM启停参数、元空间的动态策略有关。说白了如果启动脚本里对-XX:MaxMetaspaceSize或某些缓存上限参数限制不当或者JVM根据物理机内存自动推算的缓存上限超过了实际可用内存就容易在业务高峰触发“缓存无法晋代”式的连环OOM表现为进程突然退出。实际处理时我给TongWeb设置的元空间参数通常是JAVA_OPTS-XX:MetaspaceSize512m -XX:MaxMetaspaceSize512mMetaspaceSize和MaxMetaspaceSize相等是为了让JVM在启动时就预留足够的元空间避免运行期动态扩容触发不必要的Full GC。如果你观察到jstat -gc里Metaspace区域持续增长且每次Full GC后回收效果有限那基本可以断定是类加载器泄漏得从应用代码层面排查单纯调大参数只是治标。maxsizecache相关的诡异案例这里补充一个我实际踩过的坑。有一个业务系统TongWeb部署在8G内存的虚拟机上堆设了6G结果每天下午三点左右准时宕机系统日志里完全找不到JVM的OOM记录只能看到进程突然消失。查了很久才发现是启动脚本里被加了一行类似-XX:MaxDirectMemorySize4g的参数——其中隐含的缓存大小上限在物理内存不足时直接触发了JVM本地内存分配失败进程被操作系统强制终止。这类问题隐蔽性极强因为堆内存指标都正常但实际上JVM在堆外内存的申请上已经撞了墙。处理方案是把-XX:MaxDirectMemorySize调小或者干脆移除让JVM自行管理同时保证堆内内存和物理内存在总量上留足余量。排查内存问题时我常用的命令序列是# 查看GC情况和各内存区域用量 jstat -gcutil pid 1000 10 # 查看堆空间各区细节 jmap -heap pid # 如果能在OOM时保留堆转储用MAT分析支配树 jmap -dump:live,formatb,file/opt/tongweb/logs/heap.hprof pid2.2 线程耗尽连接池与线程池的配置陷阱线程池耗尽导致的“假死”是TongWeb宕机里最让人抓狂的一类问题——表面上看进程还在但是所有请求都进不来日志也不滚动像中了定身术。这类问题的根源九成出在线程池和连接池的配置上。TongWeb里有两套线程池最容易惹祸HTTP请求处理线程池和后端数据库连接池。HTTP线程池默认大小通常在200左右如果并发请求量突增或者某个下游接口响应极慢线程池会被全部占满新请求只能排队等待最终表现为服务不可用。数据库连接池比如Druid、HikariCP或者TongWeb自带的连接池如果最大连接数设置太小或者连接泄漏导致连接被占满HTTP线程又会集体阻塞在获取连接的环节形成“线程池耗尽→连接池耗尽”的双重死锁。遇到这种假死情况最直接的排查动作就是抓线程转储jstack -l pid thread_dump_1.txt sleep 10 jstack -l pid thread_dump_2.txt打开线程转储文件重点看几类状态RUNNABLE的线程如果大量集中在同一个源码位置比如JDBC驱动的SocketInputStream.read说明都在等数据库返回优先排查慢SQL和数据库连接数。BLOCKED状态的线程如果数量巨大且集中等待同一个锁对象说明存在锁竞争或者代码层面的死锁。WAITING状态的线程大量堆积在AbstractQueuedSynchronizer$ConditionObject上多半是线程池队列过长请求在排队饿死。有一个我反复强调的点线程转储里的“满载”不代表线程泄漏要看是被IO阻塞还是被业务锁阻塞。如果是被下游系统拖累导致HTTP线程池全满单纯调大TongWeb的HTTP线程数maxThreads治标不治本反而会加重下游负担。这时候应该加的是熔断限流机制把异常请求在入口处挡掉。2.3 磁盘与文件句柄容易被忽略的定时炸弹内存和线程问题相关文档很多容易被大家记住但磁盘和文件句柄这两类问题因为太“底层”反而成了宕机的隐形黑手。我处理过的TongWeb宕机里至少有三次的根因不是Java层而是系统资源被耗尽了。磁盘写满TongWeb的应用日志、访问日志、GC日志都在持续写磁盘。如果日志保留策略配置不当或者日志文件所在分区容量规划不足磁盘一旦写满TongWeb的日志组件会抛出IOException: No space left on device如果这个异常发生在关键线程比如负责会话持久化的后台线程会直接拖垮整个节点。检查磁盘空间是排查的第一件事df -h du -sh /opt/tongweb/logs/* | sort -rh | head -20尤其是logs/目录下的server.log和access.log在访问量大的系统上一天就能涨好几个G。我的建议是在TongWeb的日志配置里开启按天滚动和数量轮转保留最近7~15天的日志即可千万别图省事把日志留着不清理。GC日志同理-Xloggc:/opt/tongweb/logs/gc.log配上-XX:UseGCLogFileRotation和-XX:NumberOfGCLogFiles5 -XX:GCLogFileSize20m防止GC日志无限占盘。文件句柄耗尽前面提到过/proc/pid/fd这里再展开讲一下。Java应用服务器对文件句柄的需求远超普通进程——每个socket连接、每个打开的文件、每个Jar包都要占用一个句柄。TongWeb在连接数多的时候文件句柄数轻松就能破万。Linux系统默认的ulimit -n往往只有1024如果不调高一旦连接数上来进程就会报java.io.IOException: Too many open files处理方案有两个层面。系统层面在/etc/security/limits.conf里设置* soft nofile 65535 * hard nofile 65535TongWeb层面确认它的启动用户归属正确避免因为启动方式不同比如用systemd启动导致limits配置没生效。一个很容易忽略的点是ulimit -n的设置只对当前shell和其启动的进程生效如果TongWeb是通过开机自启脚本拉起来的必须确认自启脚本的环境里也加载了对应的limits配置。这类问题的排查命令是# 查看进程实际打开的文件句柄数 ls -l /proc/pid/fd | wc -l # 查看各项限制 cat /proc/pid/limits | grep -i open files # 统计句柄类型分布 ls -l /proc/pid/fd | awk {print $NF} | sed s/.*\.// | sort | uniq -c | sort -rn | head3. 深入TongWeb的日志与配置诊断把系统层面的原因排除之后如果问题还定位不到就得回到TongWeb本身从它的运行日志和配置文件里找线索。TongWeb的日志体系虽然没有像WebLogic那样庞大复杂但关键信息还是有的关键看你会不会用。3.1 关键日志文件怎么看TongWeb安装目录下的标准日志结构大致如下/opt/tongweb/ ├── bin/ ├── conf/ │ └── server.xml # 主配置文件端口、线程池、连接器都在这里 ├── logs/ │ ├── server.log # TongWeb自身运行日志宕机前最后的信息最有用 │ ├── access.log # HTTP访问日志可以统计请求量和响应码 │ ├── kong.log # 部分版本的kong同步模块日志 │ ├── management-server.log # 管理控制台相关日志 │ └── gc.log # 如果配置了-verbose:gc会生成排查宕机时server.log是我第一个打开的文件。看日志不要从头一页页翻直接跳到宕机时间点前后找异常堆栈里第一个出现的ERROR或FATAL级别记录。很多时候TongWeb宕机前会在日志里留下连续的异常链从最早的那条异常开始分析往往能找到真正的根因。TongWeb的logs/server.log默认是单一文件没有按天切分长期运行下来文件会非常大几G很常见用grep和tail配合时间过滤比较高效# 查看宕机前后共5分钟内的日志 grep 2025-01-15 13:55\|2025-01-15 14:00\|2025-01-15 14:05 server.log | grep ERROR # 统计一段时间内的异常类型 sed -n /2025-01-15 13:00/,/2025-01-15 14:00/p server.log | grep -oP (?\[)\w(?\]) | sort | uniq -c | sort -rn访问日志通常不会直接导致宕机但它能帮你还原宕机前的流量状态。如果宕机前访问日志里的请求量呈爆发式增长或者大量请求的响应码是5xx那基本能判断是并发压力过大如果访问日志在某个时间点突然停止增加而进程还存活那大概率是线程池不干活了。3.2 授权license引发的问题处理说完了常规日志排查再提一个TongWeb独有的坑——授权license失效。热词里专门提到了tongweb授权license说明大家在实践中确实没少被它坑过。TongWeb作为商业中间件正常运行需要有合法的license授权文件。这个授权文件通常是一段加密的许可信息包含了版本、有效期、授权CPU数、授权实例数等内容。在实际运维中license引发的“异常”常见有三种情况授权过期license文件里的有效期到期TongWeb在启动或者运行到一定时间后拒绝继续提供服务日志里会出现类似license expired、License is invalid或者授权已过期的提示。授权与硬件绑定冲突有的授权是绑定服务器硬件特征的网卡MAC、CPU序列号如果你给服务器做过硬件变更比如换网卡、迁移虚拟机原license就失效了TongWeb会在启动时直接拒绝。授权容量不足license里限制了最大并发连接数或者最大部署应用数当业务量超过授权上限时新连接可能被拒绝业务侧感觉像是宕机了。这类问题的排查难度在于业务方看到的现象是“服务不可用”不会联想到license往往会把锅甩给TongWeb本身。我遇到过最典型的一次某系统凌晨的批处理任务突然大面积失败所有调用方都报连接超时TongWeb进程还活着但新连接一律不进来。当时排查到凌晨四点无意中打开了TongWeb的管理控制台发现授权状态显示“已过期”才恍然大悟——因为业务系统是7×24小时运行的license在前一天午夜过期服务器表面上看起来一切正常但TongWeb内部的授权校验机制已经开始拒绝新请求了。处理license问题的方法很直接# 查看TongWeb当前授权信息 cat /opt/tongweb/conf/license.dat # 或者在管理控制台的“授权”页面查看 # 更新license # 将新的授权文件放到conf目录下重启TongWeb即可在部署规划时务必要给你的license配置一个到期提醒机制。比如写一个简单的脚本每天检查license文件的过期时间提前30天通过邮件或者企业微信告警。千万别等到服务挂了才发现license过期那时候业务已经受损了。注意修改TongWeb配置文件或更换license文件时务必先备份原文件。我见过有同事在替换license时直接把原本永久的授权文件覆盖成了试用授权结果试用授权的有效期更短反而加速了下次问题的出现。4. 一次性解决方案与长期防护排查归排查生产环境出了宕机第一要务永远是恢复业务。等故障恢复之后再回头做根因分析和长期防护。这一节我按“应急处置”和“预防体系”两部分来讲分别对应宕机当下和日常运维中的动作。4.1 停机窗口内的应急处置TongWeb宕机之后最快速的处理路径是重启但重启也不是无脑地敲一个startserver.sh就完事。我的建议流程是立马停掉旧进程避免多个实例抢占端口。用ps -ef | grep tongweb找到进程号先kill再kill -9确认进程完全退出。检查端口是否释放netstat -lnp | grep 8080如果端口仍被占用可能是进程未完全退出或者处于TIME_WAIT状态等待一会儿或者手工清理。确认JVM启动参数启动前再检查一遍-Xmx、MaxMetaspaceSize、HeapDumpPath这些关键配置避免参数本身有问题导致重启后立刻又宕。依次启动TongWeb和应用有些环境里TongWeb和多个业务应用是相互依赖的启动顺序错了会导致应用启动失败。一般先启动TongWeb核心再部署/启动应用级服务。观察启动日志看TongWeb是否成功加载license、端口是否正常监听、应用是否完成部署连续观察几分钟确认没有异常堆栈再通知业务方验证。如果重启后还是在短时间内宕机比如重复发生就必须考虑另一种应急方案——降级或者切换。如果系统有负载均衡和高可用架构直接把故障节点从负载均衡池里摘掉将流量导到健康节点给故障节点留出充足的排查时间。如果只有单节点临时扩容一台备用机器、把应用部署上去顶着再回头分析故障根因也是可接受的方案。这里顺带提一句TongWeb宕机后千万不要在原因未查明时就急于调整一堆参数。我见过有人一看到宕机就把-Xmx从4G改成8G其实根因是代码里的内存泄漏改大堆内存只是延迟了下次宕机的时间。每次只改一个变量改完再观察一段时间验证这才是理性的做法。4.2 参数调优与监控告警配置故障恢复后的长期防护核心是两件事参数调优和信息监控。参数调优的目标不是“看上去舒服”而是给TongWeb在预期负载下留出充分的余量。TongWeb的HTTP线程池和连接器配置在conf/server.xml里它跟Tomcat的配置风格很像。一个比较通用的调优参考如下Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads400 minSpareThreads50 acceptCount200 maxConnections10000 /这里有几点要说清楚。maxThreads不是越大越好它代表Tomcat/TongWeb处理请求的工作线程数设太大容易让CPU在上下文切换上浪费大量时间。acceptCount是等待队列的长度如果队列太长请求的等待时间会非常久用户体验很差。我的经验是maxThreads400、acceptCount200的组合对于多数业务系统已经足够除非经过压测确认需要更高。JVM层面的参数推荐一份经过实践检验的底线配置JAVA_OPTS-server -Xms4g -Xmx4g -XX:MetaspaceSize512m -XX:MaxMetaspaceSize512m -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt/tongweb/logs-XX:UseG1GC是G1垃圾回收器相比传统CMSG1对超大堆的停顿控制更平稳在中间件这种长生命周期进程上表现更好。配合-XX:MaxGCPauseMillis200设置期望的最大GC停顿时间让JVM在吞吐量和延迟之间自动找平衡。监控告警方面TongWeb虽然自带管理控制台但生产环境建议接入统一的监控体系。方案有很多种最简单的是通过JMX导出TongWeb的指标然后用Prometheus Grafana采集展示。TongWeb的启停脚本里通过-Dcom.sun.management.jmxremote开启JMX远程监控JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port1099 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse注意生产环境直接开不认证的JMX端口有安全风险最好是绑定内网IP或者走跳板机访问。采集到的指标里我重点盯这几项堆内存使用率超过85%持续5分钟就告警。GC频率和耗时Full GC次数突然增加或者单次Full GC超过3秒就告警。活跃线程数HTTP线程池的使用率超过90%就告警。文件句柄数超过系统软限制的70%就告警。磁盘使用率超过85%就告警。有了这些监控指标绝大多数宕机风险都能在业务受到实质影响之前被发现。我团队的做法是给每个TongWeb节点配了一张监控大盘故障发生前就能收到告警短信等真宕机的时候至少心里有谱是因为内存涨上去了、还是线程池被打满了、还是磁盘快满了。这个“有谱”往往能把故障恢复时间从小时级压缩到分钟级。写在最后的一些体会做中间件运维这些年我越来越觉得TongWeb宕机本身不可怕可怕的是没有一套清晰、可复用的排查方法。每次故障处理完之后我都会把过程整理成一篇简单的复盘记录包括现象、时间线、排查命令、根因、最终处理方案、后续预防措施。这些记录在团队内部流转比任何培训都管用——当类似问题第二次出现时我们往往能在几分钟内直接定位到根因而不是又从零开始翻日志。宕机问题的排查说到底拼的不是谁懂的理论多而是谁踩过的坑多、记录得多。我踩过的这些坑希望你看完之后能少走几步弯路。
