第四章:应用参数配置
说实话,应用参数配置这块,我踩过的坑比吃过的盐还多。你想想看,一个线上服务跑得好好的,突然因为某个参数没配对就挂了,那感觉真是...嗯,咱们今天就把 Spring Boot、Tomcat、Nginx、Node.js 这四个主流应用的启动参数讲透。
核心观点:启动参数不是越多越好,关键是理解每个参数背后的原理。我见过太多人把网上搜来的参数直接复制粘贴,结果出了问题都不知道怎么排查。
4.1 Spring Boot 启动参数
Spring Boot 的启动参数,说白了就是 JVM 参数 + 应用配置参数的组合。我个人习惯把它们分成三类:内存相关、调试相关、应用配置。
4.1.1 内存参数配置
先看一个我在生产环境常用的配置模板:
java -Xms2g -Xmx2g \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=256m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-jar app.jar
这里有几个关键点:
- -Xms 和 -Xmx 设成一样:避免 JVM 动态调整堆大小带来的性能抖动。我曾经在一个大促项目里没注意这个,结果流量高峰时 JVM 频繁扩容,GC 时间暴涨,差点出事故。
- MetaspaceSize 要显式指定:默认值太小,容易触发 Full GC。我记得有一次线上服务每隔半小时就 Full GC 一次,查了半天才发现是元空间扩容导致的。
- G1GC 是标配:除非你的堆小于 4GB,否则别用 CMS 了。G1 的停顿时间可控,适合大多数场景。
小技巧:启动时加个 -XX:+PrintGCDetails,先跑几天看看 GC 日志,再调整参数。别一上来就抄网上的配置。
4.1.2 应用配置参数
Spring Boot 支持多种方式传参,我推荐用 -- 前缀的方式:
java -jar app.jar \
--server.port=8080 \
--spring.profiles.active=prod \
--spring.datasource.url=jdbc:mysql://... \
--logging.level.com.example=DEBUG
为什么要用 --?因为它会覆盖 application.yml 里的配置,而且优先级最高。我在做灰度发布时经常用这个特性,临时改个端口或者数据库连接,不用重新打包。
注意:敏感信息(密码、密钥)别直接写在命令行里。用环境变量或者配置中心,比如 SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}。我曾经见过有人把数据库密码直接写在启动脚本里,结果代码库泄露后整个库都被拖走了。
4.2 Tomcat 启动参数
Tomcat 的启动参数主要分两块:JVM 参数(通过 CATALINA_OPTS)和 Tomcat 自身配置(通过 server.xml)。
4.2.1 JVM 参数配置
在 catalina.sh 或 setenv.sh 里设置:
export CATALINA_OPTS="-Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-Djava.security.egd=file:/dev/./urandom"
最后那个 -Djava.security.egd 参数,很多人会忽略。Tomcat 启动时如果用到 SecureRandom,默认会从 /dev/random 读取,这个设备在熵池不足时会阻塞。我曾经在测试环境等 Tomcat 启动等了 5 分钟,查了半天才发现是这个原因。改成 /dev/./urandom 后,启动时间从 5 分钟降到了 10 秒。
4.2.2 连接器参数
在 server.xml 里配置:
<Connector port="8080"
protocol="HTTP/1.1"
maxThreads="200"
minSpareThreads="25"
acceptCount="100"
connectionTimeout="20000"
maxConnections="10000" />
这些参数怎么配?我一般遵循这个原则:
- maxThreads:不是越大越好。我见过有人设成 1000,结果 CPU 上下文切换开销直接占满。一般 200-500 就够用了。
- acceptCount:排队队列长度。如果请求量突然暴增,这个值能起到缓冲作用。但别设太大,否则客户端会超时重试,反而加重服务器负担。
- maxConnections:Tomcat 能同时处理的最大连接数。超过这个值的连接会被拒绝。我建议设成 maxThreads 的 2-3 倍。
经验之谈:我曾经接手过一个项目,Tomcat 的 maxThreads 设成了 50,但业务高峰期并发请求有 200 多。结果 150 个请求在排队,平均响应时间从 200ms 飙升到 5 秒。调大 maxThreads 后,问题立刻解决了。
4.3 Nginx 启动参数
Nginx 的启动参数主要在 nginx.conf 里。我把它分成核心参数和性能参数两类。
4.3.1 核心参数
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 10240;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
}
这里有几个关键点:
- worker_processes auto:自动匹配 CPU 核心数。我建议别手动指定,除非你的机器有特殊的 NUMA 架构。
- worker_connections:每个 worker 能处理的最大连接数。总连接数 = worker_processes × worker_connections。我一般按 10240 起步,根据内存和业务量调整。
- use epoll:Linux 下最高效的事件驱动模型。别用 select 或 poll,性能差一个数量级。
避坑指南:我曾经把 worker_rlimit_nofile 设得太小,结果 Nginx 报 "too many open files" 错误。这个值要大于 worker_connections × 2,因为每个连接至少占用两个文件描述符。
4.3.2 性能调优参数
http {
client_body_buffer_size 128k;
client_max_body_size 10m;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
gzip on;
gzip_min_length 1k;
gzip_comp_level 2;
gzip_types text/plain application/javascript text/css;
}
gzip 压缩这块,我建议压缩级别别超过 2。级别高了 CPU 开销大,但压缩率提升有限。我曾经测试过,级别 2 和级别 9 的压缩率只差 5%,但 CPU 使用率差了 3 倍。
4.4 Node.js 启动参数
Node.js 的启动参数主要通过 NODE_OPTIONS 环境变量或者命令行参数传入。
4.4.1 内存参数
NODE_OPTIONS="--max-old-space-size=2048 --max-semi-space-size=512" node app.js
Node.js 的 V8 引擎内存管理跟 JVM 不太一样:
- --max-old-space-size:老生代内存上限。我一般设成 2GB-4GB,具体看业务。超过这个值会触发垃圾回收,如果回收后还超,进程就挂了。
- --max-semi-space-size:新生代内存。这个值影响 GC 频率。设小了 GC 频繁,设大了单次 GC 时间长。我一般用 512MB 起步。
注意:Node.js 默认内存限制是 512MB(32位)或 1GB(64位)。如果你不做任何配置,内存密集型应用很容易 OOM。我曾经有个数据处理服务,处理 100MB 的 JSON 文件就挂了,加上 --max-old-space-size=4096 后稳如老狗。
4.4.2 调试与性能参数
NODE_OPTIONS="--inspect=0.0.0.0:9229 --trace-warnings --pending-deprecation" node app.js
这几个参数我经常用:
- --inspect:开启调试端口。生产环境别开,除非你确定防火墙规则没问题。
- --trace-warnings:打印警告的堆栈信息。排查问题时特别有用。
- --pending-deprecation:显示即将废弃的 API 警告。我建议在测试环境开启,提前发现兼容性问题。
4.4.3 集群模式参数
node --experimental-cluster-reload cluster.js
Node.js 是单线程的,要充分利用多核 CPU,得用 cluster 模块。我一般这样启动:
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// 工作进程代码
}
这里要注意,每个 worker 进程都会占用一份内存。如果你的应用内存占用大,worker 数量别超过 CPU 核心数,否则内存会爆。
总结一下:应用参数配置没有银弹。每个参数都要结合你的业务场景、硬件配置、流量模型来调整。我的习惯是:先用默认参数跑起来,然后通过监控数据逐步调优。别一上来就搞一堆参数,出了问题都不知道是哪个引起的。