本轮 服务器运维面试题库(带答案)
【面试题】1. Nginx的proxy_cache和fastcgi_cache有什么区别?分别在什么场景下使用?缓存命中率低怎么优化?
【标准答案】两种缓存机制的核心区别在于缓存对象不同:
proxy_cache:缓存上游服务器的响应,适用于反向代理场景。Nginx作为代理把后端返回的内容存到本地磁盘,后续相同请求直接从缓存返回,不透传到后端。配置关键:proxy_cache_path /data/cache levels=1:2 keys_zone=api:100m max_size=10g inactive=60m; 配合 proxy_cache api; proxy_cache_valid 200 302 10m;。
fastcgi_cache:缓存FastCGI上游(如PHP-FPM)的响应,专门针对fastcgi_pass场景。配置方式类似,只是指令前缀从proxy换成fastcgi:fastcgi_cache_path、fastcgi_cache、fastcgi_cache_valid。
使用场景:proxy_cache通用性强,任何proxy_pass上游都适用,Java/Python/Go后端都用它;fastcgi_cache只针对PHP-FPM这种FastCGI协议。
缓存命中率低优化:
- 缓存key设计不合理——确保
proxy_cache_key包含完整维度(如$scheme$host$request_uri),漏掉查询参数会导致缓存穿透 - inactive时间太短——热点数据还没过期就被LRU淘汰,根据业务调大inactive
- max_size太小——缓存空间不够,频繁淘汰,按磁盘空间合理设置
- 没有区分缓存时长——用
proxy_cache_valid对不同状态码设不同过期时间,200缓10m、404缓1m - 后端返回了禁缓存头——用
proxy_ignore_headers Set-Cookie Cache-Control;忽略上游的缓存控制
面试加分:提到用$upstream_cache_status日志变量统计命中率(HIT/MISS/EXPIRED),生产环境命中率低于80%就该优化。
【面试题】2. Tomcat部署WAR包时,热部署和冷部署有什么区别?生产环境怎么选?热部署导致PermGen/Metaspace OOM怎么解决?
【标准答案】冷部署:先停Tomcat再部署新WAR包,然后启动。优点是干净、不会有残留状态;缺点是有停机时间。
热部署:Tomcat运行中直接替换webapps目录下的WAR包,Tomcat自动检测变化并重新加载应用。通过<Context reloadable="true">或监听WAR文件变化实现。优点是不停机;缺点是每次重新加载会产生新的ClassLoader,旧的ClassLoader和它加载的类无法回收。
PermGen/Metaspace OOM根因:每次热部署创建新的WebappClassLoader,旧的Class实例由于被GC Root引用(如线程、静态字段、JNI)无法回收,类元数据堆积在Metaspace中,反复部署几次就OOM。
解决方案:
- 生产环境不用热部署——用蓝绿发布或滚动更新,新版本起新实例切流量
- 必须用热部署时——调大Metaspace:
-XX:MaxMetaspaceSize=512m,加-XX:+CMSClassUnloadingEnabled(JDK7),JDK8+默认支持类卸载 - 排查泄漏——用
jstat -gcutil <pid>观察Metaspace增长趋势,MAT分析ClassLoader引用链,找出谁持有旧ClassLoader - 用
-XX:+ClassUnloadingWithConcurrentMark(G1收集器)加速类卸载
面试要点:热部署只适合开发调试,生产一定要用滚动更新。Spring Boot内嵌Tomcat不存在这个问题,因为它不走Tomcat的WebappClassLoader机制。
【面试题】3. Linux系统中文件描述符(fd)耗尽时有什么表现?如何排查和解决?
【标准答案】典型表现:应用日志报Too many open files;Nginx报accept() failed (24: Too many open files);新连接无法建立,ssh登不上去;tail -f等命令也打不开文件。
排查步骤:
- 确认系统级限制:
cat /proc/sys/fs/file-nr,三个数字分别是已分配fd数、未使用fd数、最大fd数 - 查看进程级限制:
cat /proc/<pid>/limits | grep "open files" - 找到fd占用最多的进程:
ls /proc/*/fd 2>/dev/null | wc -l逐个统计,或用lsof -n | awk '{print $2}' | sort | uniq -c | sort -rn | head - 看具体fd类型:
lsof -p <pid> | wc -l,区分是文件fd、socket fd还是pipe fd
解决方案:
- 临时修改:
prlimit --pid <pid> --nofile=65535:65535在线调整进程限制 - 永久修改:
/etc/security/limits.conf添加* soft nofile 65535和* hard nofile 65535 - 系统级:
sysctl -w fs.file-max=2000000 - Nginx特殊:配置
worker_rlimit_nofile 65535;
根因分析:fd耗尽不是光调大限制就完事,要查为什么泄漏——最常见的是短连接没关闭(代码里socket/文件流没close)、连接池配置不当、日志轮转配置错误导致fd堆积。
面试要点:调大限制是治标,找到泄漏根因是治本。lsof是排查利器,生产中配合监控fd使用率趋势告警。
【面试题】4. Nginx的rewrite规则中,last、break、redirect、permanent四个flag有什么区别?生产中容易踩什么坑?
【标准答案】四个flag决定了rewrite之后的行为:
last:停止当前server/location中的rewrite规则执行,重新发起location匹配。相当于"重新走一遍location路由"。适合重写后需要匹配新location的场景,如URL伪静态化。
break:停止当前server/location中的rewrite规则执行,不再重新匹配location,直接在当前location中继续执行后续指令。适合重写后不需要换location的场景,如参数清洗。
redirect:返回302临时重定向,浏览器地址栏会变。适合临时URL调整。
permanent:返回301永久重定向,浏览器会缓存。适合域名迁移、HTTP转HTTPS。
常见坑:
- last和break搞混——last会重新匹配location,可能进入死循环(Nginx限制最多10次重写,超了返回500);break不会重匹配,更安全
- redirect和permanent对SEO影响不同——301永久重定向搜索引擎会转移权重,302不会;URL迁移必须用301
- last导致循环重写——
rewrite ^/test(.*)$ /test$1 last;会无限循环,用break替代或加条件判断 - rewrite放在server级还是location级效果不同——server级的rewrite先执行,location级的后执行
面试加分:HTTP转HTTPS用return 301 https://$host$request_uri;比rewrite更高效,不需要正则匹配。
【面试题】5. Spring Boot内嵌Tomcat如何调优?和独立部署Tomcat的调优有什么区别?
【标准答案】Spring Boot内嵌Tomcat调优分三方面:
线程池调优(application.yml):
server:
tomcat:
threads:
max: 500 # 最大工作线程,默认200
min-spare: 20 # 最小空闲线程
max-connections: 10000 # 最大连接数,默认8192
accept-count: 200 # 等待队列长度,默认100
connection-timeout: 5000 # 连接超时msJVM调优(启动脚本):
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-jar app.jar关键区别:
- 内嵌Tomcat无法切换APR模式(除非额外引入tomcat-native依赖),默认NIO
- 内嵌Tomcat的日志走SLF4J,不生成catalina.out,需要用logback配置
- 内嵌Tomcat不支持Manager App和热部署,每次更新需要重启应用
- 内嵌Tomcat的Connector配置通过Spring Boot属性,不直接改server.xml
- 内嵌Tomcat默认不生成访问日志,需配置
server.tomcat.accesslog.enabled=true
生产建议:
- 线程数不是越多越好,CPU核数×200是经验上限,再多上下文切换反而更慢
- 前面挂Nginx做SSL卸载和静态资源,内嵌Tomcat只处理业务逻辑
- 用actuator的
/metrics端点监控Tomcat线程池使用率,线程使用率持续>80%就该扩容
面试要点:Spring Boot内嵌Tomcat的核心优势是"零配置运维",但也意味着灵活度不如独立Tomcat。大规模部署建议独立Tomcat+统一配置管理。
【面试题】6. Linux系统内核参数net.core.somaxconn和net.ipv4.tcp_max_syn_backlog分别控制什么?高并发场景怎么调?
【标准答案】两个参数控制的是TCP连接排队的地方不同:
net.core.somaxconn:控制全连接队列(Accept Queue)的最大长度。三次握手完成后,连接进入这个队列等待应用accept()。默认值128,高并发远远不够。如果队列满了,新的已完成握手的连接会被丢弃,客户端看到Connection Reset。
net.ipv4.tcp_max_syn_backlog:控制半连接队列(SYN Queue)的最大长度。收到SYN但还没完成三次握手的连接排在这里。默认1024。SYN Flood攻击时这个队列会满,正常高并发也可能不够。
高并发调优:
# 系统级调大
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# 永久生效写入/etc/sysctl.conf
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535但光调内核参数不够,应用层也要配合:
- Nginx的
listen 80 backlog=65535;要同步调大,否则Nginx默认511还是瓶颈 - Tomcat的
acceptCount要调大,默认100 - Java应用
ServerSocket构造函数的backlog参数要传值
排查队列溢出:
netstat -s | grep "overflowed"看全连接队列溢出次数netstat -s | grep "SYNs to LISTEN"看半连接队列溢出次数- 如果持续增长,说明队列不够,需要调大
面试要点:somaxconn是三次握手后排队,tcp_max_syn_backlog是三次握手中排队。两个都要调,还要配合应用层的backlog参数。SYN Flood防护要开tcp_syncookies=1。
【面试题】7. WAF(Web应用防火墙)的工作原理是什么?Nginx层面如何实现基本的WAF功能?
【标准答案】WAF在应用层(OSI第七层)对HTTP请求进行检测和过滤,防护SQL注入、XSS、命令注入、文件包含等Web攻击。
工作原理:
- 请求到达WAF → 提取URL、参数、Header、Body
- 规则引擎匹配(正则、语义分析、行为分析)
- 匹配到攻击规则 → 拦截/记录/挑战
- 正常请求 → 放行到后端
Nginx实现基础WAF:用开源模块ngx_lua_waf或ModSecurity。
ModSecurity + OWASP核心规则集(CRS)方案:
# 加载ModSecurity模块
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
# OWASP CRS规则
Include /etc/nginx/modsec/crs-setup.conf
Include /etc/nginx/modsec/rules/*.confOpenResty + Lua轻量方案:
# 拦截SQL注入
location / {
access_by_lua_block {
local uri = ngx.var.request_uri
if ngx.re.find(uri, "(union|select|insert|drop|delete|update).*from", "ijo") then
ngx.exit(403)
end
-- XSS检测
if ngx.re.find(ngx.var.args, "<script|javascript:|onerror", "ijo") then
ngx.exit(403)
end
}
proxy_pass http://backend;
}生产建议:
- 中小规模用Nginx+Lua自研规则够用,灵活可控
- 大规模用云WAF(阿里云WAF/腾讯云WAF),规则维护交给云厂商
- 规则要分模式:观察模式(只记录不拦截)→ 混合模式 → 拦截模式,逐步收紧
- 误杀率是核心指标,上线前必须做回归测试
面试加分:提到语义分析WAF比正则WAF更准确(如长亭科技的语义分析引擎),传统正则WAF容易被编码绕过(双重URL编码、Unicode编码等)。
【面试题】8. Docker的网络模式有哪些?容器间如何通信?生产环境推荐哪种?
【标准答案】Docker有四种主要网络模式:
bridge(默认):容器通过虚拟网桥docker0通信,每个容器分配独立IP,NAT方式访问外部。同一bridge下的容器可以通过IP互通,但不能用容器名解析。
host:容器直接使用宿主机网络栈,没有网络隔离,性能最好。缺点是端口冲突风险高,一个容器占了80端口其他就不能用。
none:容器没有网络,只有lo接口。适合安全敏感的计算任务。
overlay:跨主机容器通信,Swarm集群默认用这个。底层VXLAN隧道,支持加密。
容器间通信方案:
- 同一自定义bridge网络——
docker network create mynet,容器加入同一网络后可用容器名互访,DNS自动解析 --link(已废弃)——老方案,不推荐- 外部服务发现——Consul/Etcd注册发现
生产推荐:
- 单机多容器:自定义bridge网络,服务名访问
- 多机集群:Kubernetes网络方案(Calico/Flannel),不用原生Docker网络
- 性能极致场景:host模式,但要做好端口管理
- 微服务架构:K8s Service + ClusterIP,天然服务发现和负载均衡
docker-compose示例:
services:
web:
image: nginx
networks:
- frontend
api:
image: myapp
networks:
- frontend
- backend
db:
image: mysql
networks:
- backend
networks:
frontend:
backend:
internal: true # 外部不可访问面试要点:bridge最通用,host性能最好,overlay跨主机,K8s环境用CNI插件。核心是理解网络隔离和通信需求再做选择。
【面试题】9. CDN加速的原理是什么?Nginx如何配合CDN做静态资源加速?缓存命中率和回源率怎么优化?
【标准答案】CDN原理:将内容缓存到离用户最近的边缘节点,用户请求时DNS智能解析到最近的CDN节点,直接返回缓存内容,减少回源延迟。
工作流程:用户请求 → DNS解析到最近CDN节点 → CDN有缓存直接返回 → CDN无缓存回源站获取 → 缓存到CDN并返回用户。
Nginx配合CDN:
- 静态资源独立域名:
static.example.com,CDN回源到Nginx - Nginx配置缓存头:
location ~* \.(css|js|jpg|png|gif|ico|woff2)$ {
root /var/www/static;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
add_header X-Cache-Status $upstream_cache_status;
}- Nginx做回源保护:限流防止CDN被攻击时大量回源
limit_req_zone $http_x_forwarded_for zone=origin:10m rate=1000r/s;
location / {
limit_req zone=origin burst=5000 nodelay;
proxy_pass http://backend;
}缓存命中率优化:
- 合理设置Cache-Control——静态资源长期缓存+文件名加hash(
app.a3b2c1.js),更新改hash不改路径 - 分离动静域名——动态请求不走CDN,避免CDN缓存不命中
- 设置合理的回源条件——
Vary头别乱加,Vary: Accept-Encoding会导致同一资源缓存多份 - URL规范化——
/path和/path?和/path/在CDN是不同缓存key,统一规范 - 预热——新内容上线主动推送缓存到CDN节点,避免首批用户回源
监控指标:CDN命中率>95%是目标,低于90%要排查。回源率高→检查缓存策略、URL规范、过期时间。
面试加分:提到CDN回源时Nginx通过X-Forwarded-For和X-Real-IP获取真实客户端IP,CDN节点IP不要被误判为攻击源。大型站点用多CDN(双厂商)容灾。
【面试题】10. 线上服务器某个Java应用持续Full GC,CPU和内存都异常,如何快速定位并恢复服务?
【标准答案】Full GC频繁的应急处理分"先止血、再查因"两步走:
第一步:紧急止血(1-2分钟)
- 扩容:如果是集群,把流量切到健康节点,隔离问题实例
- 限流:Nginx限流或网关降级非核心接口,减轻压力
- 如果是内存泄漏导致的FGC,不要重启——重启后现场没了,下次还会复现。先dump再处理
第二步:现场取证
# 1. 确认FGC频率
jstat -gcutil <pid> 1000 10
# 观察 FGC 列数字是否快速增长,FGCT 是 FGC 总耗时
# 2. 导出堆内存
jmap -dump:format=b,file=/tmp/heap_$(date +%s).hprof <pid>
# 或配置了 -XX:+HeapDumpOnOutOfMemoryError 会自动生成
# 3. 查看GC日志(启动时需配置 -Xlog:gc*)第三步:分析根因 用MAT或JProfiler打开hprof文件:
- Dominator Tree找到占用最大的对象
- Leak Suspects自动分析可疑泄漏点
- 常见原因:
- 大对象直接进老年代(超过PretenureSizeThreshold)
- 内存泄漏:集合类只增不删、ThreadLocal不remove、缓存无上限
- Metaspace满:动态代理类加载过多(CGLIB)
- System.gc()被显式调用
第四步:验证修复
- 修复代码上线前,用压测模拟场景验证
- 配置GC日志+Prometheus JMX监控持续观察
JVM参数优化防复发:
# G1收集器,控制GC停顿
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
# 大对象阈值
-XX:G1HeapRegionSize=8m
# 自动dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/面试要点:FGC频繁的本质是老年代空间不够——要么泄漏(对象该死没死),要么产生太快(大对象/短期对象太多)。必须先dump再重启,保留现场是排查关键。