Skip to content

第4章 Nginx 与中间件交付(中间件部署 · SSL · 前后端联调) ​

对应简历:技能第7条「熟练掌握 Nginx、Tomcat 等中间件部署与 SSL 证书配置,具备前后端应用联调上线经验」;技能第6条「熟悉 Redis、RocketMQ、Nacos 等中间件部署」;项目一 P1-4「参与 BCS 边缘自治系统与数币智能合约管理平台部署联调(毕昇 JDK、Redis、Nginx 编译部署与前后端联调)」;项目一 P1-2「通过 Nginx + Keepalived(VIP 漂移 + 健康检查)升级为多 master 高可用」。 实施版口径:本章保留较全——中间件部署、SSL 证书配置、前后端联调、try_files/proxy_pass 实施案例是核心;Nginx 内部原理只保留实施工程师要讲的深度。K8s apiserver 入口的 stream/Keepalived 部分与第 6 章衔接,本章保留入口配置视角。 使用提醒:4.5/4.6(try_files、proxy_pass 尾斜杠)是我在数币平台真实踩过的联调案例,实施岗面试命中率极高,两个模板要能默写并讲出故事。

4.1 Nginx 架构与 master/worker 模型 【必背】 ​

标准回答:Nginx 是高性能 HTTP 服务器和反向代理,在实施交付里它通常承担四个角色:静态资源服务器、反向代理、负载均衡、SSL 终端。它高并发的根本是"多进程 + 事件驱动 + epoll 异步非阻塞":master 进程是管理者——读配置、绑端口、创建回收 worker,自己不处理业务请求;支持平滑重载,nginx -s reload 时 master 先校验新配置、起新 worker,给老 worker 发信号让它们处理完存量连接再退,全程服务不断——我在客户现场改前端配置全是这么做的,用户零感知。worker 是干活的,数量通常等于 CPU 核数(worker_processes auto),worker 之间独立无锁,一个崩了 master 立刻补,稳定性好。epoll 是 Linux 的 IO 多路复用机制:一个 worker 单线程同时监听成千上万连接,谁就绪处理谁,不为每个连接开线程,几万并发只占 MB 级内存——对比 Apache 每连接一线程的模型,资源差一个量级。这套架构给我最直接的交付体感:国产化 ARM 环境源码编译的 Nginx 1.25.1 跑在统信 UOS 上,reload 改配置业务无感、worker 偶发崩溃自动补起——架构图我不是背的,是天天在客户现场看着它这么表现的。

追问链:

  • 追问1:worker 数为什么等于核数? → 答:worker 是单线程事件驱动的,一个 worker 最多打满一个核;等于核数把多核吃满又不浪费,多了增加切换开销还争抢连接。grep processor /proc/cpuinfo | wc -l 对核数、ps -fC nginx 对 worker 数,我在部署核对时就这么验。
  • 迭问2(深挖):epoll 和 select 的区别? → 答:核心差异在"谁来遍历连接"。select 每次把整个连接集合拷进内核逐个检查,O(n),连接多时 CPU 全耗在无效遍历;epoll 在内核里用红黑树管连接,事件到达由回调挂进就绪链表,进程只从就绪链表取,接近 O(1)。我理解到原理层,生产上按"epoll + worker_connections"参数组合做调优——把原理和调优动作对应着说最稳。

4.2 源码编译部署:ARM 一次配齐 【必背】 ​

标准回答:源码编译部署 Nginx 是我在银行项目的真实动作——国产化环境没有可用的软件包且需要 HTTPS,必须编译装。流程五步:第一步环境依赖——gcc、openssl-devel、pcre-devel、zlib-devel 装齐,ARM 仓库里依赖不一定全,先确认再 configure,否则 configure 直接报错,这是我在统信 UOS 上真踩过并写进部署指南的;第二步 configure 带全模块——./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --with-stream;第三步 make 加 make install;第四步写 systemd 服务单元托管(第 1 章 1.5 的模板),配 nginx -t 校验加 reload 的标准变更动作;第五步验证——nginx -V 确认编译参数、ss 查端口、curl 打通。这里有个实施意识必须讲:编译要"一次配齐"——ssl、stub_status、stream 该带的模块一次性全带上,因为二次补编译要停服,而交付后再发现少模块就是变更事故。为什么 stream 必须带?因为后面 apiserver 四层代理要用(4.9);为什么 stub_status 要带?因为监控和验收要用状态页。模块清单按"未来要用什么"倒推,这是我给客户交付编译类软件的固定思路。

追问链:

  • 迭问1:怎么确认现有 Nginx 支持某模块? → 答:nginx -V 2>&1 | grep 模块名 查编译参数。没有就重新 configure 带参数再编译替换——所以才有"一次配齐"的原则。这个问题在客户环境排查"为什么 stream 配置报错"时第一步就用,答案几乎总是当年编译时没带。
  • 迭问2:编译安装和 yum 安装怎么选? → 答:内网信创环境经常没有现成包,必须编译;有包的环境,标准部署用包管理器装(好升级、好管理),需要定制模块或非默认路径时才编译。我的实际经历:银行项目被环境倒逼走了编译路线,反而把模块规划和路径规划练成了主动能力——编译版我统一 /usr/local/nginx 前缀,目录结构写进手册,客户运维照手册管。

实操模板(编译命令链,可背):

bash
# 依赖(ARM 环境先确认齐全)
yum install -y gcc openssl-devel pcre-devel zlib-devel
# 一次配齐:ssl + 状态页 + 四层流
./configure --prefix=/usr/local/nginx \
  --with-http_ssl_module --with-http_stub_status_module --with-stream
make && make install
nginx -V 2>&1 | tr ' ' '\n' | grep stream     # 验证编译参数

4.3 正向/反向代理与虚拟主机 【理解】 ​

标准回答:一句话区分:正向代理代理客户端(替客户端访问外部,目标服务器看到的是代理 IP),反向代理代理服务端(客户端无感,直接访问代理的域名或 VIP,真实后端被隐藏保护)。实施交付里我们配的几乎全是反向代理,它的典型价值四条:负载均衡分发、SSL 终端(证书挂代理层,后端免加解密)、静态缓存、安全隔离(后端不暴露公网)。配套必须会的是真实 IP 透传:代理层配 proxy_set_header X-Real-IP $remote_addr 和 X-Forwarded-For $proxy_add_x_forwarded_for 两个头,后端应用和日志才能拿到真实客户端 IP——我在数币平台 /prod-api/ 反代里配了这两个头,后端 jar 的访问日志才记录了真实来源;多级代理时取 XFF 的第一个值,这是我提示开发注意的细节。虚拟主机解决"一台 Nginx 跑多个站点":一个 server 块一个站点,基于域名(server_name 区分,靠 Host 头,最常用)、基于端口、基于 IP 三种。匹配顺序:精确、前缀通配、正则、都不中落 default_server——这个机制也是共享主机和 Ingress 的基本原理,讲 K8s 时我会回指。

追问链:

  • 迭问1:后端日志里全是代理 IP 怎么办? → 答:两步:代理层配透传头;后端日志格式改用 $http_x_forwarded_for 取透传值。这个细节是"实际配过反代"的标志——没配过的人交付完会留下一个"后端日志全是 Nginx IP、客户审计查不到访问来源"的隐患,我在联调验收时专门核对这一项。

4.4 location 匹配与 root/alias 【必背】 ​

标准回答:这两组规则是 Nginx 配置类手写题的最高频坑,我都真实踩过。location 优先级四档从高到低:= /path 精确匹配命中即停;^~ /static/ 前缀匹配命中后不再查正则——专门"锁住"静态路径防止被后面的正则抢走;~ 正则 按配置文件顺序第一个命中生效;普通前缀 /path 取最长匹配但只作"备胎",正则全不中才用它。口诀:精确 > ^~ > 正则(按顺序)> 普通前缀(取最长)。root 和 alias 的区别一句话:root 是"路径 + 完整 URI"拼接,alias 是"替换掉 location 前缀"。同一个请求 /ui/a.js:location /ui/ { root /opt/app; } 找 /opt/app/ui/a.js(前缀保留拼进结果);location /ui/ { alias /opt/app/dist/; } 找 /opt/app/dist/a.js(前缀被整体替换)。约束:alias 只能写在 location 里,root 可以写任何层级。我的实战判断口诀:访问路径和磁盘目录名一致用 root,不一致必须 alias——把前端文件放在目录名和访问路径不一致的地方,root 就 404,换 alias 立刻好,这个坑我在数币平台配静态资源时踩明白的。此外配置组织上我按业务拆 include:主 nginx.conf 只留全局段,conf.d/ 下一个业务一个文件,公共定义抽单独文件——变更隔离、回滚按文件回,这是多站点交付的标配组织法。

追问链:

  • 迭问1:location /prod-api/ 和 location ~ \.php$ 谁优先? → 答:正则优先——请求同时匹配两者时(如 /prod-api/x.php)正则块生效,普通前缀只是备胎。所以静态站配置常见 ^~ /static/ 先行锁死静态目录,防止被 .php、.js 正则误抢。理解"正则压普通前缀",多 location 配置行为就能推理出来,不用死记。
  • 迭问2:rewrite 的四个标志位? → 答:last 用新 URI 重新走一遍 location(小心死循环);break 在当前 location 内终止匹配;redirect/permanent 分别 302/301 跳转。我项目里 80 跳 443 用 rewrite ^/(.*)$ https://$host/$1 permanent;——捕获路径拼进 HTTPS 地址,301 让浏览器缓存跳转关系。

实操模板:

nginx
location ^~ /static/ { root /opt/app; }          # 锁住静态前缀
location /ui/ { alias /opt/app/dist/; }           # 目录名≠访问路径时用 alias
location / { root /opt/dcepscapi/ui; index index.html; }  # 数币平台前端(路径正好对齐用 root)

4.5 try_files 与前端路由 404:联调核心案例 【必背】 ​

标准回答:这是我在数币平台上线时真实处理过的联调问题,也是实施岗 Nginx 题的最高命中率考点。现象:客户反馈"登录后按 F5 刷新页面就 404,直接输入首页地址又正常"。定位过程:先看 Nginx 错误日志,发现 404 的是 /user/list 这类业务路径;再看前端框架——Vue 用的是 history 路由模式,URL 路径是前端 JS 管理的"虚拟路径",页面上点按钮切换路由是 JS 行为不发请求;但刷新时浏览器把这个路径原样发给 Nginx,Nginx 按字面路径去磁盘找文件,当然找不到——根因是"Nginx 按路径找文件"和"前端路由是 JS 行为"的模型冲突。解法一行:try_files $uri $uri/ /index.html;——先找真实文件($uri)、再找目录($uri/)、都失败回退到 index.html 交给前端路由处理。效果:刷新任何业务路径都返回首页由 JS 恢复路由状态,问题彻底解决。从那以后 try_files 成了我交付任何前端项目的标配,写进了部署手册的"前端站点模板"。这个案例我讲得最顺的原因是它四段齐全:现象、根因(两个模型的冲突)、一行解法、预防(模板化)——"小问题见基本功"的最好例证。

追问链:

  • 迭问1:try_files 三个参数分别什么作用? → 答:$uri 按当前请求路径找真实文件,静态资源命中它;$uri/ 按目录找(会尝试目录下的 index);/index.html 是兜底,把所有找不到的路径回退给前端入口。顺序不能乱——兜底放前面就所有请求都回首页了,静态资源也加载不出来。
  • 迭问2:为什么点按钮切换页面不 404、刷新才 404? → 答:点按钮的路由切换是 JS 在浏览器里改 URL 和渲染组件,根本没发 HTTP 请求;刷新是浏览器拿当前完整 URL 向服务器发请求,虚拟路径第一次真正到达 Nginx。把这个时序讲清楚,就证明真理解了根因而不是背了答案。

4.6 proxy_pass 尾斜杠与前后端联调路径约定 【必背】 ​

标准回答:这是前后端联调的另一个高频坑,我在数币平台配置 /prod-api/ 反代时踩明白的。规则一句话:proxy_pass 带尾斜杠,location 前缀会被剥掉;不带尾斜杠,完整 URI 原样传给后端。同一个请求 /prod-api/user/login:proxy_pass http://localhost:8080/;(带尾斜杠)后端收到 /user/login——/prod-api/ 前缀被剥掉再拼接;proxy_pass http://localhost:8080;(不带)后端收到 /prod-api/user/login——原样透传。到底带不带取决于前后端的路径约定:数币平台前端 baseURL 配的是 /prod-api、后端服务是根路径 context-path,所以我用带尾斜杠的写法剥掉前缀。配错的现象就是接口全 404——前端请求都发出去了、Nginx 也转发到了后端,但后端收到的路径和它注册的接口路径对不上。联调时我的排查顺序:浏览器 F12 看请求状态码和实际 URL → 后端日志看收到的路径是什么 → 对不上就是前缀剥离规则的问题,改尾斜杠或统一约定。从工程角度我的建议是"路径约定写进联调文档":前端 baseURL、Nginx location 前缀、后端 context-path 三者对齐关系画一张图给开发,这类问题在联调开始前就对齐掉,比现场排查快得多。

追问链:

  • 迭问1:联调时接口 404,你的完整排查链? → 答:五步:F12 看请求路径与状态码(确认前端发的对不对)→ Nginx access_log 看请求到没到 Nginx、$upstream_addr 打到了哪 → 后端日志看收到的实际路径 → 路径对不上查尾斜杠和 rewrite 规则 → 都对还 404 查后端服务 context-path 和接口注册。这条链我在现场十几分钟就能定位到具体环节——联调排障是实施岗每天的工作,这个链路比我背多少配置都值钱。
  • 迭问2:反代还要配哪些必要参数? → 答:透传三件套(Host、X-Real-IP、X-Forwarded-For)、超时两件套(proxy_connect_timeout 5s、proxy_read_timeout 60s)、失败切换 proxy_next_upstream error timeout。这些参数我在 4.7 的完整模板里都有,背模板一次全带上。

4.7 HTTPS/SSL 证书配置与 80→443 【必背】 ​

标准回答:HTTPS 部署是我的标准交付动作,流程四步。第一步确认编译支持:nginx -V 查 --with-http_ssl_module,没有就带参数重编译(4.2 一次配齐原则)。第二步证书准备:CA 签发或客户提供——银行项目用的是客户内部 CA 的证书,拿到 crt 和 key 两个文件放 /usr/local/nginx/cert/ 并收紧权限(key 文件 600)。第三步 443 配置:listen 443 ssl 挂证书,配 ssl_session_cache shared:SSL:1m 会话复用减少重复握手、ssl_protocols 限 TLSv1.2/1.3 禁老旧协议、ssl_ciphers 收紧套件、ssl_prefer_server_ciphers on——这套安全参数组合在客户安全扫描里是检查项。第四步 80 重定向:独立 server 块 rewrite ^/(.*)$ https://$host/$1 permanent; 把路径捕获拼进 HTTPS 地址做 301 永久跳转,浏览器会缓存跳转关系减少后续往返。整套配置在数币平台完整落地:80 全部 301 到 443,443 挂客户 CA 证书服务前端静态页,/prod-api/ 反代本地 8080 后端 jar——这也是 4.5/4.6 两个联调案例的载体。验证三连:nginx -t 过语法、reload 后 curl -k 打 443、openssl s_client 看证书链和有效期。

追问链:

  • 迭问1:证书到期怎么管理? → 答:自动化监控加标准换证流程。监控:openssl x509 -in cert.crt -noout -enddate 查到期,巡检脚本固定项,剩余 30 天、7 天两级告警——证书过期是"低级但致命"的故障,靠日历记必漏。换证:申请新证书 → 替换文件 → nginx -t → reload(平滑不断线)→ openssl s_client 远程验证线上实际生效的证书——注意验证线上而不是看文件,文件可能换了没 reload。我经手过数币平台客户 CA 证书的部署和一次到期更换。
  • 迭问2:为什么客户端报证书不可信,服务器上看着证书正常? → 答:常见是证书链不完整——服务器只发了叶子证书,没发中间证书,严格校验链的客户端(Java、老安卓)就不认。openssl s_client -showcerts 数链上张数,把中间证书拼进 crt 文件重新 reload。这个坑在客户环境排查过,属于"配完不验证链"留下的隐患——所以我的交付验收里有"多客户端类型访问验证"这一项。

实操模板(数币平台真实结构,占位符替换,可默写):

nginx
    # 80 强制跳 443
    server {
        listen      80;
        server_name localhost;
        rewrite ^/(.*)$ https://<nginx-ip>:443/$1 permanent;
    }
    # HTTPS 主站
    server {
        listen      443 ssl;
        server_name localhost;
        ssl_certificate      /usr/local/nginx/cert/server.crt;
        ssl_certificate_key  /usr/local/nginx/cert/server.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_ciphers          HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;
        ssl_protocols        TLSv1.2 TLSv1.3;
        location / {
            root  /opt/dcepscapi/ui;
            try_files $uri $uri/ /index.html;     # 前端路由刷新 404 兜底(4.5)
            index index.html index.htm;
        }
        location /prod-api/ {                     # API 反代(4.6 尾斜杠剥前缀)
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://localhost:8080/;
        }
        error_page 500 502 503 504 /50x.html;
        location = /50x.html { root html; }
    }

4.8 负载均衡 upstream 五步 【必背】 ​

标准回答:七层负载均衡配置我按固定五步走,口诀"定义组 → 配策略 → 加健康检查 → 建 server → 写 location"。第一步 http 块 upstream 组名 定义后端组;第二步组内声明策略:默认轮询适合对等后端,weight 加权适合配置不均,ip_hash 按 IP 哈希做会话保持(有 NAT 出口倾斜和节点增减重排两个结构性缺陷,更稳的方案是 session 外置到 Redis——数币平台就是这么做的,负载层不需要任何会话保持),least_conn 给长连接服务;第三步每个节点配 max_fails 加 fail_timeout 做故障摘除——注意开源版是被动检查:实际转发失败才计数,死了才发现,主动探测要 Plus 或第三方模块;backup 标备份节点、down 永久摘除;第四步 server 块监听端口;第五步 location proxy_pass http://组名 转发,透传三件套加超时加 proxy_next_upstream 失败自动切下一台。变更后的验证是标准动作:reload → 自定义日志里看 $upstream_addr 的分布是否符合权重 → curl 逐台验证。银行项目 apiserver 用的就是默认轮询——三台 master 完全对等,谁处理结果一致,轮询最合理最简单,"策略选择的前提是理解后端有没有状态"这句话能把选型思维展示出来。

实操模板(七层标准模板,可手写):

nginx
upstream backend {
    least_conn;
    server <web1-ip>:8080 weight=3 max_fails=2 fail_timeout=10s;
    server <web2-ip>:8080 max_fails=2 fail_timeout=10s;
    server <web3-ip>:8080 backup;
}
server {
    listen 80;
    server_name www.example.com;
    location / {
        proxy_pass http://backend;
        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_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_next_upstream error timeout;
    }
}

追问链:

  • 迭问1:怎么观察流量实际分到哪台? → 答:日志里加 $upstream_addr 变量记录每个请求实际命中的后端,grep 统计看分布;再配合 stub_status 状态页看连接分布。我的变更验证流程就是"reload → 看 upstream_addr 分布 → curl 探活",这在生产是标准动作不是可选项。

4.9 四层 stream:apiserver 入口实施案例 【必背】 ​

标准回答:四层和七层的区别按"工作在哪层、能看懂什么"讲:七层在 http 块,Nginx 完整解析 HTTP/HTTPS,能按 URL、Header、Cookie 路由,支持改写缓存限流;四层在 stream 块,只看 IP 加端口做纯 TCP 转发,性能更高,适合 MySQL、Redis 这类非 HTTP 服务和 K8s apiserver——apiserver 是 HTTPS 但不需要按 URL 路由,四层转发就够。这个案例我在银行项目亲手配的:三台 master 的 apiserver 各监听 6443,Nginx 的 stream 块 upstream 挂三台 6443、listen 16443 对外 proxy_pass——关键细节是端口避让:Nginx 和 master 复用同一台机器,6443 已被本地 apiserver 占用,监听端口绝不能撞,统一用 16443,外部所有 kubectl 和组件都连 VIP:16443。stream 是独立模块,编译必须带 --with-stream(4.2 一次配齐的又一实证)。四层的健康检查两层分工:stream 内 proxy_connect_timeout 转发层被动检查管"三台后端哪台可用";Keepalived 的 vrrp_script 检测 16443 端口监听管"这台入口机器的 Nginx 活着没"——两层各管一段,这个分工我在 4.10 展开。

追问链:

  • 迭问1:为什么 apiserver 用四层不用七层? → 答:因为转发需求只有"把 TCP 流量分给三台对等后端",不需要看 URL——四层少解析一层协议性能更好,配置也更简单。什么时候必须七层:要按路径路由、要 TLS 卸载给不同后端、要改写请求头的时候。按需求选层,不按"七层更高级"选。
  • 迭问2:LVS 能做 URL 转发吗? → 答:不能。LVS 工作在内核态传输层只认 IP 加端口,看不到 URL——需要 URL 路由必须 Nginx/HAProxy 七层。这也是性能换功能的分界:LVS 性能最强功能最少,我项目规模一个 Nginx 全够,没上 LVS——"没有瓶颈就不预支复杂度"。

实操模板(stream 四层,可整段背):

nginx
stream {
    upstream k8s-apiserver {
        server <master01-ip>:6443 max_fails=2 fail_timeout=10s;
        server <master02-ip>:6443 max_fails=2 fail_timeout=10s;
        server <master03-ip>:6443 max_fails=2 fail_timeout=10s;
    }
    server {
        listen 16443;                  # 避让本地 6443
        proxy_pass k8s-apiserver;
        proxy_connect_timeout 5s;
    }
}

4.10 Keepalived:VIP 漂移与健康检查 【必背】 ​

标准回答:这套配置我在银行项目从零写过并实测验证,是"多 master 高可用"改造的入口层。原理先说:Keepalived 实现 VRRP 协议——多台机器组成一个虚拟路由器(同一 virtual_router_id),对外暴露一个 VIP;priority 最高的当 MASTER 持有 VIP,每 advert_int 秒(默认 1 秒)发通告;BACKUP 收不到通告约 3 倍周期就接管 VIP,这就是 VIP 漂移,客户端入口地址永远不变。第二个关键机制是 vrrp_script 加 track_script:Keepalived 本身只保证"自己活着"不保证"Nginx 活着",必须挂真实服务检测——我的脚本检测 16443 端口有没有进程监听(ss -antp | grep 16443),数到 0 个就 exit 1,Keepalived 按 weight -20 降权;主备 priority 100/90 拉开差距,100 减 20 低于 90,VIP 立即漂走。参数设计:interval 3 秒平衡灵敏度开销、fall 2 连续两次失败才判死防单次抖动误切。故障转移验证四步是我实测做过的:ip addr 确认 VIP 在主节点 → 主节点停 Nginx → 备节点看到 VIP 漂过来 → 客户端 curl -k https://VIP:16443/version 验证集群响应——kubectl 走 VIP 无感切换。这套验证动作和第 3 章数据库切换演练、第 6 章控制面演练是同一套方法论,我在面试里会主动串起来讲。

追问链:

  • 追问1:为什么检测端口而不是检测进程名? → 答:因为"进程在"不等于"服务可用"——配置错误、端口被占、证书加载失败时 nginx 进程可能在但端口起不来。检测"入口端口有没有人监听"才是端到端可用性视角。这个原则我推广到所有健康检查设计:CLB 健康检查、K8s 探针、数据库连接验证,都是"贴近真实用户路径"。
  • 迭问2:脑裂怎么防? → 答:四条:vrrp_script 检测真实服务主动降权;priority 拉开差距快速决出;保证 VRRP 报文畅通(组播放行、防火墙放行协议号 112、跨交换机提前验证);监控双 VIP 告警——两台同时持有 VIP 就是脑裂铁证,发现后人工摘除一侧。脑裂真正的危险是两个入口同时接收写请求导致状态分叉——讲到"数据视角"才算理解它为什么可怕。

实操模板(主备+脚本,可整段背写):

bash
# /etc/keepalived/check_nginx.sh(主备相同)
#!/bin/bash
count=$(ss -antp | grep 16443 | egrep -cv "grep|$$")
[ "$count" -eq 0 ] && exit 1 || exit 0
nginx
# keepalived.conf —— 主节点(备节点 priority 90)
vrrp_script check_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 3
    weight -20        # 100-20 < 90 触发漂移
    fall 2
    rise 2
}
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51      # 主备必须一致
    priority 100
    advert_int 1
    authentication { auth_type PASS; auth_pass 1111; }
    virtual_ipaddress { <VIP>/24; }
    track_script { check_nginx; }
}

4.11 502/504 与联调排障 【必背】 ​

标准回答:这两个是联调阶段我处理最多的入口层故障,每个有固定排查链。502 Bad Gateway 含义是"后端无效响应或连不上"——排查顺序:后端进程在不在(ps 看进程、ss 看端口)→ 后端是拒绝连接还是崩溃退出 → Nginx 到后端的网络和防火墙 → SELinux 权限拒绝 → 后端自身崩溃日志。经验上"后端没起或崩了"占 502 的大多数——联调第一天最常见的 502 就是后端 jar 还没起来前端已经在打接口。504 Gateway Timeout 含义是"后端活着但太慢"——排查顺序:应用慢日志和慢 SQL → 按需调大 proxy_read_timeout → 长任务异步化 → 后端资源层。一句话区分:502 是死、504 是慢,处理方向完全不同。快速定位哪台后端靠日志里的 $upstream_addr 变量——它记录请求实际打到的后端,grep 出 502 的分布锁定机器再上去 curl 复现。所以我的 log_format 永远带着 upstream_addr、request_time、upstream_response_time 三个变量——request_time 和 upstream_response_time 一减就把"Nginx 侧耗时"和"后端侧耗时"分开,慢在哪段一眼看出。这套联调排障能力是实施岗日常:前后端联调阶段,入口层问题基本都是我在现场第一手定位的。

追问链:

  • 迭问1:联调时前端说"接口不通",你的第一组命令? → 答:三连:ss -lntup | grep 端口 确认后端起了没;curl -v 本地:端口/接口 绕过 Nginx 直接打后端——通了说明后端没问题,问题在 Nginx 配置或网络;tail -f nginx error.log 看转发报错。三分钟内把问题切分到"后端、Nginx、网络"三段之一——联调排障的价值就是快速切分责任段。
  • 迭问2:502 间歇性出现怎么查? → 答:看时间分布和 upstream_addr 分布:固定打某台后端就是那台机器的问题(资源、GC、半死状态);随机分布看是不是后端处理能力到顶(配合 ab 压测找拐点)或者 keepalive 配置问题。间歇性问题比持续问题难查,靠的是日志里的变量留痕——这就是我说"日志为排障而设计"的原因。

4.12 日志分析与访问报表 【理解】 ​

标准回答:access_log 记录每个请求,我的自定义 log_format 固定追加三个变量:$upstream_addr(实际命中后端,定位哪台机器)、$request_time(总耗时)、$http_x_forwarded_for(多级代理真实客户端)。error_log 配 warn 级别足够,排障临时调 debug。给客户出访问报表的三件套命令:TOP10 IP(awk 取列 | sort | uniq -c | sort -nr | head)、状态码分布(awk 关联数组)、5xx 数量(awk '$9>=500')——这三条是联调验收后给客户交付"平台运行情况"材料的固定动作,第 2 章 2.4 的统计模式在这里落地。日志轮转用 logrotate 加 postrotate 执行 nginx -s reload 重开文件(第 1 章 1.13 模板)。另外我把"日志含 upstream_addr"列为 Nginx 交付的检查项——没这个变量,后续所有"哪台后端出问题"的排查都要瞎猜,交付时一行配置的事,价值长期。

追问链:

  • 迭问1:$request_time 和 $upstream_response_time 差很多说明什么? → 答:两个值一减分开"Nginx 侧"和"后端侧"耗时:request_time 明显大、upstream_response_time 小,时间耗在传输或排队——客户端网络慢、大响应体、到后端的连接没复用(该配 upstream keepalive);两者接近且都大,瓶颈在后端本身。这个"对表归因"是我分析慢请求的第一刀。

4.13 性能参数、压测与交付验收 【理解】 ​

标准回答:性能参数五组按优先级背:worker_processes auto 对齐核数;worker_rlimit_nofile 65535 进程级句柄;events 块 use epoll 加 worker_connections 10240 加 multi_accept;传输层 sendfile on 加 tcp_nopush/nodelay(静态文件零拷贝);keepalive_timeout 65s 加客户端超时防慢连接。理论并发等于 worker 数乘 worker_connections,反代场景除以 2(每个客户端连接对应一条后端连接)。系统层必须配套:ulimit 和 somaxconn 不抬,Nginx 配再大也接不住——第 1 章 1.12 的联动。交付验收的容量证明用 ab:ab -n 10000 -c 200 URL,看吞吐 QPS、Failed requests 必须为零、P95/P99 分位数——对延迟敏感业务验收标准应该是"P99 小于 X 毫秒"而不是"QPS 到多少"。压测纪律三条:别打生产或低峰加限流保护、逐级加压找拐点、边压边观测(stub_status、后端 CPU、数据库连接)找"谁先到瓶颈"。我的方法论总结句:参数是起点不是终点,以压测和观测为准——这套口径答出来比背数字可信。

实操模板(参数组,可背):

nginx
worker_processes auto;
worker_rlimit_nofile 65535;
events {
    use epoll;
    worker_connections 10240;
    multi_accept on;
}
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65s;
    client_header_timeout 15s;
    client_body_timeout 15s;
}

4.14 Tomcat 部署与 Java 应用交付 【理解】 ​

标准回答:Tomcat 是 Java 应用的常用容器,我的部署口径按"解压版 + 标准化"讲。部署:下载 tar.gz 解压到 /opt/tomcat(解压版不用装、目录自定,和编译 Nginx 一个思路),确认 JDK 就绪——银行项目用的是毕昇 JDK(华为针对鲲鹏优化过的 OpenJDK 发行版,aarch64 版本从鲲鹏镜像站拿,三步:下载解压、配 JAVA_HOME 和 PATH、java -version 确认输出且架构对——详见华为篇第 6 章 6.1)。配置三处:server.xml 改端口(HTTP 端口、shutdown 端口——多实例共存时必须改,默认 8005 冲突是经典问题)、JVM 内存参数在 catalina.sh 或 setenv.sh 配 Xms/Xmx;应用部署把 war 放 webapps 或配置独立 context。交付标准化动作:systemd 托管(不用自带的 startup.sh 散跑)、日志配 logrotate、防火墙放行端口、catalina.out 是排障第一入口。健康验证:ss 查端口起来、curl 首页或健康检查接口、日志无启动异常。数币平台后端是 jar 直跑(java -jar 配 systemd),Tomcat 部署我在课程实验和其他项目场景做过——两套口径我分开讲,不混。

追问链:

  • 迭问1:Java 应用启动失败你怎么查? → 答:固定三步:看应用日志(我们固定在应用目录 logs/ 下)——启动失败的具体原因基本都在里面;看 JVM 层——内存参数是否超了机器实际(Xmx 配太大被 OOM killer 杀);看环境——JAVA_HOME 对不对、毕昇 JDK 架构匹不匹配(ARM 上误用 x86 JDK 直接起不来)。华为篇第 6 章 6.1.3 有一组"真用过的证据"命令,面试深挖去那里拿。
  • 迭问2:jar 直跑和 Tomcat 部署怎么选? → 答:Spring Boot 类应用 jar 内嵌容器,直跑加 systemd 托管最简单,是现在的主流;传统 war 或需要 Tomcat 高级特性(JNDI、特定 Valve)才用外置 Tomcat。数币平台是 jar 直跑,我的交付模板里 jar 的启停脚本和 unit 文件是标配(第 2 章 2.12 的启停模板)。

4.15 Redis 源码编译部署交付 【理解】 ​

标准回答:Redis 部署我真实做过——银行项目源码编译部署 Redis 5.0.3,作为数币平台的会话和缓存层。流程:下载源码包 make 编译(ARM 环境先确认依赖库),make install 后配置 redis.conf 的交付参数:bind 0.0.0.0(按网络安全要求收敛到指定网段更好)、daemonize yes 后台运行、requirepass 设密码(内网也不能裸奔)、端口按规划、持久化按定位配置——当时定位是缓存,开 RDB 定时快照就够(丢了回源重建);maxmemory 加淘汰策略 allkeys-lru 配缓存定位。交付标准化三件:systemd 托管、logrotate、redis-cli ping 加 info replication 写进巡检脚本。验收动作:set/get 读写验证、应用侧连接测试、重启后数据按预期(RDB 恢复)验证。缓存三兄弟(穿透/击穿/雪崩)这类运行期话题在支持/运维视角问得多,实施岗我备着标准层答案:穿透缓存空值加布隆过滤器、击穿互斥锁重建、雪崩随机 TTL 加高可用——面试被问到能接住一层即可,深挖我按"配合开发定位过方向"的口径答。

追问链:

  • 迭问1:为什么缓存定位只开 RDB? → 答:因为丢了能从数据库回源重建,快照间隔的丢失窗口业务可接受;AOF 或混合持久化是"当存储用"才必须的。这个选择的逻辑是"按业务定位定持久化策略"——我在面试里把"为什么这么配"讲清楚,比背 RDB/AOF 对比表更有说服力。

关联话术:"中间件部署是简历技能第 6、7 条的直接支撑:Nginx 编译部署加 SSL、Redis 源码编译、Tomcat/JDK 环境,全是我在银行项目亲手做的——'国产化环境里把中间件从源码装到能交付',这套手上功夫是我做实施的看家本领之一。"

4.16 本章案例 ​

  • 【真实项目·银行多 master 高可用入口】背景:数币平台 K8s 集群最初单 master,业务方要求控制面高可用。任务:把 apiserver 入口做成无单点。行动:三台 master 各部署 Nginx(stream 监听 16443 挂三台 6443,避开与本地 apiserver 端口冲突)加 Keepalived(priority 100/90,check_nginx.sh 检测 16443 监听,失败 weight -20 漂移);期间先踩证书 SAN 的坑(VIP 连集群报 x509,按六步更新 certSANs 解决,详见第 6 章)。结果:主节点关机实测 VIP 秒级漂移,kubectl 经 VIP:16443 正常返回;后又用 haproxy 方案(roundrobin + check + 1080 stats 页)完整复现一遍,两方案都沉淀进标准实施文档——"同一目标双方案验证"的完整故事。
  • 【真实项目·数币平台 Nginx 编译部署与联调】背景是国产化环境没有 Nginx 包且需 HTTPS。任务:在统信 UOS ARM 上部署生产级入口并完成前后端联调。行动:源码编译 1.25.1(ssl/stub_status/stream 一次配齐);配 80→443 的 301、443 挂客户 CA 证书服务前端静态页(root + try_files 解决刷新 404)、/prod-api/ 带尾斜杠反代本地 8080 并透传真实 IP。结果:前端与 API 入口稳定运行至交付,编译参数与配置结构成为我后续项目复用模板——简历技能第 7 条的完整实证。
  • 【课程项目】LNMP 动静分离实验:Nginx 按后缀拆 location——静态资源(html/css/js/图片)走 root 直出配 expires 7d 缓存,动态请求 proxy/fastcgi 转发后端,中间把 php-fpm 的进程池参数也调过一轮观察并发表现;用 ab 压测对比动静分离前后的吞吐与响应时间,数据验证了"静态走 Nginx 直出高一个量级"的结论。这个实验让我对 sendfile 零拷贝、worker_connections、keepalive 这些参数有了量化体感——后来做数币平台的 Nginx 交付,参数怎么定我心里是有数的。
  • 【经典故障】前端 history 路由刷新 404:客户反馈"登录后 F5 刷新 404、输首页正常"。定位:前端虚拟路径刷新时被 Nginx 按字面找文件。解决:try_files $uri $uri/ /index.html; 一行修复,之后成为我交付前端项目的标配。配套收获:同期的 proxy_pass 尾斜杠 404 问题也一并踩明白——两个坑让我此后交付前端项目条件反射先配 try_files、先确认反代前缀约定。

章末自测清单(快问快答) ​

  1. Nginx 高并发三要素? → 多进程(master/worker)+ 事件驱动 + epoll 异步非阻塞。
  2. ARM 编译 Nginx 为什么要一次配齐模块? → 二次补编译要停服;--with-http_ssl_module/--with-stream/--with-http_stub_status_module。
  3. location 优先级? → = 精确 > ^~ > 正则(按顺序)> 普通前缀(取最长)。
  4. root 和 alias? → root 拼 URI(路径对齐时用),alias 替换前缀(目录名≠访问路径时用)。
  5. 前端刷新 404 一行解法? → try_files $uri $uri/ /index.html;(history 路由回退前端入口)。
  6. proxy_pass 尾斜杠的规则? → 带尾斜杠剥 location 前缀再拼;不带则完整 URI 透传;数币平台带尾斜杠(baseURL /prod-api、后端根路径)。
  7. 80→443 怎么写? → rewrite ^/(.*)$ https://$host/$1 permanent;(301)。
  8. 反代透传哪两个头拿真实 IP? → X-Real-IP、X-Forwarded-For。
  9. 证书到期监控怎么做? → openssl x509 -noout -enddate 进巡检,30/7 天两级告警;换证后用 openssl s_client 验线上。
  10. 为什么监听 16443? → Nginx 与 master 复用机器,6443 被本地 apiserver 占用。
  11. vrrp_script 检测什么? → 真实服务(16443 端口监听),不是 Keepalived 自身;失败 weight -20 降权漂移。
  12. 502 和 504 一句话区分? → 502 后端死/无效响应,504 后端慢超时;排障入口完全不同。
  13. 哪个变量定位"请求打到哪台后端"? → $upstream_addr(log_format 必带)。
  14. 理论并发公式与反代系数? → worker 数 × worker_connections;反代场景除以 2。
  15. 毕昇 JDK 是什么? → 华为针对鲲鹏优化的 OpenJDK 发行版,aarch64 包三步装(下载/配环境变量/验证版本架构)。
  16. Redis 缓存定位的持久化选择? → RDB 够用(丢了回源重建);当存储用才必须 AOF/混合。