1. 先把 TongWeb7 这层窗户纸捅破:它到底解决什么问题
前阵子接了个活,客户那边一套跑了七八年的 Java Web 系统要从 Tomcat 挪到 TongWeb7 上,要求在 Linux 上重新部署一遍。我原本估摸着半小时收工,结果整整折腾了一下午——先是解压出来一堆乱码文件名,接着 License 文件放错目录导致启动脚本悄无声息地退出了,最后发现系统的文件句柄数只有 1024,压测一上来连接就被拒。这类部署活儿,难的不是"会不会",而是那些文档里从来不写、但一定会遇到的边角问题。
TongWeb7 是东方通推出的一款企业级 Java 应用服务器,实现了 Java EE 7 规范,涵盖 Servlet 3.1、JSP 2.3、EJB 3.2、JMS 2.0、JPA 2.1 这一整套能力。它和 Tomcat 最大的区别在于:Tomcat 本质是个 Servlet 容器,而 TongWeb 是完整的应用服务器,自带管理控制台、集群会话复制、JNDI 资源管理、国密算法支持这些企业级特性。很多项目之所以指定用它,是因为系统集成的中间件清单里写死了这一款,或者老应用用了 EJB、JMS 这类 Tomcat 原生不支持的东西,迁不过去。
这篇文章面向的是需要在一台干净的 Linux 服务器上,从零把 TongWeb7 跑起来的运维和开发同学。不管你用的是 CentOS、Ubuntu,还是麒麟、统信这类国内发行版,部署路径基本一致。我会把安装、授权、调优、服务化、排障这条完整链路掰开揉碎讲一遍,重点放在那些真正会卡住你的地方。
1.1 它和 Tomcat、WebLogic 的实际差异在哪
先做个横向对比,心里有个谱:
| 维度 | Tomcat | TongWeb7 | WebLogic |
|---|---|---|---|
| 规范覆盖 | 仅 Servlet/JSP | 完整 Java EE 7 | 完整 Java EE |
| 管理方式 | 改 XML、命令行 | Web 控制台 + 命令行 + 配置文件 | 控制台 + WLST |
| 部署单元 | war | war / ear | war / ear |
| 集群会话 | 需自行搭建 | 内置会话复制 | 内置 |
| 授权模式 | Apache 开源 | 商业 License | 商业 License |
| 安装体积 | 十几 MB | 数百 MB | 上 GB |
从表里能看出来,TongWeb7 的定位是"轻量一些的 WebLogic",但比 Tomcat 重。它的安装包通常有几百兆,解压后目录结构和 Tomcat 有几分神似,但多了license、deploy、domains这类目录。熟悉 Tomcat 的人上手会有种"似曾相识但又处处不同"的感觉,这也是为什么很多人第一次部署会踩坑——按 Tomcat 的习惯去操作,往往行不通。
1.2 版本和 CPU 架构必须先核对清楚
这一步千万别跳过。TongWeb7 的安装包是按 CPU 架构分发的,常见的有x86_64(Intel/AMD/海光)和aarch64(鲲鹏/飞腾)两种,个别版本还有龙芯 LoongArch 的包。拿错包解压后一执行,报错非常直接:
bash: ./startserver.sh: /bin/sh: bad ELF interpreter # 或者 cannot execute binary file: Exec format error看到这两个报错,别急着怀疑 JDK,先执行下面几条命令确认底数:
uname -m # 输出 x86_64 还是 aarch64 cat /etc/os-release # 看发行版和版本号 getconf LONG_BIT # 32 还是 64安装包的文件名里一般会带架构标识,比如TongWeb7.0.x.x_x86_64.tar.gz。如果文件名里没有,就找厂商要一份架构对照说明,别靠猜。
2. 装之前的环境底数:JDK、句柄数、安全策略一个都不能少
部署这类中间件,八成以上的"启动失败"其实发生在 TongWeb 启动之前,问题出在操作系统这一层。我见过太多人上来就解压、就跑脚本,然后卡在莫名其妙的报错上。正确的做法是先把系统环境捋一遍,逐项确认,后面能省下大量返工时间。
2.1 JDK 的版本选择和 JAVA_HOME 的坑
TongWeb7 主流版本要求 JDK 8 及以上(多数项目用 1.8),较新的小版本开始支持 JDK 11 和 JDK 17。选哪个版本取决于你的应用编译时用的字节码版本,不是越新越好——老应用在 JDK 17 上很可能因为模块化限制直接起不来。
装完 JDK 后有几件事必须做:
which java # 看当前用的是哪个 readlink -f $(which java) # 追到真实路径 java -version # 确认版本和位数关键在于JAVA_HOME。TongWeb 的启动脚本会去读这个变量,如果没设或者设错了,脚本会在早期阶段就退出。建议写进/etc/profile.d/java.sh:
export JAVA_HOME=/usr/local/jdk1.8.0_361 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar写完之后source /etc/profile生效,再用echo $JAVA_HOME验证。这里有个细节:如果你是通过su - tongweb切换用户,环境变量会重新加载;但如果用su tongweb(不带减号),环境变量不会刷新,很容易出现"root 下测得好好的,切用户就起不来"的情况。这是我踩过的第一个坑。
另外提一句,如果系统里装了多个 JDK,比如系统自带一个 openjdk、你手动又装了一个,务必确认 TongWeb 用的到底是哪个。有些启动脚本内部会硬编码 JDK 路径,改配置文件比改环境变量更可靠。
2.2 文件句柄数和内核参数要提前放量
默认的 Linux 单进程文件句柄限制通常是 1024,这个数值对中间件来说完全不够用。TongWeb 一个进程打开的 socket、日志文件、jar 包句柄加起来轻松超过这个数,压测或者并发一高就是Too many open files。
修改/etc/security/limits.conf,追加:
tongweb soft nofile 65535 tongweb hard nofile 65535 root soft nofile 65535 root hard nofile 65535同时在/etc/security/limits.d/下确认没有被别的文件覆盖掉。改完重新登录该用户,用ulimit -n验证。注意这个参数对已经登录的会话不生效,必须重新开一个终端。
还有两个常被忽略的参数:
sysctl -w net.core.somaxconn=32768 sysctl -w net.ipv4.tcp_max_syn_backlog=16384somaxconn决定了 accept 队列长度,默认值在很多发行版上只有 128,高并发下会出现连接被丢弃的情况。写进/etc/sysctl.conf持久化。
2.3 SELinux、防火墙、时区、字符集这四件事
这四个每一件单独拎出来都能让你排查半天。
SELinux:先getenforce看状态,如果是Enforcing,TongWeb 读写某些目录、绑定非标准端口时会被拦。临时用setenforce 0验证是不是它的问题,确认后要么改成Permissive,要么老老实实写 SELinux 策略。生产环境我倾向于配置策略而不是直接关,但过渡阶段先关掉确认问题来源也是常规做法。
防火墙:别以为开放了 Web 端口就完事了。TongWeb 至少涉及三个端口——业务端口(默认 8080)、管理控制台端口(通常是 9060,以启动日志为准)、关闭端口(默认 8005)。少开一个,就会出现"能访问应用但进不去控制台"的迷惑现象。
时区:用timedatectl确认。时区不对会让日志时间线错乱,更麻烦的是 License 校验依赖系统时间,时间偏差大了会直接判为无效。
字符集:locale看一下,如果输出里全是POSIX,那中文乱码几乎是必然的。生成 UTF-8 locale:
localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 localedef -c -f UTF-8 -i en_US en_US.UTF-8然后写进/etc/locale.conf,设LANG=zh_CN.UTF-8。
3. 安装包落地:账号、解压、权限的完整链路
环境捋顺了,接下来才是真正动手。这一段的操作看着简单,但每一步都有讲究,尤其是解压和权限这两块,做错了后面全是麻烦。
3.1 创建一个专用的运行账号
绝对不要用 root 跑 TongWeb。这是原则问题,不是习惯问题——中间件被拿下的例子不少,root 身份运行意味着整个系统都在风险敞口里。
groupadd -r tongweb useradd -r -g tongweb -m -d /home/tongweb -s /bin/bash tongweb passwd tongweb-r表示系统账号,-m创建家目录。建完之后把安装目录的所有权交给它:
mkdir -p /opt/tongweb chown -R tongweb:tongweb /opt/tongweb这里有个实操建议:安装目录别放在/root下面,也别放在/tmp里。/tmp在很多发行版上配置了noexec挂载选项,脚本根本执行不了,而且系统重启会被清理。
3.2 解压时中文文件名乱码的真正原因
这一条是热词里高频出现的"Linux 解压文件乱码",我专门说一下。
关键点在于:tar.gz 本身几乎不会乱码,zip 才是重灾区。原因是 tar 打包时保存的是文件名的原始字节流,解包时原样写回,跟编码无关。而 zip 格式的历史包袱比较重,Windows 上的压缩工具默认用 GBK 编码存文件名,Linux 解压时按 UTF-8 解释,就成了一堆问号或者方块。
如果你手上是 zip 包,有几种解法:
# unzip 6.0 及以上支持指定编码 unzip -O CP936 package.zip # 用 libarchive 的 bsdtar bsdtar -x --charset=GBK -f package.zip # 用 unar(macOS 移植过来的工具,中文字符处理很稳) unar -e GBK package.zip如果是 tar.gz 且确实有乱码(少数从特殊工具打包出来的包会这样),GNU tar 1.28 以上支持转换:
tar --iconv=GBK,UTF-8 -xzvf package.tar.gz上传之前先校验一下文件完整性,别传输中断了都不知道:
md5sum TongWeb7.0.x.x_x86_64.tar.gz # 和厂商给的校验值比对解压后进入目录,第一件事是ls -l bin/看看脚本都是什么名字。不同小版本的脚本命名可能不一样,我见过startserver.sh、tongweb.sh、startup.sh好几种,别照着某篇教程硬套。看启动脚本里的注释和变量定义,比看教程靠谱得多。
3.3 目录结构与权限规划
解压出来的目录大致长这样:
| 目录 | 作用 |
|---|---|
bin | 启停脚本、工具脚本 |
conf | 主配置文件、日志配置、License 相关 |
lib | 依赖 jar 包 |
deploy | 应用部署目录(热部署扫描) |
logs | 运行日志、访问日志 |
temp/work | 临时文件、JSP 编译产物 |
license | 授权文件存放位置(部分版本) |
权限上,bin下的.sh脚本需要可执行位:chmod +x bin/*.sh。logs和temp目录需要写权限,用tongweb账号运行就没这个问题。整体做一次chown -R tongweb:tongweb最省心。
4. License 与首次启动:最容易卡住的十分钟
TongWeb 是商业中间件,没有 License 起不来。这一步是新手最容易翻车的地方,因为失败时的表现往往是"脚本跑了一下就退出了",什么提示都没有。
4.1 License 文件的获取与放置位置
License 通常需要向厂商申请,申请时要提供服务器的 MAC 地址、IP、主机名等信息(各家规则略有差异,以厂商交付说明为准)。拿到的是一个license.dat或者类似名字的文件,需要放到指定目录。
放置位置不同版本有差异,常见的是conf目录或者独立的license目录。判断方法很简单:看启动日志。启动失败时把logs下的日志翻出来,一般会有明确提示,比如:
License file not found License is expired License does not match this machinedoes not match this machine这句意味着绑定的机器信息变了——换网卡、改主机名、迁移虚拟机都会导致。虚拟化环境下要特别注意,克隆虚拟机时 MAC 会变,License 会直接失效。
提示:拿到 License 后先备份一份到别的地方,并且记录申请时用的主机名和 IP。后面做迁移、扩容时能省很多沟通成本。
4.2 第一次启动要盯住什么
启动命令执行之后别扭头就走,前一两分钟的输出信息量最大。
su - tongweb cd /opt/tongweb/TongWeb7.0 ./bin/startserver.sh观察几个关键信息:JVM 有没有正常初始化、有没有绑定端口、License 校验有没有通过、最后有没有打印出控制台访问地址。同时另开一个窗口看端口:
ss -lntp | grep -E '8080|9060'端口起来了不代表应用能用,但端口都起不来那一定是路径、权限或者配置的问题。如果启动脚本一闪就退出,用下面的方式追一下:
bash -x ./bin/startserver.sh-x会把每条命令的执行过程打出来,卡在哪一行一目了然。
4.3 控制台登录和必须做的三件事
浏览器打开http://服务器IP:管理端口/console(管理端口以启动日志实际打印为准,常见为 9060)。默认凭据各版本不一样,老版本常见admin/admin123,新版本首次登录会强制改密,具体以安装包内说明或厂商交付文档为准。
进去之后先做三件事:
- 改掉默认密码,并且改成一个符合密码复杂度要求的强口令。
- 确认端口配置,把管理端口限制在内网访问,别暴露到公网。
- 检查 JVM 参数,默认的堆大小通常偏小,生产环境必须调整。
5. 按生产标准调优:从能跑到跑得稳
默认配置只保证"能启动",离"能扛住生产流量"还有距离。这一节讲几个关键参数的调整思路。
5.1 JVM 堆内存怎么定
堆大小的经验值:物理内存的 50% 到 60%,且不超过 32GB。为什么强调 32GB?因为 JVM 在堆超过约 32GB 后会失去指针压缩(Compressed OOPs)的优化,对象引用从 4 字节变成 8 字节,实际内存占用反而涨得比堆增长更快,得不偿失。
参数一般在bin下的启动脚本里,或者conf目录下某个vmoptions之类的外部配置文件里。典型配置:
-Xms8g -Xmx8g \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/opt/tongweb/dumps \ -Xloggc:/opt/tongweb/logs/gc.log \ -XX:+PrintGCDetails -XX:+PrintGCDateStamps-Xms和-Xmx设成一样大,避免运行期反复扩容缩容带来抖动。GC 策略上,JDK 8 环境如果堆不大(4GB 以内)用 Parallel 更省 CPU;堆大了就上 G1,停顿更可控。HeapDumpOnOutOfMemoryError一定要开,出问题的时候这份 dump 就是唯一的救命线索。
5.2 端口、连接器和线程池
端口配置在conf下的主配置文件里,或者通过控制台图形界面改。除了改端口本身,还要关注连接器参数:
| 参数 | 含义 | 建议值 |
|---|---|---|
maxThreads | 最大工作线程数 | 200~500,按业务 IO 特性调 |
minSpareThreads | 最小空闲线程 | maxThreads 的 10% |
acceptCount | 等待队列长度 | 与 somaxconn 匹配 |
connectionTimeout | 连接超时 | 20000~30000 毫秒 |
maxConnections | 最大连接数 | 视并发量而定 |
maxThreads不是越大越好。业务如果是 CPU 密集型,线程数超过核数太多只会加剧上下文切换;如果是 IO 密集型(大量数据库、外部接口调用),可以适当放大。我一般的做法是先在测试环境做一轮压测,从 200 起步,观察 CPU、线程栈和响应时间,再往上加。
5.3 字符编码的三层链路
中文乱码几乎每个项目都会碰到,而且往往是"应用代码没问题、数据库也没问题,就是显示乱码"。原因在于编码链路有三层,任何一层断了都会出问题:
- JVM 层:加
-Dfile.encoding=UTF-8,同时确认系统 locale 是 UTF-8。 - 容器层:连接器上配置 URI 编码为 UTF-8,POST 请求体编码同理。
- 数据库层:JDBC 连接串里加
characterEncoding=utf8,MySQL 还要注意表和列的字符集是不是utf8mb4。
排查的时候用二分法:先写一个最简单的 JSP 或者接口,直接输出中文字符串,看是哪一层开始乱的。如果是终端里cat日志乱码,那多半是 SSH 客户端编码没设对,跟服务器无关——这一条我自己就误判过好几次。
6. 应用部署的三种姿势,按场景挑
TongWeb7 部署应用的方式比较灵活,控制台、目录、脚本三条路各有适用场景。
6.1 控制台部署:适合首次和调试
登录管理控制台,找到应用部署入口,上传 war 包,指定上下文路径,提交。整个过程图形化,能直接看到部署日志和异常堆栈,调试阶段最省事。缺点是上传大包(几百兆)时容易超时,而且每次都要人工操作。
6.2 目录部署:适合自动化发布
把 war 包丢进deploy或autodeploy目录,容器按扫描周期自动检测并部署。这种方式特别适合配合 CI/CD 做自动化——构建流水线最后一步用scp把包推过去,剩下的交给容器。需要注意的是自动扫描有间隔,改完包不会立刻生效,急的时候还是得手动触发或者重启。
上下文路径的命名也值得一提。默认按 war 包文件名生成,中文名和带特殊字符的名字一定要改掉,否则 URL 里会出现百分号编码,前端调接口时容易出玄学问题。
6.3 脚本化部署:批量场景的标配
多实例环境下,人工操作迟早出错。我的做法是写一个发布脚本,固定流程:停服务 → 备份旧包 → 替换新包 → 启动服务 → 健康检查。健康检查这一步别省,简单的curl -I看返回码就够用:
#!/bin/bash set -e APP_DIR=/opt/tongweb/TongWeb7.0 WAR=/tmp/app.war BACKUP=/opt/backup/$(date +%Y%m%d%H%M%S) $APP_DIR/bin/stopserver.sh || true sleep 5 mkdir -p $BACKUP mv $APP_DIR/deploy/app.war $BACKUP/ 2>/dev/null || true cp $WAR $APP_DIR/deploy/app.war chown tongweb:tongweb $APP_DIR/deploy/app.war su - tongweb -c "$APP_DIR/bin/startserver.sh" for i in $(seq 1 30); do if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/app/health | grep -q 200; then echo "deploy success" exit 0 fi sleep 2 done echo "deploy failed, check logs" exit 1脚本里set -e让任何一步失败就中断,避免带着半成品状态继续跑。健康检查最多等 60 秒,超时就报错退出,让流水线标红。
7. 交给 systemd 托管,让它像系统服务一样活着
手动敲脚本启动的服务,服务器一重启就没了,运维半夜还得爬起来。用 systemd 托管是标准做法。
7.1 unit 文件怎么写
在/etc/systemd/system/tongweb.service里写:
[Unit] Description=TongWeb 7 Application Server After=network.target remote-fs.target Wants=network-online.target [Service] Type=forking User=tongweb Group=tongweb Environment=JAVA_HOME=/usr/local/jdk1.8.0_361 Environment=LANG=zh_CN.UTF-8 PIDFile=/opt/tongweb/TongWeb7.0/logs/tongweb.pid ExecStart=/opt/tongweb/TongWeb7.0/bin/startserver.sh ExecStop=/opt/tongweb/TongWeb7.0/bin/stopserver.sh ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=10 LimitNOFILE=65535 TimeoutStartSec=180 TimeoutStopSec=120 StandardOutput=append:/opt/tongweb/logs/systemd-out.log StandardError=append:/opt/tongweb/logs/systemd-err.log [Install] WantedBy=multi-user.target几个关键点解释一下:
Type=forking适用于脚本自己 fork 到后台的场景。如果启动脚本本身是前台阻塞的,改成simple。PIDFile必须和启动脚本实际写出的 pid 文件路径一致,路径错了 systemd 会认为启动失败,即使进程其实活着。LimitNOFILE在 unit 里再设一遍,防止 limits.conf 没生效。Restart=on-failure让进程意外挂掉时自动拉起,配合RestartSec避免疯狂重启打爆日志。TimeoutStartSec给足 180 秒,中间件启动本来就慢,默认 90 秒经常不够。
7.2 生效与验证
systemctl daemon-reload systemctl enable tongweb systemctl start tongweb systemctl status tongweb验证的时候别只看active (running),那只能说明主进程还在。真正的验证要访问一次业务接口,确认返回正常。另外重启一次机器,看服务是不是自动起来了。
8. 排障实战:那些让我熬到深夜的问题
前面讲的都是"按部就班",这一节讲"出了事怎么查"。我把这些年遇到的典型问题整理成一张表,后面挑几个展开说。
| 现象 | 常见原因 | 排查手段 |
|---|---|---|
| 启动脚本闪退 | JAVA_HOME 未设 / License 缺失 | bash -x跟踪,看 logs |
| 端口未监听 | 端口被占用 / 权限不足 | ss -lntp,lsof -i:8080 |
| 进程无故消失 | 被 OOM Killer 杀掉 | dmesg | tail -50 |
| 中文乱码 | locale / file.encoding / DB 字符集 | 三层二分排查 |
| 访问缓慢 | GC 频繁 / 线程池打满 | jstat、jstack |
| License 失效 | 主机信息变化 / 时间漂移 | 核对 MAC、timedatectl |
8.1 启动失败要按"从下往上"的顺序查
排查链路应该是这样的:先看进程有没有起来,再看端口有没有监听,再看日志有没有报错,最后才怀疑应用本身。顺序颠倒的话,容易在应用层瞎折腾半天,结果发现是端口被占了。
具体操作:
ps -ef | grep -i tongweb | grep -v grep ss -lntp | grep 8080 ls -lt logs/ | head # 看最新日志文件 tail -200 logs/server.log # 看有没有异常堆栈 dmesg | tail -50 # 看有没有 OOM Killer 记录dmesg这一条特别容易被忽略。进程"自己消失了"且日志里没有任何异常,十有八九是被内核的 OOM Killer 干掉了,dmesg里会有明确的Killed process记录。这种情况下光加堆内存没用,得先看是不是堆设得太大导致物理内存不够。
8.2 启动特别慢,可能是熵池不够
这是个比较冷门但确实存在的问题。JVM 初始化安全随机数生成器时会读/dev/random,在虚拟机上熵池积累很慢,会导致启动卡住几十秒甚至几分钟。表现是日志停在某个位置长时间不动。
验证方式:
cat /proc/sys/kernel/random/entropy_avail如果数值长期低于 200,那基本可以确定。解决办法是安装haveged或者rng-tools来补充熵源:
yum install -y haveged systemctl enable --now haveged另一个规避方式是在 JVM 参数里把随机数源指定为非阻塞的/dev/urandom。具体用哪种,看你的安全要求。
8.3 内存溢出和线程泄漏的现场取证
线上跑着跑着变慢,最典型的两类原因:内存回收不掉(内存泄漏)和线程堆积(线程泄漏)。这两个问题事后补救没用,必须在"慢"的时候抓现场。
内存方面:
jstat -gcutil <pid> 1000 10 # 每秒采样,看老年代使用率是否持续上涨 jmap -histo:live <pid> | head -30 # 看哪些对象占得最多 jmap -dump:live,format=b,file=/tmp/heap.hprof <pid> # 抓堆快照如果老年代使用率在 Full GC 之后还是不降,那就是有对象被长期持有,堆快照拿去用 MAT 一分析就能定位。
线程方面:
jstack <pid> > /tmp/thread.txt grep -c "java.lang.Thread.State" /tmp/thread.txt # 总线程数 grep "java.lang.Thread.State" /tmp/thread.txt | sort | uniq -c如果发现大量线程卡在WAITING且堆栈相同,那就是有地方在无限制地创建线程或者等待锁。隔几分钟抓两次对比,能看出哪些线程一直没动。
8.4 日志暴涨把磁盘撑爆
这条听起来低级,但我见过不止一次。访问日志加上调试日志,高峰期一天几十 GB,磁盘一满,整个服务全部挂掉,连带数据库连接池都报错。
防护措施有三层:一是日志级别调成INFO或WARN,别在生产开DEBUG;二是配置按天滚动加保留天数,比如保留 15 天;三是配 logrotate 兜底:
/opt/tongweb/TongWeb7.0/logs/*.log { daily rotate 15 missingok notifempty compress copytruncate }copytruncate这个选项要注意——它不移动文件而是清空,适用于进程一直持有文件句柄的场景。如果不加这个选项,日志会被轮转走但进程还在往老句柄里写,导致磁盘空间不释放,这是个很隐蔽的坑。另外单独给日志挂一个分区,即使写满了也不影响系统盘。
9. 前置代理和日常巡检的几条实战经验
服务跑起来只是开始,后面还有长期的运维。这一节讲几个我认为最有价值的实践。
9.1 Nginx 前置该怎么配
直接让 TongWeb 对外提供服务不是好主意,一来不好做统一入口和证书管理,二来静态资源交给 Nginx 处理效率高得多。典型配置:
upstream tongweb_backend { server 127.0.0.1:8080 weight=1 max_fails=3 fail_timeout=30s; keepalive 64; } server { listen 443 ssl; server_name app.example.com; client_max_body_size 100m; charset utf-8; location / { proxy_pass http://tongweb_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 30s; proxy_read_timeout 120s; proxy_send_timeout 120s; } location /static/ { root /data/www; expires 7d; } }几个容易忽略的点:proxy_set_header Connection ""配合keepalive才能复用长连接,少了这行等于白配;client_max_body_size默认 1MB,文件上传场景不加会直接返回 413;X-Forwarded-For不传的话,应用里拿到的客户端 IP 全是 127.0.0.1,做风控和审计时会很尴尬。
9.2 日常巡检该看什么
我习惯写一个巡检脚本每天跑一次,输出几个关键指标:进程是否存活、端口是否监听、JVM 堆使用率、GC 次数和耗时、日志里的 ERROR 数量、磁盘使用率。堆使用率持续超过 80% 或者 Full GC 次数陡增,就要提前介入,别等到真出故障。
PID=$(pgrep -f tongweb | head -1) echo "=== 进程 ===" [ -n "$PID" ] && echo "running: $PID" || echo "NOT RUNNING" echo "=== 堆使用 ===" jstat -gcutil $PID 2>/dev/null | tail -1 echo "=== 日志错误 ===" grep -c "ERROR" logs/server.log echo "=== 磁盘 ===" df -h /opt这类脚本的价值在于把"发现问题的时机"从用户投诉提前到故障发生前。我做过一个统计,加了日常巡检之后,紧急故障的数量下降了一大截,因为大部分问题在变成故障之前就已经被发现了。
9.3 我踩过的几个坑,直接告诉你结论
最后分享几个具体到操作层面的经验,都是真金白银换来的:
- 别在生产上直接用
kill -9。虽然大部分情况下能重启成功,但正在写的会话、日志缓冲可能丢失。先用正常停止脚本,给足 60 秒,实在不行再强杀。 - 配置文件改动前先备份,改动后用
diff核对。中间件的配置项动辄几百个,改错一个字符可能引发连锁反应,尤其是缩进敏感的 XML 和 YAML。 - JVM 参数不要在启动脚本里硬编码堆大小。把它抽到独立的环境变量文件里,测试环境和生产环境用同一套脚本,只换变量,减少"环境不一致"类问题。
- 扩容或者迁移之前,先把 License 的绑定规则搞清楚。有的绑定 MAC,有的绑定 IP,有的绑定主机名。搞清楚之后再动,比动完了发现起不来要省事得多。
- 别在业务高峰期做变更。这条听着像废话,但每年都有团队在流量最大的时候去调 JVM 参数。
部署这件事本身的复杂度其实不高,真正耗时间的是环境差异和那些文档没覆盖的细节。把这套流程跑通一次,写成脚本固化下来,后面再部署就是几分钟的事。我自己维护的那套发布脚本,从最开始手动敲十几个命令,到现在一条命令搞定,省下来的时间全用来处理真正的业务问题了。