Skip to content

本轮 服务器运维面试题库(带答案) ​

【面试题】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协议。

缓存命中率低优化:

  1. 缓存key设计不合理——确保proxy_cache_key包含完整维度(如$scheme$host$request_uri),漏掉查询参数会导致缓存穿透
  2. inactive时间太短——热点数据还没过期就被LRU淘汰,根据业务调大inactive
  3. max_size太小——缓存空间不够,频繁淘汰,按磁盘空间合理设置
  4. 没有区分缓存时长——用proxy_cache_valid对不同状态码设不同过期时间,200缓10m、404缓1m
  5. 后端返回了禁缓存头——用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。

解决方案:

  1. 生产环境不用热部署——用蓝绿发布或滚动更新,新版本起新实例切流量
  2. 必须用热部署时——调大Metaspace:-XX:MaxMetaspaceSize=512m,加-XX:+CMSClassUnloadingEnabled(JDK7),JDK8+默认支持类卸载
  3. 排查泄漏——用jstat -gcutil <pid>观察Metaspace增长趋势,MAT分析ClassLoader引用链,找出谁持有旧ClassLoader
  4. 用-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等命令也打不开文件。

排查步骤:

  1. 确认系统级限制:cat /proc/sys/fs/file-nr,三个数字分别是已分配fd数、未使用fd数、最大fd数
  2. 查看进程级限制:cat /proc/<pid>/limits | grep "open files"
  3. 找到fd占用最多的进程:ls /proc/*/fd 2>/dev/null | wc -l逐个统计,或用lsof -n | awk '{print $2}' | sort | uniq -c | sort -rn | head
  4. 看具体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。

常见坑:

  1. last和break搞混——last会重新匹配location,可能进入死循环(Nginx限制最多10次重写,超了返回500);break不会重匹配,更安全
  2. redirect和permanent对SEO影响不同——301永久重定向搜索引擎会转移权重,302不会;URL迁移必须用301
  3. last导致循环重写——rewrite ^/test(.*)$ /test$1 last;会无限循环,用break替代或加条件判断
  4. 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):

yaml
server:
  tomcat:
    threads:
      max: 500          # 最大工作线程,默认200
      min-spare: 20     # 最小空闲线程
    max-connections: 10000  # 最大连接数,默认8192
    accept-count: 200       # 等待队列长度,默认100
    connection-timeout: 5000  # 连接超时ms

JVM调优(启动脚本):

bash
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攻击时这个队列会满,正常高并发也可能不够。

高并发调优:

bash
# 系统级调大
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攻击。

工作原理:

  1. 请求到达WAF → 提取URL、参数、Header、Body
  2. 规则引擎匹配(正则、语义分析、行为分析)
  3. 匹配到攻击规则 → 拦截/记录/挑战
  4. 正常请求 → 放行到后端

Nginx实现基础WAF:用开源模块ngx_lua_waf或ModSecurity。

ModSecurity + OWASP核心规则集(CRS)方案:

nginx
# 加载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/*.conf

OpenResty + Lua轻量方案:

nginx
# 拦截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隧道,支持加密。

容器间通信方案:

  1. 同一自定义bridge网络——docker network create mynet,容器加入同一网络后可用容器名互访,DNS自动解析
  2. --link(已废弃)——老方案,不推荐
  3. 外部服务发现——Consul/Etcd注册发现

生产推荐:

  • 单机多容器:自定义bridge网络,服务名访问
  • 多机集群:Kubernetes网络方案(Calico/Flannel),不用原生Docker网络
  • 性能极致场景:host模式,但要做好端口管理
  • 微服务架构:K8s Service + ClusterIP,天然服务发现和负载均衡

docker-compose示例:

yaml
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:

  1. 静态资源独立域名:static.example.com,CDN回源到Nginx
  2. 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;
}
  1. Nginx做回源保护:限流防止CDN被攻击时大量回源
nginx
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;
}

缓存命中率优化:

  1. 合理设置Cache-Control——静态资源长期缓存+文件名加hash(app.a3b2c1.js),更新改hash不改路径
  2. 分离动静域名——动态请求不走CDN,避免CDN缓存不命中
  3. 设置合理的回源条件——Vary头别乱加,Vary: Accept-Encoding会导致同一资源缓存多份
  4. URL规范化——/path和/path?和/path/在CDN是不同缓存key,统一规范
  5. 预热——新内容上线主动推送缓存到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分钟)

  1. 扩容:如果是集群,把流量切到健康节点,隔离问题实例
  2. 限流:Nginx限流或网关降级非核心接口,减轻压力
  3. 如果是内存泄漏导致的FGC,不要重启——重启后现场没了,下次还会复现。先dump再处理

第二步:现场取证

bash
# 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文件:

  1. Dominator Tree找到占用最大的对象
  2. Leak Suspects自动分析可疑泄漏点
  3. 常见原因:
    • 大对象直接进老年代(超过PretenureSizeThreshold)
    • 内存泄漏:集合类只增不删、ThreadLocal不remove、缓存无上限
    • Metaspace满:动态代理类加载过多(CGLIB)
    • System.gc()被显式调用

第四步:验证修复

  • 修复代码上线前,用压测模拟场景验证
  • 配置GC日志+Prometheus JMX监控持续观察

JVM参数优化防复发:

bash
# G1收集器,控制GC停顿
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
# 大对象阈值
-XX:G1HeapRegionSize=8m
# 自动dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/

面试要点:FGC频繁的本质是老年代空间不够——要么泄漏(对象该死没死),要么产生太快(大对象/短期对象太多)。必须先dump再重启,保留现场是排查关键。