运维常见题-网站维护
DeepSeek V4 Flash 辅助生成。为确保内容的可靠性,笔者已对大部分关键论点进行了人工复核与校验。但鉴于大模型的固有局限,本文仍可能存在认知偏差或未尽准确之处。若您在阅读中发现存疑或矛盾之处,欢迎反馈讨论,笔者将及时核实与修正。🤔 简述 HTTP/1.0、1.1、2、3 区别?
HTTP协议经历了四代演进,每一代解决前一代的核心痛点:连接复用、并发能力、传输效率。HTTP/1.0(1996,RFC 1945)- 特点:每个请求/响应使用一个独立的
TCP连接,请求完成后连接立即关闭(短连接) - 问题:一次页面加载需要多个资源(
HTML、CSS、JS、图片),每个资源都要新建TCP连接,TCP握手(尤其HTTPS的TLS握手)开销巨大 - 其他:无
Host头(同一IP上无法在HTTP层区分多个域名,即虚拟主机);缓存控制简陋(仅有Expires/Pragma/Last-Modified,无Cache-Control/ETag)
- 特点:每个请求/响应使用一个独立的
HTTP/1.1(1997首版RFC 2068,1999定稿RFC 2616,现为RFC 9110/9111/9112)- 持久连接(
Keep-Alive) :默认复用同一个TCP连接发送多个请求,解决频繁握手问题 Host头:支持一个IP上部署多个域名(虚拟主机)- 管线化(Pipelining):客户端可以连续发送多个请求而不必等前一个响应——但由于响应必须按请求顺序返回(
FIFO),一个慢响应会阻塞后面的所有响应(队头阻塞),实际很少启用 - 其他:
chunked传输编码、更完善的缓存控制(Cache-Control、ETag) - 核心问题:队头阻塞(
Head-of-Line Blocking) ——同一连接上的请求必须串行处理,一个响应慢,后续全等
- 持久连接(
HTTP/2(2015,RFC 7540,现为9113)- 二进制分帧:把
HTTP消息拆成二进制帧(HEADERS、DATA等),替代HTTP/1.1的纯文本格式 - 多路复用(
Multiplexing) :多个请求/响应可以在同一个TCP连接上并行交错传输,解决了应用层的队头阻塞 - 头部压缩(
HPACK) :压缩首部(请求和响应两侧),减少重复头部传输 - 服务器推送(
Server Push) :规范层面仍保留(RFC 9113 §8.4),但Chrome 106(2022-09)、Firefox(2022)已移除实现、Safari从未实现,实际未普及;HTTP/3无此功能,业界替代是103 Early Hints(RFC 8297) - 流优先级:
RFC 7540的依赖-权重方案已被RFC 9218(Extensible Prioritization,2022)取代(原方案"并不成功") - 局限:虽然解决了应用层队头阻塞,但
TCP层队头阻塞仍然存在——TCP保证数据有序,一个TCP包丢失会导致后续所有包等待重传,即使它们属于不同的请求流
- 二进制分帧:把
HTTP/3(2022,RFC 9114)- 传输层改用
QUIC协议(RFC 9000,承载于UDP之上)替代TCP - 解决
TCP层队头阻塞:QUIC在用户空间实现可靠传输和有序交付,但丢失的包只影响它所在的流,其他流不受影响 0-RTT连接恢复:基于TLS 1.3会话恢复票据(PSK),第二次连接可免握手直接发送数据(注意0-RTT有重放风险,RFC 9001)- 连接迁移:
QUIC用连接ID标识连接,IP地址变化(如Wi-Fi切到移动网络)连接不中断 - 内建
TLS 1.3:加密是QUIC的强制部分,没有明文HTTP/3 - 头部压缩用
QPACK(RFC 9204)而非HPACK(应对乱序到达的首部) - 部署现状:
W3Techs 2026-08数据显示约40%的网站已支持HTTP/3
- 传输层改用
四代对比速览
1.0:短连接,一请求一连接1.1:持久连接,但有队头阻塞2:多路复用(解决应用层队头阻塞),基于TCP3:多路复用 + 解决传输层队头阻塞,基于UDP/QUIC
协助记忆
- 一代一痛点:
1.0连接费(短连接)→1.1连接省(持久连接)但排队(队头阻塞)→2并行(多路复用)但堵在TCP→3换路(QUIC/UDP)彻底不堵 - 队头阻塞有两个层面:应用层(
HTTP/1.1的FIFO)和传输层(TCP包丢失重传) ———HTTP/2解决前者,HTTP/3解决后者
- 一代一痛点:
进阶思考
为什么
HTTP/2多路复用后还要升级到HTTP/3?HTTP/2解决的是应用层队头阻塞——多个请求可以在一个TCP连接上并行,不需要按序等待。但TCP本身有传输层队头阻塞:TCP保证数据按序交付,如果某一个TCP段丢失,接收方必须等待该段重传才能继续处理后续数据,即使这些数据属于完全不同的请求流。在丢包率高的弱网环境(移动网络)这个问题尤其严重。HTTP/3用QUIC(UDP)解决了这个问题——每个流独立处理,一个流的丢包不影响其他流
HTTP/3用了UDP,可靠性和顺序性怎么保证?QUIC在UDP之上自己实现了可靠传输和有序交付——它有类似TCP的序号、确认、重传机制,但作用域是"流"而不是整个连接。每个流独立编号、独立确认、独立重传,所以一个流的丢包只重传该流的数据,不影响其他流。这也是QUIC解决队头阻塞的本质:把TCP的"全局有序"变成QUIC的"流内有序"
🤔 HTTP 有哪些常见的状态码?
HTTP状态码是服务器在响应中返回的三位数字,用于告知客户端请求的处理结果。按首位数字分为五类:1xx(信息)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)。1xx:信息性响应100 Continue:客户端可以继续发送请求体101 Switching Protocols:协议切换(如升级到WebSocket)103 Early Hints:提前发送提示(如预加载资源链接),配合301/308重定向场景做性能优化- 日常运维很少直接见到
1xx,多由协议层内部处理
2xx:成功200 OK:请求成功,最常见201 Created:资源创建成功(常用于POST创建资源)202 Accepted:请求已接受但尚未处理完成(异步任务)204 No Content:请求成功但无内容返回(常用于DELETE、以及POST/PUT的"成功无内容"场景)
3xx:重定向301 Moved Permanently:永久重定向,后续请求应使用新URL(默认允许缓存,受Cache-Control控制)302 Found:临时重定向,后续请求仍用原URL303 See Other:方法强制改为GET(SHOULD use GET,RFC 9110 §15.4.4)——常用于表单提交后重定向到结果页304 Not Modified:资源未修改,可使用缓存(配合If-Modified-Since/ETag)307 Temporary Redirect:临时重定向,且保留请求方法和请求体308 Permanent Redirect:永久重定向,且保留请求方法和请求体- 方法语义完整图谱:
301/302(MAY改方法)→303(SHOULD用GET)→307/308(必须保留方法)
4xx:客户端错误400 Bad Request:请求语法错误,服务器无法或不愿处理401 Unauthorized:未认证(未登录或凭据无效)。注意:401是"没证明你是谁",403是"不让进"403 Forbidden:服务器理解请求但拒绝处理/授权——常因权限不足,但也可能是未认证、IP封禁、WAF拦截;未认证时服务器也可能返回403而非401(如不想暴露资源存在性)404 Not Found:资源不存在,最常见405 Method Not Allowed:请求方法不被允许(如对只读接口发POST)408 Request Timeout:请求超时409 Conflict:请求与当前资源状态冲突(如版本冲突)410 Gone:资源已永久删除(区别于404的"不存在",410是"曾经存在但没了")413 Content Too Large:请求体过大(RFC 9110新名,旧名Payload Too Large仍广泛使用)429 Too Many Requests:请求过多,触发限流(RFC 6585定义,常配合Retry-After头)
5xx:服务器错误500 Internal Server Error:服务器内部错误,通用错误码501 Not Implemented:服务器不支持请求的方法502 Bad Gateway:网关/代理收到上游服务器的无效响应(Nginx作为反向代理时常见)503 Service Unavailable:服务暂时不可用(过载、维护中),常配合Retry-After头,通常过一段时间会恢复504 Gateway Timeout:网关/代理等待上游响应超时
协助记忆
五位分类:
1信、2成、3转、4客户错、5服务器错易混三对:
301vs308:都是永久,但301方法可能变(POST→GET),308保留方法302vs307:都是临时,302方法可能变,307保留方法401vs403``:401是"你是谁",403是"不让进"(注:403不要求已认证,是"拒绝授权")
502vs503vs504:502是上游答了但答错(无效响应)、503是自己没空(过载)、504是上游没答(超时)
进阶思考
301和308到底有什么区别?什么时候用哪个?- 核心区别是请求方法是否保留。
301(和302)在重定向时,很多浏览器和客户端会把POST请求改为GET请求(RFC使用MAY,历史行为普遍存在);303明确要求改用GET;308(和307)明确要求保留原方法和请求体。实践中:永久迁移且希望客户端用新URL重新发起GET请求,用301;API场景希望POST/PUT的请求体和方法原样转发到新地址,用308。现代Web的API重定向推荐308/307以保证语义一致
- 核心区别是请求方法是否保留。
反向代理场景,
502、503、504分别对应什么排查方向?502(Bad Gateway):上游服务器响应了但响应无效——如上游进程崩溃返回空响应、返回了非法响应格式。排查上游应用是否挂了、端口是否监听。503(Service Unavailable):本机(代理)或上游过载/维护——如后端连接数打满、限流触发。排查后端负载、连接池。504(Gateway Timeout):上游没有及时响应——如后端处理超时、慢查询。排查后端应用延迟、数据库慢查询。- 一句话:
502是上游答了但答错,503是没空,504是没答
🤔 浏览器访问域名经历了哪些流程?
从在地址栏输入域名到页面显示,浏览器经历了一条完整的链路:
URL解析 →DNS解析 →TCP连接 →TLS握手(HTTPS)→HTTP请求 → 服务器处理 →HTTP响应 → 浏览器渲染。每一步都有对应的可排查点。第一步:
URL解析- 浏览器解析输入的
URL,拆分成协议(https)、域名(www.example.com)、端口(默认443)、路径(/index.html)、查询参数等 - 判断协议是否合法、域名格式是否正确
- 浏览器解析输入的
第二步:
DNS解析(把域名变成IP)- 按缓存层级逐级查找:浏览器缓存 → 系统解析器(
hosts文件、系统缓存、DNS服务器的相对顺序由操作系统决定 ———Windows通常是hosts→DNS Client缓存 →DNS服务器,Linux按nsswitch.conf默认files dns通常hosts优先) - 客户端向递归
DNS服务器发起递归查询;递归解析器对根DNS→ 顶级域DNS(.com)→ 权威DNS(example.com的NS)的逐级查询称为迭代查询(浏览器本身不逐级问根服务器) - 细节:现代浏览器和系统可能启用
DNS-over-HTTPS(DoH,RFC 8484),DNS查询走HTTPS加密通道
- 按缓存层级逐级查找:浏览器缓存 → 系统解析器(
第三步:
TCP连接(三次握手)- 拿到
IP后,浏览器与服务器建立TCP连接:SYN→SYN-ACK→ACK(RFC 9293) - 涉及
TCP相关优化:Fast Open(TFO)、连接复用(keep-alive);若启用HTTP/3,TCP+TLS合并为QUIC一次握手
- 拿到
第四步:
TLS握手(HTTPS专属)HTTPS下进行TLS握手(以TLS 1.3为主,现行规范RFC 9846):- 客户端发送
ClientHello(支持的加密套件、TLS版本、key_share密钥共享) - 服务器返回
ServerHello(含密钥共享)、证书;证书在加密消息中发送(EncryptedExtensions→Certificate→CertificateVerify→Finished) - 客户端验证证书(信任链、域名匹配、有效期)
- 双方确认,握手完成,开始加密通信
- 客户端发送
注:
TLS 1.2流程是"独立的密钥交换回合(ECDHE等)",TLS 1.3把密钥共享直接放进ClientHello/ServerHello,更快更简洁TLS 1.3首次握手1-RTT、会话恢复0-RTT(注意0-RTT有重放攻击风险,规范要求默认禁用,浏览器默认不开early data)
第五步:发送
HTTP请求- 浏览器构造
HTTP请求(方法、URL、头部、cookie),通过已建立的加密通道发送 - 经过
CDN时,请求先到最近的CDN节点,CDN命中缓存则直接返回,未命中则回源
- 浏览器构造
第六步:服务器处理
- 请求到达服务器(或反向代理
Nginx),经过:负载均衡 →Web服务器 → 应用代码 → 数据库/缓存查询 - 服务器生成响应(状态码、响应头、响应体)
- 请求到达服务器(或反向代理
第七步:
HTTP响应返回- 服务器返回响应:状态码(
200/404/500等)、响应头(Content-Type、Cache-Control、Set-Cookie等)、响应体(HTML) - 浏览器判断状态码:
2xx正常渲染,3xx跟随重定向(重新走流程),4xx/5xx显示错误页
- 服务器返回响应:状态码(
第八步:浏览器渲染
- 解析
HTML构建DOM树 - 解析
CSS构建CSSOM DOM+CSSOM合并生成渲染树(Render Tree) (注:Render Tree是WebKit/Blink的实现术语,非统一Web标准定义,Gecko叫Frame tree)- 布局(Layout/Reflow) :计算每个元素的几何位置
- 绘制(Paint) :绘制到屏幕
- 合成(Composite) :把各层合并呈现 ———
transform/opacity动画只触发合成,不触发重排重绘 - 脚本与样式对渲染的影响:
<script>是parser-blocking(阻塞DOM解析进而阻塞渲染,除非async/defer;<script type="module">默认defer),但现代浏览器有预加载扫描器提前并行下载脚本(下载并行、执行阻塞);CSS是render-blocking但不阻塞DOM构建 JavaScript执行修改DOM/CSSOM会触发重排(Reflow)和重绘(Repaint)
- 解析
协助记忆
- 八步链路:拆
URL→ 查DNS→ 连TCP→ 握TLS→ 发请求 → 服务器处理 → 收响应 → 渲染页面 - “三握一握”:
TCP三次握手 +TLS一次握手(HTTPS专属) - 渲染五步:
DOM(结构)→CSSOM(样式)→Render Tree(合并)→Layout+Paint(画出来)→Composite(合成呈现) - 排查口诀:访问慢,先看
DNS快不快,再看TCP/TLS握多久,再看服务器响应时间,最后看渲染卡不卡
- 八步链路:拆
进阶思考
为什么
HTTPS站点首次访问比HTTP慢?慢在哪?HTTP只需TCP三次握手(1个RTT);HTTPS除了TCP还要TLS握手 ———TLS 1.2需要2个RTT、TLS 1.3需要1个RTT(首次),加上TCP总共2~3个RTT才能发出第一个请求。RTT越大(物理距离越远),差距越明显。优化手段:TLS 1.3会话恢复(0-RTT,需权衡重放风险)、HTTP/3(QUIC把TCP+TLS合并为一次握手)、连接复用与TFO。在HTTP/3或连接复用场景下HTTPS与HTTP差距已不明显,但全新连接下HTTPS仍多约1个RTT
输入域名后浏览器直接显示"无法访问此网站",最可能是哪一步出了问题?
- 按经验上最常见的顺序:一是
DNS解析失败(域名拼错、DNS服务器故障、本地hosts被改)——浏览器提示"找不到服务器IP地址";二是TCP连接失败(服务器宕机、端口不通、防火墙拦截)——— 提示"连接超时"或"连接被拒绝";三是TLS握手失败(证书过期、不受信任)——提示"您的连接不是私密连接";四是服务器返回5xx错误(有响应但处理失败)。浏览器通常给出具体错误提示,根据提示即可定位到是哪一步
- 按经验上最常见的顺序:一是
🤔 HTTP 请求头和响应头有哪些内容?
HTTP头部(Header)是请求/响应中"键值对"形式的元数据,用于传递请求上下文、内容描述、缓存策略、认证信息等。现代分类(RFC 9110)不再使用传统的"通用头/实体头"分类,头部按用途分为请求头、响应头、表示头、内容头等;本文按常见场景组织,仍会说明新旧分类对应关系。请求行 / 状态行结构(头部之前)
- 请求行:
GET /index.html HTTP/1.1(方法 + 路径 + 版本) - 状态行:
HTTP/1.1 200 OK(版本 + 状态码 + 原因短语)
- 请求行:
请求头(客户端 → 服务器)
Host:目标主机和端口(HTTP/1.1必需,虚拟主机依据)User-Agent:客户端标识(浏览器/版本/操作系统)Accept:客户端可接受的内容类型(如text/html、application/json)Accept-Encoding:客户端支持的压缩算法(gzip、br、zstd——2024-2025主流浏览器已支持)Accept-Language:客户端语言偏好Content-Type / Content-Length:请求体类型和长度(POST/PUT时;注意这属于"表示头/内容头",同一字段在请求和响应中含义一致)Authorization:认证凭据(Bearer token、Basic)Cookie:客户端携带的cookie(RFC 6265定义,注意不是RFC 9110)Referer:来源页面URL(注意拼写是Referer而非Referrer)Origin:跨域请求的来源(协议+域名+端口,CORS用)X-Forwarded-For:经过代理时的原始客户端IP(非标准头,事实标准;标准替代为Forwarded,RFC 7239)
响应头(服务器 → 客户端)
Content-Type / Content-Length:响应体类型和长度Set-Cookie:服务器设置 cookie(RFC 6265 定义,含 Secure、HttpOnly、SameSite 等属性)Location:重定向目标地址(3xx 响应;也用于 201 Created 返回新资源地址)Cache-Control:响应缓存策略(RFC 9111 定义)ETag:资源版本标识(配合 If-None-Match 缓存验证;也配合 If-Match 做乐观并发控制)Last-Modified:资源最后修改时间(配合 If-Modified-Since)Expires:缓存过期时间(HTTP/1.0 遗留,被 Cache-Control 取代)Server:服务器软件信息Date:响应时间Access-Control-Allow-Origin:CORS 允许的跨域来源Retry-After:多久后重试(配合 429/503)Strict-Transport-Security:强制 HTTPS(HSTS,RFC 6797)
传统分类与新分类的对应(旧文档常见,帮助理解)
- 传统四分类:通用头、请求头、响应头、实体头
RFC 9110已同时废弃"通用头"和"实体头"概念,重构为:请求头、响应头、表示头(Representation,描述资源表示,如 Content-Type、ETag、Last-Modified)、内容头(Content,描述消息内容,如 Content-Length)、验证器字段(Validator,ETag/Last-Modified 单独归为验证器)- 旧教材把 ETag 归入实体头是简化说法,严格按 RFC 2616 ETag 是响应头
常见安全响应头(现代 Web 标配)
CSP(Content-Security-Policy):内容安全策略,限制资源加载来源X-Content-Type-Options: nosniff:禁止 MIME 嗅探X-Frame-Options / frame-ancestors:点击劫持防护Referrer-Policy:控制 Referer 发送策略Permissions-Policy:限制浏览器功能权限COOP/COEP/CORP:跨源隔离相关头
协助记忆
- 请求头记一组:
Host、UA、Accept家族、Cookie、Authorization——“你是谁、要什么、带什么” - 响应头记一组:
Content-Type、Set-Cookie、Location、Cache-Control、ETag——— “给你什么、让你存什么、跳去哪” - 排查缓存问题看这四个:
Cache-Control、ETag、Last-Modified、Expires - 区分两个"内容":
Content-Type(媒体类型)≠Content-Length(字节长度)
- 请求头记一组:
进阶思考
Cookie和Authorization都能传递身份信息,有什么区别?- 机制不同。
Authorization是请求头,每次请求由客户端代码显式携带(如Bearer token),无状态、常用于API。Cookie是浏览器自动管理的状态机制——服务器Set-Cookie后,浏览器自动在后续请求中带上对应域名的cookie,有状态、常用于Web会话。Cookie会自动附加到同域请求(不受代码控制),Authorization需要代码显式添加。安全上:Authorization适合API token,Cookie需配合Secure/HttpOnly/SameSite防窃取
- 机制不同。
Nginx反代场景,如何保证后端能看到真实客户端IP?Nginx在转发请求时把客户端真实IP写入X-Forwarded-For或X-Real-IP头,后端应用从这些头读取。但X-Forwarded-For是可伪造的——客户端可以直接构造这个头。安全做法:在Nginx层用set_real_ip_from+real_ip_header处理,或在信任的代理边界重写X-Forwarded-For(不要直接信任客户端传入的值)。这是反代场景的常见安全坑
🤔 网站显示中文乱码会是什么原因?
中文乱码的根本原因是编码不一致——网页的字节是按
A编码写入的,但浏览器用B编码解读,导致字节序列被错误解码。排查方向就是找出"写入编码"和"读取编码"哪里对不上。编码机制速览(先理解再排查)
- UTF-8:现代标准,兼容
ASCII,中文字符占3字节,全栈默认。2025-2026年UTF-8已占全网超98%,且Encoding标准已把UTF-8定为新协议的强制编码 GBK / GB2312:中文传统编码,中文字符占2字节,旧系统常见(注意GBK是GB2312的超集,现行国标是GB18030)- 乱码的本质:同一串字节,用不同编码解读得到不同字符
- UTF-8:现代标准,兼容
常见原因一:
HTML文件内部声明与实际编码不符HTML文件实际以UTF-8保存,但<meta charset="GBK">声明为GBK(或反过来)——浏览器按声明解码,乱码- 注意
file命令的局限:file能可靠识别UTF-8/UTF-16(靠BOM和字节特征),但对GBK等东亚双字节编码通常只报含糊的 “ISO-8859 text” 或 “data"。排查GBK更可靠的方式:用iconv -f GBK -t UTF-8文件 和iconv -f UTF-8 -t GBK文件 双向试转,看哪个方向能转出正常文字;或用chardet/uchardet检测 - 修复:统一文件保存编码和
meta声明
常见原因二:
HTTP响应头charset与meta声明冲突HTTP响应头Content-Type: text/html; charset=UTF-8与HTML内meta声明不一致时,HTTP头的优先级更高- 排查:
curl -I <url>查看响应头charset;curl -s <url> | head -20查看meta声明 - 修复:让两者一致(改
Nginx/应用配置或改meta) - 补充:还有更高优先级的
BOM——— 文件开头的UTF-8 BOM优先级高于包括HTTP头在内的一切声明。这是"改了meta还是乱码"的另一个隐蔽原因 meta声明必须位于文件前1024字节内才生效
常见原因三:数据库存储编码与连接编码不一致
- 数据库表是
UTF-8,但应用连接数据库时用了错误的连接字符集(如latin1),导致"存进去是乱码"或"读出来是乱码” - 机制(
MySQL):服务器按character_set_client解释客户端发来的字节,再转character_set_connection,最后转目标列字符集;读出的结果由character_set_results决定 - 排查:
SHOW VARIABLES LIKE 'character_set%'查看各字符集配置 - 修复:连接时
SET NAMES utf8mb4(同时设置client/connection/results三个变量);建库时CREATE DATABASE ... DEFAULT CHARACTER SET utf8mb4 - 注:
MySQL 8.0+默认即utf8mb4,旧的utf8(utf8mb3)已废弃、未来大版本移除
- 数据库表是
常见原因四:编码被二次转换
- 数据经过多个环节时,某个环节做了一次错误的编码转换
- 是否可逆取决于原始字节是否被改写:纯显示层乱码(字节仍是原始
UTF-8,只是被按GBK显示)可逆 ——— 著名例证是"锟斤拷" (UTF-8替换符被按GBK显示);只有经真实转码(如iconv转错后落库)才不可逆
常见原因五:页面完全没有编码声明
HTTP头无charset、HTML内也无meta声明时,浏览器按默认(现代浏览器默认UTF-8)尝试解码,非UTF-8字节就会乱码- 这比"用户改了浏览器设置"常见得多——排查时先确认页面是否有编码声明,而不是去查浏览器设置
协助记忆
- 一句话:乱码 = 写的时候用
A编码,读的时候用B编码,对不上 - 读取侧优先级(从高到低) :
HTTP头charset→BOM→meta声明 → 默认UTF-8 - 写入侧检查:文件实际编码(iconv 双向试转)、数据库/连接字符集
- 排查命令:
curl -I看响应头、iconv -f X -t Y文件 试转换、chardet检测 utf8mb4记住:MySQL存emoji必须用utf8mb4而非utf8
- 一句话:乱码 = 写的时候用
进阶思考
HTTP响应头charset和HTML meta charset哪个生效?HTTP响应头的Content-Type: text/html; charset=xxx优先级更高——浏览器先看HTTP头,如果HTTP头没有明确charset,才看HTML内的meta声明。但注意BOM优先级最高:文件开头的UTF-8 BOM会覆盖包括HTTP头在内的一切声明。所以"改了meta还是乱码"要查两个地方:HTTP头里的旧charset、文件是否带BOM
数据库已经是
UTF-8,为什么页面还乱码?- 存储编码和连接编码是两回事。表字符集是
UTF-8只保证数据以UTF-8存,但写入时服务器按character_set_client(连接时声明的客户端字符集)解释应用发来的字节——如果连接用的还是latin1或GBK,写入时就错了。MySQL排查:character_set_server(服务器默认)、character_set_database(当前库)、character_set_client/connection/results(连接链路)。统一SET NAMES utf8mb4+ 建库指定utf8mb4通常能解决
- 存储编码和连接编码是两回事。表字符集是
🤔 网站访问很慢,该如何排查?
网站访问慢的排查要遵循"分层定位"思路:把一次访问拆成多个阶段,先定位慢在哪一层(DNS?网络?服务器?数据库?),再针对该层深入。不要一上来就盯着服务器 CPU——很多"网站慢"其实是网络或前端问题。
第一步:先量化"慢"在哪——用
curl计时curl -o /dev/null -s -w 'DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB:%{time_starttransfer}s\nTotal: %{time_total}s\n' https://example.com重要:这些变量是"从
curl启动到该阶段完成的累计时间",不是各阶段本身耗时。要算阶段耗时需做差:DNS解析耗时 =time_namelookupTCP握手耗时 =time_connect−time_namelookupTLS握手耗时 =time_appconnect−time_connect- 服务器处理时间 =
time_starttransfer−time_appconnect(近似)
TTFB(time_starttransfer):从curl启动到收到第一个字节的累计时间,包含DNS查询 +TCP握手 +TLS握手 + 服务器处理(MDN定义)判断方法:
DNS差值长 →DNS问题;TCP差值长 → 网络问题;DNS/TCP/TLS各阶段差值都正常但TTFB仍长 → 服务器处理慢;Total长但TTFB正常 → 响应体传输慢(带宽/大文件)
第二步:按阶段定位
DNS慢:dig + time = 2 example.com看解析耗时,dig @8.8.8.8 example.com对比公共DNS;检查本地DNS服务器、递归解析器、上游权威、DNSSEC;考虑DoH/DoT。注意:智能DNS/CDN解决的是"解析到哪个节点"的调度准确性,不直接加速解析耗时本身- 网络慢:
ping看RTT,mtr看中间链路是否有丢包或高延迟节点;跨地域访问考虑CDN。2025新坑:HTTP/3走UDP,部分网络对UDP做QoS限速/防火墙拦截——如果time_connect长但ping正常,可怀疑UDP被限速 - 服务器响应慢(
TTFB高) :进入服务器端排查(第三步) - 传输慢(
Total−TTFB长) :响应体太大、带宽不足、压缩未开(gzip/brotli)、大图片未优化
第三步:服务器端深入
Nginx 层:
- 先确认日志格式包含耗时字段:默认
combined格式没有$request_time,需要在log_format中显式配置,如log_format main '$remote_addr $request_time $upstream_response_time';,然后 awk '{print $2}' access.log | sort -n | tail -20看最慢的请求(注意取的是配置里的耗时字段列) - 检查反向代理超时配置(
proxy_read_timeout等)、upstream是否有慢节点
- 先确认日志格式包含耗时字段:默认
应用层:
- 看应用日志的请求处理时间,定位是哪个接口慢
- 检查应用是否有死锁、线程池打满、
GC频繁(Java)、GIL阻塞(Python)
数据库层:
- 开慢查询日志,看是否有全表扫描、缺索引的
SQL SHOW PROCESSLIST看是否有长时间运行的查询- 检查数据库连接池是否打满(连接等待导致
TTFB升高)
- 开慢查询日志,看是否有全表扫描、缺索引的
缓存层:
- Redis/缓存是否生效(缓存命中率低会导致每次都打数据库)
- 缓存穿透/击穿/雪崩
资源层:
top/free看CPU、内存是否打满iostat看磁盘IO是否瓶颈- 连接堆积用
ss -s(汇总统计)或ss -ant state established | wc -l看连接数(注意ss -lntp只显示监听中的socket,看不到ESTABLISHED堆积;-lntp中Send-Q列可看backlog溢出)
第四步:前端/浏览器侧(如果服务器很快但用户觉得慢)
Chrome DevTools的Network面板:看每个资源的加载时间,Waterfall(瀑布图)能看出哪些资源是瓶颈Core Web Vitals(2025年核心指标) :LCP(最大内容绘制)、INP(交互延迟,2024年取代FID)、CLS(布局偏移)——用Lighthouse或RUM(CrUX字段数据)审计- 常见前端问题:页面引用了太多大资源、图片未优化、未用
CDN、缓存策略未配置 - 注意:
HTTP/2时代"JS/CSS合并文件"已不必要(多路复用下合并反而有害),现代实践是bundling+code splitting+tree-shaking+ 按需加载 - 补充:
103 Early Hints可提前TTFB、Server-Timing响应头可暴露服务端耗时
协助记忆
- 分层定位口诀:先
curl计时做差分层,再按层深挖 ———DNS慢查解析,TCP慢查网络,TTFB慢查服务器,Total慢查传输 curl变量是累计值,算阶段要"做差":TCP=connect−namelookup,TLS=appconnect−connect,服务器 =starttransfer−appconnect- 排查顺序:网络层 → 服务器层 → 数据库层 → 缓存层 → 前端层,别跳过
- 分层定位口诀:先
进阶思考
TTFB高但服务器CPU/内存都正常,可能是什么原因?TTFB包含网络RTT+ 服务器处理。如果CPU/内存正常,可能的隐藏原因:一是数据库慢查询——请求卡在等数据库返回,但数据库CPU不高(如缺索引全表扫描、锁等待);二是外部依赖慢——应用调第三方API,等外部响应;三是连接池/线程池打满——请求排队等待可用连接;四是磁盘IO——日志或缓存写盘慢。排查:看应用日志确认请求在等什么,必要时用APM(链路追踪)定位耗时分布
用户分布广(全国/全球),如何区分是"网站慢"还是"用户网络慢"?
- 用多点拨测——在不同地域的机器上执行同样的
curl计时(云厂商都有多地拨测工具)。如果所有地域TTFB都高 → 服务器端问题;如果只有某些地域高 → 网络/CDN节点问题(该地域到服务器链路差,或CDN在该地域节点少)。这是"网站慢"与"网络慢"的经典区分方法
- 用多点拨测——在不同地域的机器上执行同样的
🤔 QPS、TPS、PV、UV 是什么?怎么统计?
QPS/TPS是实时性能指标(衡量系统处理能力),PV/UV是业务流量指标(衡量用户访问量)。两者维度不同,但常放在一起讨论。QPS(Queries Per Second,每秒查询数)含义:系统每秒处理的请求/查询数量,衡量系统吞吐能力
注意:
QPS在不同语境含义不同 ———Web场景指每秒HTTP请求数(严格说这是RPS,Requests Per Second),数据库场景指每秒SQL查询数。中文语境常混用,面试和讨论中要明确语境统计方法:
- 监控系统:
Prometheus+nginx-prometheus-exporter(从nginx stub_status取数,nginx_http_requests_total速率为QPS)或应用埋点 Nginx access log:按时间窗口统计日志条数- 压测工具:
ab、wrk、JMeter直接报QPS
- 监控系统:
相关概念:峰值
QPS(扩容依据,行业更常用最大5分钟窗口均值或TP99,而非秒级瞬时毛刺值)vs 平均QPS(容量规划参考)
TPS(Transactions Per Second,每秒事务数)- 含义:系统每秒处理的事务数量。事务是业务层面的完整操作,一个事务可能包含多个请求/查询
- 典型:一次下单 = 一个事务,但可能包含"查库存 + 扣款 + 生成订单 + 通知"等多个请求
- 通常
TPS≤QPS(一个事务由多个请求组成时),但取决于事务定义与接口粒度——批量/批处理接口(一个HTTP请求处理N笔转账)会出现TPS>QPS - 统计方法:应用埋点(在事务完成点打点)、
APM工具 - 适用:电商下单、支付、银行转账等有明确"事务边界"的业务
PV(Page View,页面浏览量)含义:页面被浏览的次数。每次刷新/打开页面都算一次
PV,同一用户重复浏览累加统计方法:
Nginx access log:统计HTML页面请求数(需过滤静态资源、排除非2xx、过滤爬虫)- 前端埋点 / RUM(真实用户监测)
爬虫污染:
2025年AI爬虫(GPTBot、ClaudeBot等)激增,严重污染PV/UV统计,行业标准做法是UA白名单过滤注意
PV↔QPS不能直接换算:1次PV(页面加载)通常产生1个HTML+ 数个静态资源/接口请求(1 PV≈5~20个请求);容量规划估算:峰值QPS≈(日均PV× 页面平均请求数 × 峰值系数)/86400
UV(Unique Visitor,独立访客数)含义:去重后的独立访客数,同一用户只算一次
统计方法(去重口径,不同口径结果不同):
- 基于
Cookie/ 第一方标识:给访客分配唯一ID去重。2025年隐私环境下的主流已演变为"第一方Cookie+ 登录ID+ 设备指纹"的混合识别(GA4等基于first-party标识去重)——— 因为第三方Cookie在Safari/Firefox默认拦截、GDPR/个保法限制下受限(Chrome 2024年放弃强制淘汰第三方Cookie、2025年终止Privacy Sandbox计划) - 基于
IP:按IP去重(不准确 ——NAT后多人同IP算1个;同一人换IP算多个) - 基于用户登录
ID:最准确,但只能统计登录用户
- 基于
注意:
UV是"人"的维度,PV是"次"的维度。PV/UV比值 = 人均浏览页数 ——— 不能简单解读为"内容吸引",比值高也可能因导航差反复点击、长文分页;需结合跳出率、停留时长解读
四个指标的维度对比
QPS:系统处理能力(实时、按请求数)TPS:业务事务处理能力(实时、按事务数)PV:页面访问量(累计、按次数)UV:独立访客数(累计、按人去重,与QPS无换算关系)
协助记忆
QPS是"每秒能扛多少请求",TPS是"每秒能完成多少业务",PV是"被看了多少次",UV是"有多少人来看"QPS/TPS看系统强不强,PV/UV看业务火不火PV/UV比值:人均看几页,需结合跳出率解读
进阶思考
如何从
Nginx access log统计QPS和PV?combined格式时间戳如10/Feb/2025:14:23:45,$4是完整时间戳。按分钟统计:awk '{print $4}' access.log | cut -d: -f2-3 | sort | uniq -c(必须sort再uniq,多worker写日志时同分钟行不连续,不sort会重复计数);按秒看峰值:cut -d: -f2-4 | sort | uniq -c | sort -rn | head。统计PV:过滤静态资源 + 非2xx+ 爬虫 ———grep -E '\.(html|htm)'在URL带query string或动态路由站点会失效,更可靠的是按URL类型/路径前缀区分或用GoAccess、ELK等分析工具。QPS更推荐用监控系统(nginx-prometheus-exporter)而非日志
UV统计中"基于Cookie“和"基于IP“哪个更准确?为什么行业多用Cookie?- 基于
Cookie更接近真实人数,是行业主流——因为IP在NAT场景下多人共享一个IP(会被算成1个UV),而同一用户在不同网络下IP会变(会被算成多个UV),IP口径误差大。Cookie的问题是用户清浏览器缓存会重新计数(略微高估)、禁Cookie则无法统计。2025年隐私环境下的主流是"第一方Cookie+ 登录ID+ 设备指纹"混合识别(GA4基于first-party标识去重),第三方Cookie因Safari/Firefox拦截和法规限制已不可依赖
- 基于
扩展信息
·
Apache HTTP Server自带的基准测试工具,单进程模型(-c靠fork子进程,非线程),无HTTP/2- 官方自认"未完整实现
HTTP/1.x"、解析脆弱——定位是快速冒烟测试(“给你当前Apache安装表现的一个印象”),不适合严谨压测 - 适用:随手测一下吞吐量。
2025年已属过时,正式压测选下面这些
wrk/wrk2wrk:多线程 +epoll/kqueue事件驱动,单颗多核CPU即可产生高负载,LuaJIT脚本定制请求- 维护状态:长期未实质更新(
wrk停留在2014年前后);wrk2是事实上的标准改进fork——— 恒定吞吐(-R参数)+HdrHistogram精确延迟记录,修复"协调遗漏(Coordinated Omission)",可报99.9999%分位延迟 - 适用:命令行快速吞吐/延迟测试;要正确的高分位延迟用
wrk2
JMeterApache开源、纯Java的全功能负载测试工具,从Web扩展到JDBC/JMS/FTP/LDAP/TCP等十余种协议- 图形化
Test IDE+CLI无头模式 + 动态HTML报告,Groovy/JSR223脚本化,高度可扩展 - 注意:官方明确”
JMeter is not a browser” ——— 协议层工作,不执行JS - 适用:功能/负载测试一体化、多协议、团队
GUI协作(重量级)
现代压测工具(2025 年主流)
k6(最活跃):Go内核 +JS脚本"tests as code",HTTP/WebSocket/gRPC/Browser,阈值/SLO、CI集成,2025年事实上的新一代主流Locust(活跃,Microsoft赞助):纯Python写场景,gevent协程 +Web UI+ 分布式,适合高并发用户模拟Vegeta:Go,恒定速率 +UNIX组合式CLI+Go库,内置Prometheus exporterhey:Go单文件,自称 “ApacheBench (ab) replacement",HTTP/2、限速、CSV输出,小而够用oha(活跃):Rust+tokio,实时TUI,HTTP/2/3(实验)、burst、--latency-correction(同样修复协调遗漏)- 高分位延迟的正确性:
wrk2/Vegeta/oha都处理协调遗漏,ab和wrk不处理
APM(Application Performance Monitoring,应用性能监控)- 核心能力四件套:链路追踪(跨服务请求流、瓶颈、根因、依赖分析)、性能剖析(方法级
CPU/内存profiling)、错误监控(异常捕获、聚合、告警)、指标 + 仪表盘 + 告警 Jaeger:CNCF毕业项目,纯分布式追踪平台,已发v2、原生拥抱OpenTelemetry(OTLP、ClickHouse存储后端) ——— 轻量自托管选它SkyWalking/Pinpoint:Java agent无侵入字节码注入,覆盖trace+ 指标 + 日志 +profiling的全栈APM——— 全栈自托管选它们Datadog APM/New Relic:商业SaaS,自动埋点 + 分布式追踪 + 错误监控 + 持续剖析 +SLO/告警的托管全栈——省事省运维选它们
- 核心能力四件套:链路追踪(跨服务请求流、瓶颈、根因、依赖分析)、性能剖析(方法级
一句话选型:快速冒烟测试用
ab;命令行脚本化压测用wrk/wrk2、hey;CI常态化压测首选k6;Python团队用Locust;多协议/团队协作用JMeter;自托管链路追踪用Jaeger;全栈APM自托管用SkyWalking。
🤔 简述 HTTP 和 HTTPS 区别?
HTTP和HTTPS的核心区别:HTTPS是在HTTP与TCP之间插入了一层TLS(旧称SSL)加密层,保证传输安全。HTTP是明文传输,HTTPS是加密传输。HTTP(HyperText Transfer Protocol)- 明文传输:请求和响应内容(
URL、请求头、Cookie、表单数据、响应体)在网络中以明文传输,可被中间人抓包直接读取 - 默认端口
80 - 无身份验证:无法确认你连的服务器就是目标服务器(可能被
DNS劫持/中间人冒充) - 无认证性完整性保护:数据可能被中间人篡改而不被发现(
TCP校验和只能发现传输错误,防不了恶意篡改) - 适用:非敏感场景。如今内网也推荐
HTTPS(零信任趋势)
- 明文传输:请求和响应内容(
HTTPS(HTTP over TLS)加密传输:在
HTTP和TCP之间加了一层TLS(Transport Layer Security,旧称SSL) ,数据加密后传输默认端口
443提供三方面安全能力:
- 机密性:内容加密,中间人抓包只能看到密文(但
SNI域名与流量元数据仍可见,这正是ECH要解决的——见下文) - 完整性:
AEAD认证加密(TLS 1.2及以前用MAC)保证数据未被篡改 - 身份认证:通过服务器证书验证服务器身份,防止中间人冒充
- 机密性:内容加密,中间人抓包只能看到密文(但
证书机制:服务器需要部署
CA(证书颁发机构)签发的证书,包含公钥和服务器身份信息,客户端验证证书的信任链
HTTPS的建立流程(简版)TCP三次握手 →TLS握手(协商加密套件、交换密钥、验证证书)→ 加密的HTTP通信TLS 1.3首次完整握手1-RTT;PSK会话恢复通常仍为1-RTT;开启early data(0-RTT)可零往返发送幂等请求,但有重放风险、无前向保密,规范要求默认不启用(部分CDN对QUIC 0-RTT会开启)
性能差异
HTTPS比HTTP多一次TLS握手开销(首次连接多1~2个RTT:TLS 1.3为1-RTT、TLS 1.2为2-RTT)- 现代优化(
TLS 1.3、会话恢复、HTTP/3+QUIC 0-RTT)已让差异很小
应用现状(截至 2026 年)
HTTPS已是绝对主流:主流浏览器对HTTP站点显示"不安全"警告,搜索引擎降权HTTP站点;TLS 1.3自2018发布以来已广泛部署,TLS 1.2是主要回退版本- 免费证书普及:
Let's Encrypt提供免费自动化证书(certbot),HTTPS部署成本几乎为零 HSTS强制浏览器只走HTTPS——— 注意其生效前提是首次请求已走HTTPS(或加入preload list),否则第一个请求仍可能被SSL-strip降级ECH(Encrypted Client Hello,2026年3月标准化为RFC 9849) :加密整个ClientHello(含SNI域名),配合DoH防域名泄露——解决"抓包只见密文但域名仍可见"的最后一块短板。Chrome/Firefox已默认启用,OpenSSL 4.0、nginx已支持HTTP/3基于QUIC,本身就要求TLS 1.3加密 ——— 现代Web已无"明文HTTP/3“这回事
协助记忆
- 一句话:
HTTPS=HTTP+TLS加密层,明文变密文 - 三个安全能力:机密性(加密)、完整性(防篡改)、身份认证(防冒充)
- 端口记忆:
80明文,443加密 - 类比:
HTTP是明信片(谁都能看内容),HTTPS是密封信(只有收发双方能看)
- 一句话:
进阶思考
HTTPS能防止所有攻击吗?- 不能。
HTTPS保护的是传输过程,不保护端点本身 ———Web应用漏洞(SQL注入、XSS)发生在应用层,HTTPS管不了;钓鱼网站本身就用HTTPS(有合法证书),HTTPS只证明"你是连到了证书对应的服务器”,不证明"这个服务器是可信的”。HTTPS解决的只是"传输中不被偷看、篡改、冒充"
- 不能。
为什么
HTTP/3没有明文版本?HTTP/3基于QUIC,QUIC在设计上强制内置TLS 1.3(加密是协议的一部分,不是可选项)。这和HTTP/2不同 ———HTTP/2虽然规范支持h2c(明文),但浏览器从未实现明文HTTP/2。所以到了HTTP/3时代,明文HTTP事实上只剩HTTP/1.1还在用
🤔 Session 共享是什么?有哪些实现方式?
Session是服务器端保存的用户会话状态(登录状态、购物车、临时数据)。单机部署时Session存在本机内存即可;多实例部署时,用户的请求可能落到不同的服务器——如果Session只存在某台服务器上,落到其他服务器的请求就"不认识"这个用户了。Session共享就是把Session从单台服务器内存中拿出来,让集群中所有服务器都能访问同一份会话状态。为什么需要
Session共享- 单机时代:
Session存在本机内存,一个进程处理所有请求,天然可用 - 集群时代:多台服务器 + 负载均衡,请求会分散到不同机器,
Session存在A机器,请求落到B机器就丢失登录状态 - 水平扩展:缩容掉持有
Session的那台机器,用户就被登出
- 单机时代:
实现方式一:
Session复制(已过时,不推荐)- 各服务器之间互相复制
Session。Tomcat集群两种实现:DeltaManager(all-to-all,每台机器保存全量Session)和BackupManager(只复制到一台备份节点,主备模式) - 细节:成员发现/心跳走
multicast(默认228.0.0.4:45564),会话数据复制走TCP——— 大集群下all-to-all TCP复制 +multicast心跳的网络开销剧增 - 已基本被淘汰,适合小集群
- 各服务器之间互相复制
实现方式二:
Session Sticky(粘性会话,治标不治本)- 负载均衡器把同一个用户的请求始终转发到同一台服务器(基于
IP hash、Cookie或URL参数) - 优点:实现简单,
Session仍可存在单台机器内存 - 缺点:只是"绕开"了问题而不是解决——某台服务器宕机,粘在上面的用户全部掉线;扩容/缩容会破坏
hash映射(用一致性哈希可大幅减少重映射,如Nginx hash ... consistent);无法做到真正的负载均衡(热点用户集中在一台机器) - 定位:不推荐作为唯一会话方案(云原生场景如
K8s ingress/ALB中sticky仍是常规实践,但通常与集中共享叠加使用)
- 负载均衡器把同一个用户的请求始终转发到同一台服务器(基于
实现方式三:集中式
Session存储(主流方案)- 把
Session从各服务器内存中抽出来,存到一个集中存储(Redis/Memcached) - 所有服务器从同一个地方读写
Session,天然共享 Redis方案(最常用):支持TTL过期,正好匹配Session的过期机制;读写快、支持集群- 框架支持:
Java的Spring Session(支持Redis/JDBC,切换不改应用代码)、Hazelcast/Infinispan(社区扩展)、云托管Redis - 优点:服务器无状态、支持水平扩展、单台服务器宕机不影响其他机器
- 注意点:
Redis是新的单点——本身需要高可用(主从、哨兵、集群);Session是热数据,Redis内存开销需要考虑
- 把
实现方式四:客户端存储 / 无状态
Token(趋势方案)不把会话状态存在服务器,而是放到客户端:
JWT(JSON Web Token):签名后的token存在客户端(localStorage/Cookie),服务器验证签名即可,无需存储Session。注意"无法主动失效"是简化说法——严格可用黑名单/jti/版本号实现服务端撤销,但代价是重新引入状态- 签名/加密
Cookie直接存会话数据(如Rails cookie_store、Flask、Django signed_cookies):注意这是"消除共享需求"而非"共享"——数据在Cookie里服务端已无状态,不应再叫Session;受Cookie约4KB大小限制
优点:服务器完全无状态、天然支持分布式、不需要额外
Session存储缺点:
JWT主动失效难、token泄露后有攻击窗口、体积比Session ID大适用:
API/前后端分离场景主流
2026年演进补充OIDC/SSO(企业认证标准,基于JWT)成为多应用统一登录的主流方案Passkeys/WebAuthn(无密码认证)兴起,减少对传统Session/Cookie的依赖- 第三方
Cookie逐步淘汰对依赖Cookie的Session/Sticky方案有冲击;SameSite默认Lax影响跨站Cookie会话
方案对比速览
Session复制:已淘汰(同步开销大)Sticky:临时过渡(宕机掉线、不均衡)Redis集中存储:主流(服务器无状态、支持扩展)JWT无状态:趋势(完全无状态、但难失效)
协助记忆
Session共享的本质:把"存在单台机器内存里的会话"搬到"所有机器都能访问的地方"- 一条主线:
Session存储位置从"服务器内存"→“集中存储(Redis)"→“客户端(JWT)",越来越无状态 - 选型:传统
Web应用用Redis集中存储(Spring Session);前后端分离/API用JWT
进阶思考
Redis存Session和JWT怎么选?- 看业务需求。需要主动失效(登出、踢人、封禁)、需要服务端可控(管理在线用户)、传统服务端渲染应用 →
Redis存Session(服务端可随时删Session);需要跨域/多端无缝(小程序、App、Web共享登录)、追求服务端零存储、API场景 →JWT。混合方案也常见:JWT做认证 +Redis做登出黑名单
- 看业务需求。需要主动失效(登出、踢人、封禁)、需要服务端可控(管理在线用户)、传统服务端渲染应用 →
Session Sticky和Session共享是二选一吗?- 不是,
Sticky是"让请求总去同一台机器"来规避共享问题,Session共享是"让所有机器共享同一份状态”。两者可以叠加:生产常见做法是Sticky保性能 + 集中共享存储保故障转移——正常时请求粘在本地减少跨节点访问,节点宕机时共享存储兜底。但只依赖Sticky的方案(宕机全掉线)不可接受
- 不是,
🤔 Tomcat 8005、8009、8080 端口分别作用是什么?
Tomcat的三个经典端口各有分工:8005是Shutdown(关闭)端口、8009是AJP端口(与Apache httpd集成)、8080是HTTP端口(对外服务)。都在conf/server.xml中配置。8005:Shutdown(关闭)端口- 作用:用于关闭
Tomcat的端口。向该端口发送特定的SHUTDOWN命令字符串,Tomcat会优雅关闭。它只接受一个固定的SHUTDOWN字符串,没有其他管理能力(真正的管理机制是JMX,默认随机端口) - 配置:
<Server port="8005" shutdown="SHUTDOWN">,关闭命令字符串默认为SHUTDOWN - 现状:
8005在Tomcat 9/10/11的默认server.xml中始终启用,且默认只监听localhost(127.0.0.1)。“禁用"是社区加固建议,不是版本默认行为 - 安全注意:如果配置不当绑定到
0.0.0.0或防火墙未限制,任何人连接8005发SHUTDOWN就能关掉Tomcat(DoS) - 加固方式(注意别把
Tomcat停不掉):- 确保监听
127.0.0.1、修改shutdown字符串 - 用
port="-1"完全禁用该端口 ——— 但注意catalina.sh stop默认正是通过8005发SHUTDOWN,禁用后必须:设置 ·(此时catalina.sh走kill路径)、或用jsvc/Apache Commons Daemon停止,否则Tomcat无法优雅停止
- 确保监听
- 作用:用于关闭
8009:AJP端口(与Apache httpd集成)- 作用:
AJP(Apache JServ Protocol)连接器端口,用于Tomcat与前端Apache httpd之间的通信(通过mod_jk或mod_proxy_ajp)。注意Nginx不支持AJP———Nginx与Tomcat集成应走HTTP(proxy_pass) - 场景:
Apache httpd作为前端静态服务器 +Tomcat处理动态请求,两者通过AJP通信 - 配置:
<Connector port="8009" protocol="AJP/1.3" />———8.5+默认server.xml中8009已注释(需手动启用,默认示例address="::1") Ghostcat漏洞(CVE-2020-1938) :AJP端口未限制时可读取webapps下任意文件甚至执行代码。修复版(9.0.31/8.5.51+)起默认要求secret(secretRequired默认true)且默认只监听loopback- 现状:
AJP未被官方废弃(Tomcat 10/11仍支持),但主要存在于历史遗留架构(Apache httpd+Tomcat集成);与HTTP相比对HTTP/2等现代特性支持不足
- 作用:
8080:HTTP连接器端口(对外服务)- 作用:
Tomcat对外提供HTTP服务的默认端口 - 配置:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> redirectPort="8443":当应用配置了SSL要求(web.xml中security-constraint且transport-guarantee=CONFIDENTIAL)时,HTTP请求自动重定向到8443(HTTPS端口)- 默认
8080而非80:Unix下1024以下端口需root权限而Tomcat默认非root运行(Windows无此限制),兼为避免与既有Web服务冲突
- 作用:
Tomcat 10/11的变化(2020年后)- 最大变化:
javax.servlet→jakarta.servlet包名迁移(Tomcat 10起),老应用需改包名才能运行 - 三个端口的机制与默认配置不变:
8005/8080默认启用、8009默认注释
- 最大变化:
协助记忆
- 三个端口一句话:
8005关Tomcat(管理)、8009连Apache(AJP集成)、8080给用户(HTTP服务) 8080→8443重定向:HTTP强制跳HTTPS时用(redirectPort)- 安全三查:
8005别绑外网(禁用前先配CATALINA_PID)、8009别裸奔(Ghostcat)、8080按需开放
- 三个端口一句话:
进阶思考
8005端口关闭命令的安全性如何保障?8005的SHUTDOWN默认字符串是固定的SHUTDOWN,一旦端口可达任何人都能关掉Tomcat。保障手段:确保监听127.0.0.1(默认);防火墙限制本机访问;修改shutdown字符串;最彻底是port="-1"禁用 ——— 但禁用后必须设置CATALINA_PID或改用jsvc,否则catalina.sh stop无法优雅停止(它默认走8005发SHUTDOWN)
为什么现在很多架构不用
8009 AJP了?- 一是
Ghostcat漏洞(CVE-2020-1938)暴露了安全风险;二是现代前端架构变了 ———Nginx已成为主流反向代理,直接用proxy_pass走HTTP(8080)就能完成转发,而且Nginx根本不支持AJP;三是AJP对HTTP/2等现代特性支持不足。AJP的优势(减少解析开销)在现代硬件上可忽略。所以AJP主要存在于Apache httpd+Tomcat的历史架构中
- 一是
🤔 Tomcat 如何性能优化?
Tomcat性能优化要分层进行:JVM参数 → 连接器(Connector)→ 应用层 → 操作系统层。核心原则是"先找准瓶颈再优化”,不要盲目调大参数。第一层:
JVM参数优化- 堆内存:设置
-Xms(初始堆)和-Xmx(最大堆)为相同值,避免运行时动态扩缩堆造成性能抖动。参考值:-Xms2g-Xmx2g(按实际内存调整;容器场景下JDK 10+默认启用UseContainerSupport,-Xmx受容器配额约束) - 元空间:
-XX:MaxMetaspaceSize设置上限,防止元空间无限增长 GC选择:JDK 9+默认G1,适合大堆(4GB+);超大堆(几十GB)追求低延迟用ZGC(JDK 11实验、15生产可用)GC日志:-Xlog:gc*启用GC日志,用于排查GC停顿- 常见误区:盲目调大
-Xmx不一定提升性能,堆过大反而增加GC停顿
- 堆内存:设置
第二层:连接器(
Connector)优化连接器模式:
Tomcat 8.5/9默认NIO(装了tomcat-native时自动用APR);Tomcat 11起默认纯Java NIO。BIO在8.5已移除线程池参数(
Executor):maxThreads:最大工作线程数,默认200。不是越大越好——线程过多导致上下文切换开销增大;建议结合压测确定,一般200~500minSpareThreads:保底存活线程数(Connector属性默认10,Executor上默认25);注意是"保底水位”,线程按需增长到该水位,并非启动即全部创建acceptCount:等待队列长度,默认100。高并发时调大(如500~1000)maxConnections:最大连接数,NIO默认8192
连接超时:
connectionTimeout文档默认60000ms,但随Tomcat发行的默认server.xml显式配置为20000ms——— 如果自定义server.xml未写该属性,实际生效的是60000ms;keepAliveTimeout控制长连接存活时间压缩:
compression="on"开启gzip压缩,减少传输量(对文本类资源效果明显)静态资源:配置资源缓存或交给前端
Nginx处理(推荐)
第三层:应用层优化
- 数据库连接池:配置合理的连接池参数(初始/最大连接数),避免频繁创建销毁连接
- 缓存:热点数据用
Redis/本地缓存,减少数据库压力 - 异步处理:长耗时操作用
Servlet 3.1异步处理,释放线程 - 避免阻塞:
IO操作异步化,减少线程占用
第四层:操作系统层
- 文件描述符:调大
ulimit -n(默认1024太小,Tomcat高并发会报too many open files) - 内核参数:
net.core.somaxconn(配合acceptCount)、net.ipv4.tcp_*相关优化
- 文件描述符:调大
协助记忆
- 优化四层:
JVM(堆 +GC)→ 连接器(线程池 + 超时)→ 应用(连接池 + 缓存)→ 系统(fd + 内核) - 核心原则:先压测找瓶颈,再对症下药;盲目调大参数反而更糟
- 三个默认值:
maxThreads 200、acceptCount 100、maxConnections 8192(NIO)
- 优化四层:
进阶思考
Tomcat线程数(maxThreads)是不是越大越好?- 不是。线程是"资源"而非"能力"——每个线程占用栈内存,线程过多导致内存占用大、
CPU上下文切换开销大(频繁切换反而降低吞吐)。正确做法:用压测工具(JMeter/k6)逐步加压,观察吞吐量拐点,在吞吐不再增长的点设置maxThreads。通常200~500是常见范围,但具体要压测确定。还要配合maxConnections和acceptCount一起看——三者共同决定了并发处理能力
- 不是。线程是"资源"而非"能力"——每个线程占用栈内存,线程过多导致内存占用大、
Tomcat 10/11有哪些值得关注的性能相关变化?Tomcat 10最大变化是jakarta命名空间迁移(javax→jakarta);Tomcat 11对应Jakarta EE 11。真正值得关注的是虚拟线程(Virtual Threads) ———Tomcat 10.1和11都支持(通过StandardVirtualThreadExecutor,需JDK 21+,且默认都不启用、需在server.xml显式配置)。注意概念澄清:Tomcat NIO自6.0起就不是"1线程1连接" ———acceptor+poller线程负责连接,工作线程池处理的是请求而非连接;虚拟线程改变的是"处理请求的工作线程"的实现(平台线程→虚拟线程),让每个请求跑在KB级栈的虚拟线程上,突破maxThreads=200的平台线程上限。仅对IO密集型(阻塞占比高)应用收益明显;CPU密集型无增益;注意JDK 21上synchronized会固定(pin)虚拟线程(JDK 24的JEP 491才解决)、ThreadLocal规模放大问题
🤔 简述 LVS 的三种模式及其工作原理?
LVS(Linux Virtual Server)是Linux内核内置的负载均衡方案,工作在内核空间(第四层,基于IP+ 端口),性能高。LVS有三种工作模式:NAT、DR(Direct Routing)、TUN(IP Tunneling),核心区别在于数据报文如何流转、响应流量是否经过调度器。模式一:
NAT模式(VS/NAT)原理:调度器同时改写请求和响应的
IP地址——请求进来做DNAT(目标地址改为RS(Real Server; 真实服务器)),响应方向做逆NAT(源地址从RS改回VIP,基于连接跟踪的双向改写)报文流转:请求 → 调度器 →
RS;响应 → 调度器 → 客户端。请求和响应都经过调度器特点:
- 优点:
RS可以使用任意操作系统和私网IP,配置简单;是唯一支持端口映射的模式(VIP端口可与RS端口不同) - 缺点:调度器是瓶颈——所有响应都经过它,吞吐受限于网卡带宽 + 连接表/
CPU(每个NAT连接在哈希表占两个节点) RS的网关必须指向调度器
- 优点:
适用:
RS数量少、流量不是特别大的场景
模式二:
DR模式(VS/DR,直接路由)- 原理:调度器只改写数据帧的目标
MAC地址(源MAC也会改写)把请求转发给RS;RS处理完直接把响应绕过调度器返回客户端 - 报文流转:请求 → 调度器 →
RS;响应 →RS→ 客户端(不经过调度器) - 关键配置:
VIP同时配置在调度器和所有RS上 ———RS的VIP配置在lo接口上,并抑制ARP响应(arp_ignore=1、arp_announce=2) - 特点:
- 优点:响应不经过调度器,调度器只处理请求流量,性能高,生产环境最常用
- 缺点:
RS和调度器必须在同一二层网络(同网段);RS端口必须与VIP端口相同;RS需额外配置VIP和ARP抑制 - 适用:大规模、高流量场景(
Web集群主流)
- 原理:调度器只改写数据帧的目标
模式三:
TUN模式(VS/TUN,IP隧道)- 原理:调度器把请求封装在
IP隧道(IP-in-IP,也支持GRE/SIT/GUE)中转发给RS;RS解封装后处理,响应直接返回客户端 - 报文流转:请求 → 调度器(封装)→
RS(解封装)→ 处理 → 响应 → 客户端(不经过调度器) - 关键配置(和
DR一样) :VIP配置在RS的非ARP设备(tunl0/dummy/lo)上,同样要抑制ARP响应 ——— 否则RS会抢答ARP MTU坑(TUN实战第一坑) :IPIP每包+20字节头,MTU 1500网络中DF(不分片)大包会被拒(PMTU问题);内核可配置pmtu_disc关闭让TUN方法分片- 特点:
- 优点:
RS可以跨地域(不在同一网络),响应不经过调度器 - 缺点:需
RS支持隧道协议(modprobe ipip、tunl0 up);封装带来开销和MTU问题;配置复杂度高 - 适用:异地多活、
RS分布在多个机房的场景
- 原理:调度器把请求封装在
三种模式对比速览
NAT:请求和响应都过调度器(调度器瓶颈),唯一支持端口映射DR:请求过调度器,响应直接回(同二层网络,最常用),RS端口必须等于VIP端口TUN:请求隧道封装,响应直接回(可跨地域),RS端口必须等于VIP端口
2026 年生态现状
ipvs至今在内核6.x中活跃维护;kube-proxy的IPVS模式是K8s大规模集群的常用方案- 演进方向:
DPVS(DPDK版ipvs)、Katran/Cilium(XDP/eBPF负载均衡)、ECMP+BGP替代Keepalived/VRRP
协助记忆
- 一句话区分:
NAT是"快递员两头跑"(进出发都经调度器),DR是"只送件不取件"(请求经调度器、响应直达),TUN是"打包寄送"(隧道封装跨地域) DR两个关键:VIP放lo接口 + 抑制ARP(TUN同样要)- 选型口诀:同网段高流量用
DR,跨地域用TUN,简单场景用NAT
- 一句话区分:
进阶思考
DR模式为什么要抑制RS的ARP响应?不抑制会怎样?VIP同时配置在调度器和所有RS上。如果RS不抑制ARP,局域网设备对VIP发起ARP请求时所有RS都会响应(ARP是广播的),导致MAC地址混乱——有的请求被转发到RS,有的被RS直接抢答。抑制ARP(arp_ignore=1、arp_announce=2)让只有调度器响应VIP的ARP,RS的VIP只用于接收转发请求,不对外宣告
LVS和Nginx负载均衡怎么选?LVS工作在四层(IP+ 端口),内核态,性能高(并发连接数可达百万级,注意是并发连接数而非QPS);但只做转发,本身不带健康检查和故障摘除,需Keepalived等外部组件配合。Nginx工作在七层(HTTP),能做URL路由、重写、限流、缓存,但含TLS终止/HTTP解析,开销天然更大(可比对象是Nginx stream四层模块)。生产架构常见组合:LVS(四层入口)→Nginx(七层反向代理)→ 应用服务器,高可用由Keepalived实现 ———VRRP做VIP漂移 + 对RS健康检查并动态增删ipvs规则
🤔 LVS 支持哪些调度算法?
LVS(ipvs)支持的调度算法分为静态和动态两大类:静态算法不考虑后端实时负载,动态算法根据后端当前负载/连接数做决策。截至2026年,ipvs共有14个调度算法(RR/WRR/DH/SH/MH+LC/WLC/SED/NQ/LBLC/LBLCR/FO/OVF/TWOS)。静态调度算法(不考虑后端实时状态)
RR(Round Robin,轮询) :请求依次分发到每个RS,轮流来WRR(Weighted Round Robin,加权轮询) :按权重比例分发,权重高的RS分到更多请求DH(Destination Hashing,目标地址哈希) :按请求的目标IP哈希,相同目标IP的请求始终分发到同一台RS(用于缓存场景)SH(Source Hashing,源地址哈希) :按客户端源IP哈希,相同来源的请求到同一台RS(天然实现会话保持)。注意"始终"是简化说法——RS增删/权重变化会重建哈希表,且SH有sh-port(IP+端口哈希)和sh-fallback(故障回退)两个flagMH(Maglev Hashing,Maglev哈希,Linux 4.18合入) :基于源IP的一致性哈希(Google Maglev论文,NSDI'16),表容量按权重分配,支持mh-port/mh-fallback flag。天然适合会话保持,RS变化时受影响连接少
动态调度算法(根据后端实时负载决策)
LC(Least Connections,最少连接) :把请求分给当前连接数最少的RS。严格语义是"活动连接数加权"(内核公式 (activeconns<<8) +inactconns)WLC(Weighted Least Connections,加权最少连接) :LC+ 权重,按"连接数/权重"最小的原则分配。是ipvsadm的缺省算法(内核层面无默认)SED(Shortest Expected Delay,最短期望延迟) :考虑"活动连接数 + 1"与权重的比值((active+1)/weight),预测哪个RS延迟最短NQ(Never Queue,永不排队) :SED的改进——如果某台RS活动连接数为0,直接分配给第一个空闲者,避免请求排队;否则用SEDLBLC(Locality-Based Least Connections,基于局部性的最少连接) :目标IP哈希 + 最少连接结合,适合Cache集群(相同目标IP优先到同一台RS)LBLCR(带复制的局部性最少连接) :LBLC的改进——每个目标IP对应一个服务器集合(set),集合内按最少连接选、目标IP热度高时集合扩容(注意不是"复制流量",是集合内多台RS可选)FO(Weighted Failover,加权故障转移) :把连接发给当前可用且权重最高的RS——— 类似主备故障转移(注意:不是"失效次数/权重",那是网上的以讹传讹,内核源码就是"选权重最高的dest全给流量")OVF(Overflow-Connection,溢出连接) :优先把连接给权重最高的RS,当活动连接数超过其weight时"溢出"到权重次高者;全部过载则调度失败(无WLC回退);只统计活动连接,不适合UDPTWOS(Power of Two Choices,两随机选择,Linux 6.4合入) :按权重随机抽两台候选RS,比较活动连接数归一化开销,选更空闲的一台
选型建议
- 通用
Web集群、无特殊需求:WLC(ipvsadm默认) - 后端性能差异大:
WRR(静态权重明确) - 需要会话保持:
SH/MH(源地址哈希,RS变化影响小)或配合持久性(persistence) Cache/缓存集群:DH或LBLC(目标地址哈希保证缓存命中)- 防止单台过载:
OVF - 高可用故障转移倾向:
FO
- 通用
协助记忆
- 静态五兄弟:
RR、WRR、DH、SH、MH(“轮询、加权轮询、目标哈希、源哈希、Maglev哈希”) - 动态九兄弟:
LC、WLC、SED、NQ、LBLC、LBLCR、FO、OVF、TWOS - 默认是
WLC(ipvsadm缺省) ,大多数场景够用;有特殊需求(会话保持、缓存命中)再换
- 静态五兄弟:
进阶思考
SH(源地址哈希)和持久性(persistence)都能做会话保持,有什么区别?SH是基于IP哈希的确定性分配 ——— 同一源IP按哈希到同一台RS,不依赖连接状态;但NAT后面的多个用户共享一个IP会被分到同一台(负载不均);且RS宕机时哈希到它的用户受影响(可通过sh-fallback缓解)。持久性(persistence)是基于连接跟踪的超时绑定——在一定时间窗口(默认300秒)内把同一来源的请求绑定到同一台RS,超时重新分配。持久性更灵活(支持故障转移、负载更均衡),是更推荐的会话保持方式
加权算法(
WRR/WLC)的权重怎么确定?- 权重反映
RS的处理能力差异——通常按CPU核数、内存大小或压测结果定。例如两台服务器,一台8核一台4核,权重可设为2:1。注意:权重是相对值不是绝对值;权重相同(如1:1)就退化为普通轮询/最少连接。权重要根据实际压测调整,不能拍脑袋
- 权重反映
🤔 简述 HTTP Cookie 和 Session 区别和联系?
Cookie和Session都是Web应用中记录用户状态的机制,核心区别:Cookie存在客户端(浏览器),Session存在服务端。两者常配合使用 ———Session通过Cookie传递Session ID来识别用户。Cookie(客户端状态)定义:由服务器通过
Set-Cookie响应头下发的小段文本数据,浏览器保存在本地,后续请求自动通过Cookie请求头携带特点:
- 存储位置:浏览器(客户端)
- 容量:约
4KB上限(单个Cookie约4096字节,浏览器实现惯例;注意RFC 6265规范并未强制规定这个数值) - 生命周期:可通过
Expires/Max-Age设置过期时间——持久Cookie或会话Cookie(无Expires/Max-Age的"会话Cookie“与服务器Session是两个概念,仅名字相似) - 作用域:同域(
Domain+Path限定),默认同站发送 - 可被篡改:客户端可修改
Cookie内容(需签名/加密防篡改)
常见属性:
Secure(仅HTTPS)、HttpOnly(禁止JS读取)、SameSite(缓解CSRF)、Path/Domain(作用域)
Session(服务端状态)- 定义:服务器为每个会话创建的状态数据,存储在服务端(内存、文件、数据库、Redis)
- 特点:
- 存储位置:服务端
- 容量:无
Cookie那样的硬限制(受服务端内存/存储限制) - 生命周期:由服务端超时控制(如
30分钟无活动过期),也可主动销毁 - 标识:通过
Session ID(一串随机字符串)识别,Session ID对应服务端存储的会话数据 - 安全性:数据在服务端,客户端无法直接篡改
联系:
Session靠Cookie传递Session ID典型流程:用户登录 → 服务器创建
Session并生成Session ID→ 通过Set-Cookie下发Session ID→ 浏览器存储 → 后续请求自动携带 → 服务器根据Session ID找到对应Session数据Session ID的传递方式:Cookie方式(主流) :Session ID存在Cookie中,自动携带URL重写方式(备用) :Session ID拼在URL参数中(如?jsessionid=xxx)——适用于禁用了Cookie的场景,但有泄露风险(URL可能被记录在日志/历史中)
也就是说:
Session本身不一定依赖Cookie,但实践中几乎都靠Cookie传递Session ID
核心区别对比
- 存储位置:客户端(
Cookie)vs 服务端(Session) - 容量:约
4KB(Cookie)vs 无硬限制(Session) - 安全性:可篡改(
Cookie)vs 服务端可控(Session) - 生命周期控制:客户端可控制 vs 服务端控制
- 性能:每次请求都传输(
Cookie)vs 服务端查询(Session) - 跨域:
Cookie同域限制 vsSession数据本身无域概念(但Session ID靠Cookie传递时仍受Cookie域限制)
- 存储位置:客户端(
2026年演进SameSite默认Lax:自Chrome 80(2020)/Firefox 86/Safari 13.1起默认。精确语义:跨站子资源、fetch/XHR、iframe内请求默认不发送,但顶级导航GET请求仍携带(用户从别的站点击链接进入本站时会带Cookie);且默认值在Chromium系稳定为Lax,其他浏览器略有差异- 第三方
Cookie逐步淘汰:Safari(ITP,2020起)默认全面阻止;Firefox默认启用ETP+Total Cookie Protection(第三方Cookie按站点分区存储,标准模式仅拦截已知追踪器,并非拦截所有);Chrome至今不默认拦截,仅无痕模式或用户设置时拦截,2024年放弃强制淘汰后逐步推出用户选择模式 - 无状态趋势:
JWT等无状态token兴起 ——— 不依赖服务端Session存储,天然适合分布式;但主动失效难。2026年业界出现回调反思——纯JWT存在吊销难、密钥轮换等痛点,部分场景回归"分布式服务端Session(Redis)“或混合方案
协助记忆
- 一句话:
Cookie是"存在浏览器里的便签”,Session是"存在服务器上的档案柜”,Session ID是"打开档案柜的钥匙"(钥匙放便签里) - 钥匙在客户端,档案在服务端:客户端只有
Session ID(钥匙),真正的数据(档案)在服务端 - 安全对比:档案(
Session数据)比便签(Cookie)安全,因为档案在服务器上,便签在用户手里随时可能被改
- 一句话:
进阶思考
Session数据都放服务端,为什么还需要Cookie?不能不用Cookie吗?HTTP是无状态协议,服务器不记得"你是谁"。Session数据虽然存在服务端,但服务器要知道"这个请求对应哪份Session数据" ——— 这个对应关系(·)必须由客户端每次带来。· 是最自然的携带方式(自动发送)。如果不用Cookie,只能靠URL重写传Session ID,但URL易泄露(日志、历史记录、分享链接),所以实践中几乎都用Cookie
Cookie和Session哪个更安全?- 单从"存储"角度
Session更安全 ——— 数据在服务端,客户端碰不到。Cookie的主要风险其实是被窃取(XSS窃取、明文传输窃听、CSRF)而非篡改 ——— 内容篡改在服务端签名校验后危害有限。Session的风险在Session ID失窃(XSS窃取Cookie、中间人窃听)导致会话劫持;注意会话固定(Session Fixation)与窃取机制不同——它是攻击者预先固定一个ID给受害者使用(RFC 6265 §8.4专述)。完整方案需组合:Session ID用HttpOnly+Secure Cookie保护、传输用HTTPS、防XSS(CSP)、SameSite缓解CSRF
- 单从"存储"角度
🤔 网站跨域报错 Access-Control-Allow-Origin 如何解决?
跨域报错是浏览器同源策略(
Same-Origin Policy)的保护机制:浏览器默认阻止网页脚本跨域读取响应。CORS(Cross-Origin Resource Sharing)是让服务器声明"允许哪些来源访问"的机制,报错Access-Control-Allow-Origin说明服务器没有正确声明允许的来源。先理解跨域和同源
- 同源:协议 + 域名 + 端口都相同才算同源。
http://a.com和https://a.com不同源(协议不同)、和http://a.com:8080不同源(端口不同) - 跨域请求:前端页面(
http://a.com)向后端(http://api.b.com)发请求,就是跨域 - 同源策略:浏览器只允许脚本读取同源响应,跨域响应默认被浏览器拦截(注意:请求可能已发出、服务器可能已处理,只是浏览器拦截了响应)
- 同源:协议 + 域名 + 端口都相同才算同源。
两种请求类型(决定处理方式)
- 简单请求:满足特定条件(
GET/HEAD/POST+Content-Type限定值 + 仅safelisted请求头) ——— 浏览器直接发送,服务器只需返回Access-Control-Allow-Origin。注意Content-Type只有三个值算简单请求:application/x-www-form-urlencoded、multipart/form-data、text/plain(application/json会触发预检) - 预检请求(Preflight) :非简单请求(如带
Authorization头、Content-Type: application/json、PUT/DELETE方法)——— 浏览器先发OPTIONS请求探测,服务器必须正确响应OPTIONS(且必须返回2xx)并返回允许的方法/头,才继续发真实请求 2024-2026新机制PNA(Private Network Access) :Chrome从2024年底(localhost场景Chrome 130、内网RFC1918场景Chrome 137)起,对"公网页面 → 内网/本地"的请求无条件强制预检——无论方法、无论mode(甚至no-cors和同源请求),新增Access-Control-Request-Private-Network: true/Access-Control-Allow-Private-Network: true头。2025-2026年"莫名OPTIONS_预检/请求被拦"很大比例是PNA导致
- 简单请求:满足特定条件(
解决方案:后端加
CORS响应头(根本解法)在服务器响应中加:
Access-Control-Allow-Origin: http://a.com(允许的来源)Access-Control-Allow-Methods: GET, POST, PUT, DELETE(允许的方法)Access-Control-Allow-Headers: Content-Type, Authorization(允许的请求头)Access-Control-Allow-Credentials: true(允许携带凭证,值为字面量true;配了它就不能用*)Access-Control-Max-Age: 3600(预检结果缓存时间,减少OPTIONS请求)Access-Control-Expose-Headers(可选):默认前端只能读6个简单响应头,自定义响应头需在此列出
配置位置:
Nginx:add_header Access-Control-Allow-Origin ...Spring Boot:@CrossOrigin注解或全局CORS配置Node.js:cors中间件
注意通配符的坑:
Access-Control-Allow-Origin: *允许所有来源,但不能和credentials(携带Cookie)一起用——用了withCredentials时不允许用*,必须写具体来源
解决方案:
Nginx反向代理(同源化)- 前端和后端域名不同导致跨域时,用
Nginx反代把/api转发到后端,前端只访问同源路径 ——— 从根上消除跨域 - 配置示例:前端访问
http://a.com/api,Nginx把/api代理到http://api.b.com - 优点:不需要改后端代码、避免暴露多个
CORS配置 - 适用范围:前后端域名固定对应时是常见推荐方案;但
API需被多个未知域名、第三方或移动端调用时(SaaS、开放平台),CORS头方案才是唯一可行且是行业标配
- 前端和后端域名不同导致跨域时,用
解决方案:
JSONP(历史遗留方案)- 利用
script标签不受同源策略限制的特点,通过回调函数拿数据 - 缺点:只支持
GET、安全性差(需要服务端配合)、已过时 - 仅用于兼容老系统的特殊场景
- 利用
协助记忆
- 一句话:跨域报错 = 服务器没声明允许这个来源访问
- 两条路:后端加
CORS头(改后端)或Nginx反代同源化(改架构) - 通配符的坑:
*和credentials不能同时用 - 记住预检:带
Authorization/JSON body的请求会先发OPTIONS预检,服务器要正确处理(OPTIONS必须返回2xx)
进阶思考
为什么有时候
OPTIONS请求返回403/404?- 预检请求(
OPTIONS)可能没被正确处理。常见原因:一是网关/防火墙拦截了OPTIONS方法(只放行GET/POST);二是后端没有对OPTIONS返回正确的CORS响应头(Access-Control-Allow-Methods/Headers);三是Nginx的add_header默认只对部分2xx/3xx状态码添加(200/201/204/206/301/302/303/304/307/308),需加always参数才对所有响应码添加——且预检必须返回2xx,OPTIONS返回403时即使加了头也必然失败。排查:用curl手动发OPTIONS请求模拟预检,看响应头是否包含Allow-Methods/Headers且状态码为2xx
- 预检请求(
Access-Control-Allow-Origin: *和withCredentials为什么冲突?- 安全设计。如果服务器返回
*允许所有来源,同时又允许携带凭证,任意站点就能代表用户跨域发带凭证请求并读取响应——比只写不读的CSRF更严重。所以浏览器规定:凭证请求下ACAO必须为具体来源(*判失败),且要配合Access-Control-Allow-Credentials: true。补充:现代浏览器Cookie默认SameSite=Lax,跨站fetch/XHR(非顶级导航)默认不携带Lax Cookie,只有SameSite=None;Secure的Cookie会带上——实际攻击面比"任何恶意网站都能带上Cookie“更小,但"带凭证 + 可读响应"的底线依然不能破
- 安全设计。如果服务器返回
扩展信息
安全响应头是Nginx层可以统一配置的Web安全防护。核心是CSP(Content-Security-Policy,内容安全策略)——— 它告诉浏览器"页面允许加载哪些来源的资源”,CSP是浏览器侧缓解XSS最重要、最有效的防御机制之一,但不能替代输入校验(Sanitization)与输出编码(Encoding)等后端/开发层面的安全措施。CSP是什么CSP通过响应头声明"允许加载什么",浏览器强制执行:不符合策略的资源(脚本、图片、样式、iframe)被阻止加载- 缓解
XSS原理:即使攻击者注入<script>,CSP如果没允许内联脚本/不可信来源,浏览器会拒绝执行 CSP的威胁模型:缓解内容注入(XSS)与页面被恶意嵌入(clickjacking——注意这是UI redress而非"内容注入")
CSP常用指令default-src:大多数fetch类指令的兜底策略。但需注意:frame-ancestors、base-uri、form-action、sandbox等指令不会继承default-src(未指定即默认为无限制)。script-src:脚本来源(最关键的指令)style-src:样式来源img-src:图片来源connect-src:XHR/fetch/WebSocket连接来源(注意它和CORS是两层不同机制——CSP是浏览器端限制,CORS是服务端授权)frame-ancestors:允许哪些来源嵌入本页(防点击劫持,取代X-Frame-Options)object-src:<object>/<embed>来源(建议 ’none’)- base-uri:
<base>标签来源(限制可被篡改的基准URL);form-action:表单提交目标 upgrade-insecure-requests:页面内HTTP请求自动升级为HTTPSreport-to:违规上报(report-uri已废弃,用Reporting-Endpoints+report-to替代)- 源关键字:
'self'、'none'、'unsafe-inline'(不推荐)、'unsafe-eval'(不推荐)、'strict-dynamic'(注意:strict-dynamic应与nonce或hash一起使用。在支持该特性的浏览器中,会忽略传统脚本白名单(如'self'、主机白名单),改由受信任脚本继续加载其他脚本。)、'nonce-xxx'(每次响应随机生成)
CSP配置案例- 基础案例(静态站) :
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none' - 进阶案例(带
CDN和API) :Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' data: https://cdn.example.com; connect-src 'self' https://api.example.com; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'none' - 上线前先开报告模式(只报告不拦截):
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self' Reporting-Endpoints: csp-endpoint="https://example.com/csp-violations" Content-Security-Policy-Report-Only: ...; report-to csp-endpoint; 注意两个坑:Report-Only模式下frame-ancestors不生效(既不拦截也不产生报告);若未配置report-to/report-uri(或Reporting-Endpoints),Report-Only依然在浏览器端生效:它会在开发者工具控制台输出违规警告,并可被前端securitypolicyviolation事件捕获,只是不会向服务器发送HTTP违规上报包。 Nginx配置CSP:add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;always必须加:add_header默认只对200/201/204/206/301/302/303/304/307/308生效,加always才对所有响应码生效(错误页也带CSP)add_header继承坑:子location写了add_header会覆盖父级全部(nginx 1.29.3+可用add_header_inherit merge; 改为追加)
Nginx其他安全头(一起配,注意与 CSP 语义一致)1 2 3 4 5add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; # 与 frame-ancestors 'none' 配套 add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # 仅 HTTPS 站点- 策略一致性:
CSP的frame-ancestors 'none'应配X-Frame-Options "DENY"(或'self' + SAMEORIGIN),两者语义要一致——不要出现"CSP禁止嵌入、XFO允许同源嵌入"的自相矛盾 HSTS的坑:includeSubDomains要求子域全部HTTPS,否则误伤子域;preload需去hstspreload.org提交后才生效X-Frame-Options有效值只有两档(DENY/SAMEORIGIN)———ALLOW-FROM已废弃且会导致整个头被浏览器忽略
- 基础案例(静态站) :
- 进阶思考
- 为什么
'unsafe-inline'不推荐?nonce和它什么关系?'unsafe-inline'允许所有内联脚本执行——XSS注入的正是内联脚本,等于开大后门。完全禁止内联脚本后,页面自带的<script>也会被拦截,需要移到外部.js或用nonce/hash白名单。在必须用内联脚本时,nonce远比'unsafe-inline'安全、且比完全禁止更灵活 ———nonce每次响应随机生成,攻击者无法预知。实操坑:nonce每次响应都变,页面被浏览器/CDN缓存后nonce过期、脚本被误杀——这是高频事故,缓存页面慎用nonce
CSP和X-Frame-Options都能防点击劫持,用哪个?frame-ancestors(CSP)是现代推荐、X-Frame-Options是旧方案。frame-ancestors更灵活(可精确指定多个来源),X-Frame-Options只有DENY/SAMEORIGIN两档。两者同时存在时,CSP优先(仅在enforce模式成立;X-Frame-Options在响应含frame-ancestors时被忽略)。为兼容老浏览器可两者都配,但语义必须一致
- 为什么
🤔 简述 CDN 工作原理?
CDN(Content Delivery Network,内容分发网络)的核心思想:把内容缓存到离用户更近的节点,让用户从"附近的服务器"而不是"远方的源站"获取内容。本质是"空间换时间"——用分布在全国/全球的缓存节点,换取更短的访问延迟。CDN的核心组件- 边缘节点(
Edge Node/POP,Points of Presence) :分布在各地区/运营商的缓存服务器,真正响应最终用户请求 - 中心节点 / 源站(
Origin) :内容的原始来源(你的服务器) GSLB(Global Server Load Balancing,全局负载均衡) :负责把用户引导到"最合适"的边缘节点——通常以智能DNS方式实现,根据用户地理位置、所属运营商、节点负载,返回最优边缘节点IP
- 边缘节点(
CDN工作原理流程(一次访问)- 用户访问
http://www.example.com,发起DNS解析 - 智能
DNS/GSLB调度:DNS服务器根据用户所属网络的递归解析器出口IP判断地理位置和运营商,返回就近的边缘节点IP(北京用户解析到北京节点,电信用户到电信节点。严格说DNS默认看到的是递归解析器出口IP,启用EDNS Client Subnet(RFC 7871)才能获知用户真实IP前缀) - 用户请求打到边缘节点
- 边缘节点判断缓存:
- 命中:边缘节点有缓存且未过期,直接返回
- 未命中:边缘节点回源——向源站请求内容,缓存一份后返回(实际大型
CDN是多级缓存L1边缘/L2区域/L3中心,边缘未命中常先查上层节点而非直接回源)
- 后续同地区用户请求同一内容,直接命中边缘节点缓存
- 用户访问
CDN缓存与回源的关键点- ``TTL
(缓存有效期):由源站响应头控制 ———Cache-Control: s-maxage(共享缓存专用,优先于max-age)、max-age、HTTP/1.0的Expires;也可在CDN平台配置"强制缓存"忽略源站头 revalidation(304 校验) :缓存未命中时,边缘节点先发If-Modified-Since/ETag条件请求回源,源站返回304则复用缓存、不传body——这是缓存回源的重要环节- 缓存刷新:源站内容更新后,需主动刷新(
purge)CDN缓存或等TTL过期 - 回源率 / 缓存命中率(Cache Hit Ratio) :衡量
CDN效率的关键指标——命中率越高,源站压力越小
- ``TTL
CDN的作用- 加速访问:用户从就近节点拿内容,延迟大幅降低(尤其跨地域、跨境场景)
- 减轻源站压力:大部分请求被边缘节点缓存吸收,源站只处理未命中请求
- 抗
DDoS:用海量带宽池(Anycast)吸收流量型攻击、用WAF与限速过滤应用层(CC)攻击,同时隐藏源站IP使其无法被直接打击 - 隐藏源站
IP:用户只和边缘节点通信,源站IP降低暴露面(需配合回源IP白名单)
现状与前沿(2026 年)
- 已是标配(成熟多年) :
HTTP/3(QUIC 2021标准化、HTTP/3 2022标准化,主流CDN 2019年已商用,如今是标配);CDN证书托管/自动续期(用户侧边缘证书 + 源站回源证书是两个独立维度,可选mTLS);基础边缘计算(Cloudflare Workers 2017、Lambda@Edge 2017) - 当前热点(2023-2026) :边缘
AI推理(Workers AI、GPU边缘节点、AI Gateway)、边缘无服务器渲染/SSR(Vercel/Netlify edge runtime)、边缘KV/数据库(Durable Objects等)、SASE/零信任与CDN融合(Cloudflare Zero Trust等)
- 已是标配(成熟多年) :
协助记忆
- 一句话:
CDN= 把内容放到离用户近的地方,就近取货 - 三个角色:边缘节点(附近的分店)、源站(总仓库)、GSLB(导购员,告诉你哪家分店近)
- 类比快递:CDN 是"提前把货放到你楼下的便利店",用户不用每次去总部仓库取
- 一句话:
进阶思考
CDN一定能提升访问速度吗?什么场景反而更慢?- 不一定。
CDN加速依赖"就近取货"——纯动态内容(用户专属页面、实时数据)不适合缓存(核心原因是缓存收益低/需个性化,不完全是"缓存了必然旧数据",一致性可由 TTL 控制);小体量站点用CDN可能增加DNS解析和节点跳转额外开销;跨运营商调度不当反而更慢。适合CDN的是:静态资源(图片、CSS、JS、视频)、大文件下载、API只读数据。补充:现代CDN有动态加速(DSA) ——— 不缓存,但通过优化TCP/TLS、智能路由选路来加速动态请求
- 不一定。
CDN隐藏源站IP就一定安全吗?- 不是绝对的。
CDN隐藏源站IP是"降低暴露面",不是"绝对隐藏"——攻击者仍可能通过:源站直接对外解析的历史DNS记录、邮件头/证书透明度日志、源站自身的外发请求、子域爆破等找到真实IP。真正加固需要:源站只允许 CDN 回源IP访问(回源IP白名单)、关闭源站不必要的对外端口。CDN隐藏IP只是多层防护中的一层
- 不是绝对的。
🤔 四层与七层负载均衡有什么区别?
四层(
L4)和七层(L7)负载均衡工作在网络模型的不同层级,核心区别:L4在传输层(TCP/UDP)基于IP+端口转发,L7在应用层(HTTP)基于内容路由。四层负载均衡(
L4)工作层级:传输层(
OSI第4层),基于IP+ 端口转发,不解析应用层内容(需解析TCP/UDP头)代表产品:
LVS、HAProxy(tcp模式)、云厂商NLB、Katran(Meta开源,XDP/eBPF转发面) ;F5 BIG-IP LTM是L4–L7一体化 · 设备(不能仅归L4);Nginx的stream模块也能做L4转发方式:
NAT、DR(DSR,直接服务器返回——返回流量不经过LB)、隧道(LVS三模式)特点:
- 性能高:内核态或硬件转发,权威性能指标是
pps(每秒包数)而非并发连接数 - 透明:看不到
HTTP内容 - 无法做:
URL路由、内容改写、Cookie会话保持(传统上基于源IP/五元组哈希——注意标准术语是五元组:源/目的IP+端口+协议,Katran即用5-tuple一致性哈希)
- 性能高:内核态或硬件转发,权威性能指标是
TLS:传统软件
L4(LVS、nginx stream透传、HAProxy tcp passthrough)只能透传,不能终止;但现代云L4(AWS/阿里云NLB等,2019年起)支持TLS监听器/终止(多在网卡/硬件卸载,ACM管理证书);另有灰色地带——L4可只读TLS ClientHello的SNI做路由而不解密(nginx stream ssl_preread)健康检查:传统
LVS是TCP端口探测;现代L4(NLB等)同样支持HTTP/HTTPS健康检查与响应码匹配适用:高吞吐场景、
TCP/UDP通用协议、作为七层的入口
七层负载均衡(
L7)工作层级:应用层(
OSI第7层),解析HTTP内容代表产品:
Nginx、HAProxy(http模式)、Envoy/Traefik(云原生/服务网格默认L7)、云厂商ALB特点:
- 功能丰富:
URL路径路由、Header改写、Cookie会话保持、限流、缓存、TLS终止、gRPC/HTTP3支持 - 性能低于
L4(需解析报文) - 能感知应用状态(基于应用层健康检查)
- 功能丰富:
会话保持:基于
Cookie健康检查:
HTTP探测(检查具体URL的响应码,能感知应用是否真的可用)适用:
HTTP/HTTPS业务、需要路由/改写的场景
关键区别对比
- 工作层级:传输层 vs 应用层
- 性能:
L4高(pps指标)vsL7低(需解析) - 功能:
L4简单转发 vsL7内容路由/改写 TLS终止:传统L4只能透传,现代云L4支持 vsL7原生支持- 会话保持:
L4传统基于五元组 vsL7基于Cookie - 健康检查:
L4传统 TCP 探测 vsL7 HTTP探测(现代L4也支持HTTP) - 典型代表:
LVS/KatranvsNginx/Envoy
常见架构组合
- 大型架构:
L4(入口,扛高并发)→L7(应用路由)→ 后端服务器 - 经典组合:
LVS/Katran(四层)→Nginx/Envoy(七层)→ 应用
- 大型架构:
协助记忆
- 一句话:
L4是"快递分拣员"(只看地址不分内容),L7是"前台接待员"(听你需求再安排) L4管"到不到"(IP+端口转发),L7管"给谁"(URL/Host路由)——仅为简化助记,严格说L4也做健康检查、L7路由同样基于IP+端口- 选型:只要转发选
L4,要路由/改写/TLS 终止选L7,大流量先用L4挡一层
- 一句话:
进阶思考
L4能做TLS终止吗?- 分情况。传统软件
L4(LVS、nginx stream透传、HAProxy tcp passthrough)不能 ———TLS终止需要解密报文,L4只转发加密TCP流,只能透传到后端解密。现代云L4(AWS/阿里云NLB等,2019年起)支持TLS监听器,多在网卡/硬件上卸载加解密。所以"HTTPS业务 +TLS终止"传统选L7(Nginx/ALB),但现代架构也可以用支持TLS的云L4做入口
- 分情况。传统软件
四层负载均衡的性能优势有多大?
L4权威指标是pps(每秒包数)———Katran等XDP/eBPF方案在DPDK/网卡卸载下可达千万级pps;L7(Nginx)通常几万到几十万QPS(受HTTP解析和TLS开销限制),数字随硬件/配置浮动。差距来自:L4不解析应用层内容、L7要解析HTTP报文且常做TLS终止。注意量纲:L4的"百万级"通常是并发连接数/pps,L7的"几万"是QPS——— 两者不是同一个量纲,对比要说清楚