Skip to content

应用交付:RPC、WebSocket、代理、负载均衡与 CDN ​

内容摘要:用户访问的通常不是一台裸露的应用服务器,而是一条由域名系统(Domain Name System,DNS)、内容分发网络(Content Delivery Network,CDN)、四层或七层负载均衡器、网关、代理和后端实例组成的交付链路。本章先澄清远程过程调用(Remote Procedure Call,RPC)与 HTTP 的关系,再解释 WebSocket 的握手与帧,最后沿真实数据路径分析代理、负载均衡、健康检查、连接复用、缓存、超时、重试和故障排查。

难度:高级 | 前置知识:13 TCP、15 DNS、16 HTTP、17 TLS | 所属层次:应用层与传输层交界

一、先建立正确边界 ​

1、HTTP 与 RPC 不是同一维度 ​

  • HTTP 是应用层协议,定义请求方法、状态码、字段、缓存等语义以及不同版本的线上编码。
  • RPC 是“像调用本地函数一样调用远端服务”的编程与交互模型,通常还包含接口定义语言(Interface Definition Language,IDL)、序列化、代码生成、服务发现、截止时间和错误模型。
  • RPC 可以运行在 HTTP/2 上,例如 gRPC;也可以使用自定义 TCP 协议。REST 风格接口也常使用 HTTP。

因此,“HTTP 还是 RPC”不是严格二选一。更准确的问题是:

  1. 接口是否面向浏览器、开放生态还是内部服务?
  2. 是否需要 IDL、强类型和代码生成?
  3. 是否需要双向流、多路复用、截止时间传播和统一错误模型?
  4. 中间代理、可观测性、安全设备是否理解该协议?
维度通用 HTTP API典型 gRPC
传输与编码HTTP/1.1、HTTP/2 或 HTTP/3;JSON 等通常 HTTP/2;Protocol Buffers 常见
接口契约OpenAPI 等,可选.proto IDL 与生成代码
浏览器直连支持广泛原生限制较多,常需 gRPC-Web/代理
流式调用SSE、WebSocket、分块响应等客户端流、服务端流、双向流
可读性文本格式便于人工调试二进制高效但需 schema 解码
失败语义HTTP 状态码 + 业务体gRPC status + trailers
适合场景开放 API、Web、跨组织集成内部强契约服务、低延迟流式调用

工程边界:二进制编码不自动意味着端到端更快。总耗时还受排队、序列化、网络 RTT、重试、连接复用、服务端计算和下游依赖影响,必须实测。

2、WebSocket 解决什么问题 ​

WebSocket 是在一条长连接上提供全双工消息通道的应用层协议。它适合聊天、协作编辑、实时状态推送和游戏信令等双向低延迟场景。

它不是唯一的“服务端推送”方案:

方案方向连接特征适用场景主要限制
短轮询客户端主动周期性新请求低频更新、实现简单空请求多、时延受轮询周期影响
长轮询主要服务端响应请求挂起后返回,再重建兼容传统 HTTP 基础设施重连与并发管理复杂
Server-Sent Events(SSE)服务端到客户端HTTP 长响应单向事件流、浏览器自动重连不提供原生双向消息
WebSocket双向长连接、消息帧高频双向交互连接容量、心跳、代理超时需单独治理
WebTransport多流/数据报HTTP/3/QUIC新型实时应用支持度与基础设施成熟度需实测

二、端到端应用交付路径 ​

每增加一跳,都会引入新的:

  • 连接与队列;
  • 超时和重试;
  • 证书与身份边界;
  • 缓存与一致性边界;
  • 日志、指标和追踪点;
  • 故障模式与容量上限。

排障时必须画出实际路径,不能只说“请求经过了负载均衡”。

三、正向代理、反向代理与网关 ​

类型代表谁典型用途服务端看到的直接对端
正向代理客户端出网控制、匿名、缓存、审计正向代理
反向代理服务端TLS 终止、路由、缓存、限流反向代理
API 网关API 提供方认证、配额、协议转换、路由网关
服务网格数据平面工作负载服务间治理、mTLS、遥测sidecar/节点代理,取决于实现

1、代理会终止并重建连接 ​

典型七层反向代理不是简单“转发同一个 TCP 包”:

两段连接可以具有不同的:

  • 地址与端口;
  • TCP 拥塞窗口和重传;
  • TLS 版本、证书与双向认证策略;
  • HTTP 版本;
  • 超时、缓冲与连接池;
  • 抓包内容和故障原因。

客户端抓包正常,不代表代理到后端的连接正常。

2、客户端地址如何传播 ​

七层代理后的后端通常只能从 socket 看到代理地址。若业务确实需要原始客户端信息,可使用:

  • 标准化 Forwarded 字段(RFC 7239);
  • 生态常用但非统一标准的 X-Forwarded-For、X-Forwarded-Proto;
  • 四层场景的 PROXY protocol(实现规范,不是 IETF RFC);
  • 透明代理/保留源地址能力,具体依赖网络与平台。

安全红线:入口代理必须删除或覆盖来自不可信客户端的转发字段。应用只信任由明确代理链写入的位置,否则攻击者可伪造来源地址、协议和主机名。

四、四层与七层负载均衡 ​

维度四层负载均衡(L4)七层负载均衡(L7)
决策信息IP、端口、传输协议、连接状态Host、path、header、cookie、RPC method 等
典型实现NAT、DSR、IPVS、传输层代理Nginx、HAProxy、Envoy、云应用 LB
TLS常透传,也可由其他层终止常在此终止或重新加密
连接转发或代理 TCP/UDP 流解析应用协议,通常两段连接
优点开销低、协议透明、吞吐高路由灵活、可观测性与安全策略丰富
限制不理解业务语义CPU/内存开销更高,协议兼容更复杂
常见故障哈希偏斜、连接跟踪耗尽、回程不对称头部/超时限制、协议升级失败、重试放大

1、转发模式不是一个实现 ​

  • 网络地址转换(Network Address Translation,NAT):负载均衡器改写地址/端口,需要正确处理回程。
  • 直接服务器返回(Direct Server Return,DSR):入站经负载均衡器,响应可直接由后端返回,性能高但网络配置复杂。
  • 全代理(full proxy):负载均衡器分别维护客户端连接和后端连接,便于治理但增加状态和资源消耗。
  • Anycast + ECMP:先把流量引到某个站点或节点,再做本地负载均衡;路由收敛与连接稳定性需单独设计。

五、调度算法与状态 ​

算法决策依据优点风险/边界
Round Robin轮询简单、无状态不感知请求成本与实例容量
Weighted Round Robin静态权重可表达容量差异权重需要持续校准
Least Connections活跃连接数适合连接耗时差异HTTP/2/gRPC 长连接会扭曲连接数含义
Least Request / EWMA请求或延迟更接近实时负载需要可靠采样,可能对抖动敏感
哈希源地址、URI、键稳定映射节点变化导致重映射和热点
一致性哈希哈希环/虚拟节点节点变化时减少重映射仍需处理热点、权重和故障迁移
Power of Two Choices随机选两个取较轻者低开销且均衡性好指标滞后时仍可能偏斜

关键事实:

  • “连接均衡”不等于“请求均衡”。一个 gRPC/HTTP/2 连接可承载大量并发流。
  • “请求数均衡”不等于“资源均衡”。请求成本可能相差几个数量级。
  • 一致性哈希不等于会话不会丢失。节点故障和扩缩容仍会迁移一部分键。

六、健康检查、摘除与慢启动 ​

1、存活与就绪必须分开 ​

  • 存活检查(liveness):进程是否需要重启。
  • 就绪检查(readiness):实例当前是否能接收新流量。
  • 启动检查(startup):慢启动应用是否已经完成初始化。
  • 被动检查:根据真实请求的连接失败、超时或响应码判断。
  • 主动检查:负载均衡器周期性请求专用端点。

2、常见错误 ​

  • 检查端点永远返回 200,却不验证关键依赖和事件循环。
  • 检查过深,把单个非关键下游抖动放大为整个集群摘除。
  • 检查间隔过短、阈值过低,导致实例在健康/不健康间振荡。
  • 恢复后立即满量接流量,没有慢启动(slow start)或预热。
  • 摘除只停止新请求,没有连接排空(connection draining)。

健康检查设计要回答:检查什么、谁检查、从哪里检查、连续几次、多久超时、恢复阈值、摘除后如何排空、监控如何告警。

七、WebSocket:握手、帧与代理 ​

1、HTTP/1.1 Opening Handshake ​

RFC 6455 的 WebSocket 通常从 HTTP/1.1 Upgrade 开始:

http
GET /chat HTTP/1.1
Host: ws.example
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: https://app.example

服务端接受时返回:

http
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Accept 是 Base64(SHA-1(key + GUID)) 的结果,用于证明服务端理解 WebSocket 握手,不是身份认证或数据加密。机密性和服务身份依赖 wss:// 下的 TLS。

HTTP/2 可按 RFC 8441 使用扩展 CONNECT;HTTP/3 对应 RFC 9220。支持能力取决于客户端和代理,不能假设所有链路都支持。

2、帧与消息边界 ​

WebSocket 在 TCP 字节流上定义自己的帧:

字段作用注意事项
FIN是否为消息最后一个片段一个消息可跨多个帧
opcodecontinuation/text/binary/close/ping/pong控制帧有长度和分片限制
MASK载荷是否掩码客户端到服务端必须掩码;这不是加密
payload length7/16/64 位长度编码实现必须限制最大消息和聚合内存
masking key32 位掩码键仅客户端帧携带

3、代理链的生产约束 ​

  • HTTP/1.1 代理必须正确传递 Upgrade 语义;逐跳字段不能机械透传。
  • 空闲超时若小于业务静默周期,连接会被代理主动关闭。
  • Ping/Pong 是协议级保活;业务心跳还可验证应用处理能力,两者不能混用。
  • 负载均衡器按连接调度,长连接会让扩容后的新实例长期接不到均衡流量。
  • 发布时应先停止新连接,再等待存量连接排空,并让客户端使用带抖动的退避重连。
  • 会话状态应外置或可重建;依赖粘性会话会增加故障迁移难度。

八、RPC/gRPC 的数据路径 ​

1、必须一起设计的机制 ​

机制正确问题常见失败
服务发现地址多久更新、连接何时重建?DNS 已变化但长连接仍指向旧实例
截止时间请求最晚何时完成?是否向下游传播?每层独立超时,总耗时失控
重试哪些状态可重试?请求是否幂等?重试写请求导致重复副作用
负载均衡按连接还是按请求/stream?少量长连接导致实例负载偏斜
流控/背压消费方变慢时谁停止读取?缓冲无限增长或尾延迟恶化
错误模型传输错误与业务错误如何区分?把业务拒绝误判成网络故障
观测method、status、deadline、attempt 是否可见?只有平均延迟,无法定位重试放大

2、截止时间、重试与请求放大 ​

假设入口最多重试 2 次,下游也最多重试 2 次,最坏尝试次数不是 4,而可能是:

text
(1 + 2) × (1 + 2) = 9 次下游尝试

正确做法:

  1. 传递一个端到端截止时间,而不是每跳重新给完整超时。
  2. 只对明确的瞬时错误重试。
  3. 预算包含排队、连接、TLS、首字节和重试退避。
  4. 写请求使用幂等键、去重或条件更新。
  5. 采用指数退避(exponential backoff)和随机抖动(jitter)。
  6. 限制每层最大尝试次数和全局重试预算。

九、CDN:缓存不是“离用户近”这么简单 ​

1、基本数据路径 ​

CDN 的收益包括:

  • 减少用户到内容的网络 RTT;
  • 静态内容在边缘命中,降低源站带宽与请求压力;
  • 在边缘完成 TLS、压缩、协议协商和部分安全防护;
  • 通过 Anycast/DNS 调度把客户端引到合适的接入点。

2、缓存键与一致性 ​

缓存键通常至少包含 scheme、host、path、query 的某种组合,也可能包含由 Vary 指定的请求字段。错误缓存键会造成:

  • 不同用户内容互串;
  • 查询参数被忽略导致旧内容;
  • 把认证响应缓存为公开响应;
  • 多种编码或语言互相污染;
  • 缓存碎片化导致命中率下降。

需要同时设计:

  • Cache-Control、Expires、ETag 与条件请求;
  • s-maxage、stale-while-revalidate、stale-if-error;
  • 主动失效(purge/invalidation)的延迟与费用;
  • 源站屏蔽、回源并发合并和热点保护;
  • Range 请求、大对象、流媒体与分片缓存;
  • 签名 URL/Cookie 和防盗链的信任边界。

一致性边界:CDN purge 通常不是跨所有节点的原子事务。发布系统必须允许短时间新旧版本并存,静态资源宜使用内容哈希文件名。

十、TLS 终止与信任边界 ​

模式客户端到边缘边缘到后端风险重点
TLS passthroughTLS 直达后端同一连接/流L7 能力有限,SNI 路由边界
Edge terminationTLS明文 HTTP内网仍可能被窃听/篡改
Re-encryptionTLS新 TLS 连接后端证书、SNI、信任链必须验证
mTLS to originTLS双向 TLS客户端证书轮换与身份授权

不要把“云内网”自动视为可信边界。边缘到源站至少应有网络访问控制和服务身份验证;高风险场景使用 mTLS。

十一、超时、缓冲与连接池 ​

1、超时必须分阶段 ​

  • 上游总截止时间必须大于必要阶段之和,但不能无限大。
  • 代理的后端超时应小于客户端总超时,为错误转换和回退保留预算。
  • 长响应的“总时长”和“空闲时长”是不同约束。
  • WebSocket/gRPC stream 不能套用普通短请求的响应超时。
  • 连接池需要限制总连接、每目标连接、空闲连接、最大连接寿命和等待队列。

2、缓冲的取舍 ​

  • 请求缓冲可保护慢客户端影响后端,但增加延迟和磁盘/内存占用。
  • 响应缓冲便于重试和缓存,但破坏真正流式传输。
  • 禁用缓冲适合 SSE、WebSocket、流式 RPC,但后端会直接承受慢消费者背压。

十二、最小可复现实验:观察两段连接与负载分配 ​

1、环境 ​

  • Linux/macOS/Windows(Docker Desktop 亦可)
  • Docker、curl
  • 实验只绑定 127.0.0.1,不接触生产网络

以下使用 Nginx 开源版演示被动失败处理与轮询。开源版是否支持主动健康检查取决于版本和模块,不能把商业版或云 LB 能力套用到所有环境。

2、启动两个可区分的源站 ​

bash
docker network create app-delivery-lab

docker run -d --rm --name origin-a --network app-delivery-lab \
  hashicorp/http-echo:1.0 -text='origin-a' -listen=:8080

docker run -d --rm --name origin-b --network app-delivery-lab \
  hashicorp/http-echo:1.0 -text='origin-b' -listen=:8080

创建隔离实验配置 nginx.conf:

nginx
events {}

http {
    log_format upstream '$remote_addr "$request" status=$status '
                        'upstream=$upstream_addr upstream_status=$upstream_status '
                        'request_time=$request_time upstream_time=$upstream_response_time';

    upstream app_backend {
        server origin-a:8080 max_fails=2 fail_timeout=5s;
        server origin-b:8080 max_fails=2 fail_timeout=5s;
        keepalive 16;
    }

    server {
        listen 8080;
        access_log /dev/stdout upstream;

        location / {
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header Forwarded "for=$remote_addr;proto=$scheme;host=$host";
            proxy_set_header X-Request-ID $request_id;
            proxy_set_header Connection "";
            proxy_pass http://app_backend;
        }
    }
}

启动代理。生产环境应固定经过安全评估的镜像 digest;下面版本仅为实验示例,执行前以镜像仓库实际可用版本为准:

bash
docker run -d --rm --name edge-proxy \
  --network app-delivery-lab \
  -p 127.0.0.1:18080:8080 \
  -v "$PWD/nginx.conf:/etc/nginx/nginx.conf:ro" \
  nginx:1.27-alpine

重复请求:

bash
for i in 1 2 3 4; do
  curl -fsS http://127.0.0.1:18080/
done

预期结果(具体顺序受连接复用、容器解析与实现影响):

text
origin-a
origin-b
origin-a
origin-b

预期能在代理日志中看到:

  • 客户端直接连接的是 127.0.0.1:18080;
  • upstream 为 origin-a:8080 或 origin-b:8080 的容器地址;
  • 客户端请求时间与后端响应时间是不同指标。

3、注入后端故障 ​

bash
docker stop origin-a

for i in 1 2 3 4; do
  curl -sS -o /dev/null -w '%{http_code}\n' \
    http://127.0.0.1:18080/
done

可能先出现一次 502,随后请求落到 origin-b;是否重试、失败几次后摘除取决于 Nginx 配置与请求类型。不要预先声称所有请求都无损切换。

检查证据:

bash
docker logs edge-proxy
docker inspect origin-b --format '{{json .NetworkSettings.Networks}}'

4、抓包 ​

Linux 上可在 loopback 抓客户端到代理的连接:

bash
sudo tcpdump -i lo -nn -s 0 -c 100 \
  'tcp port 18080'

容器内部或宿主 bridge 上抓代理到后端的第二段连接,接口名因 Docker 实现不同,需先用 ip link 和 docker network inspect app-delivery-lab 确认,不能照抄接口名。

5、清理 ​

bash
docker rm -f edge-proxy origin-a origin-b 2>/dev/null || true
docker network rm app-delivery-lab
rm -f nginx.conf

执行状态:本章 Docker/Nginx 实验未在当前环境执行,输出为预期模式而非伪造实测。执行时应记录镜像 digest、Docker/Nginx 版本、请求次数和完整日志。

十三、测试与验证清单 ​

1、HTTP/RPC ​

  • 验证连接建立、协议协商、序列化和响应状态分别耗时。
  • 验证 deadline 是否跨代理和下游传播。
  • 对读写请求分别测试超时前、超时后、响应中断和重复提交。
  • 检查重试次数、attempt 标记和幂等键。
  • HTTP/2/gRPC 测试单连接多 stream、GOAWAY、RST_STREAM 和流控。

2、WebSocket ​

  • 验证 Upgrade/extended CONNECT 成功与失败。
  • 验证文本、二进制、分片、Ping/Pong、Close 帧。
  • 空闲超过各层超时,确认由谁关闭。
  • 代理重启、实例摘除、网络切换时验证重连退避和消息语义。
  • 限制最大帧、最大消息、连接数和每连接发送速率。

3、负载均衡/CDN ​

  • 正常、慢、拒绝连接、超时、错误码各自如何计入健康状态。
  • 扩缩容后连接与请求是否重新均衡。
  • 缓存键是否包含必要维度,认证响应是否禁止共享缓存。
  • purge 后各区域传播时间与新旧版本兼容性。
  • 回源失败时 stale 策略是否符合数据一致性要求。

十四、常见故障与证据 ​

现象可能层次优先证据
客户端 502代理到后端连接失败/无合法响应代理 access/error log、upstream address/status
客户端 503无健康后端、过载、主动拒绝健康检查、后端池、队列与限流指标
客户端 504代理等待后端超时分阶段时延、后端日志、连接与依赖追踪
WebSocket 立即断开Upgrade 字段、协议不兼容、鉴权失败101 响应、代理配置、Close code
WebSocket 周期性断开空闲超时、NAT 映射过期、心跳缺失断开周期、各层 idle timeout、Ping/Pong
gRPC 偶发 UNAVAILABLE连接迁移、GOAWAY、无健康实例、LB 偏斜gRPC status、attempt、HTTP/2 frame、代理日志
新实例无流量长连接按连接均衡每实例连接数与 stream/request 数
CDN 命中错误内容缓存键/Vary/认证边界错误Age、Via、cache status、请求/响应字段
源站被打满命中率下降、缓存穿透、回源并发无保护hit ratio、origin QPS、collapsed forwarding

排查顺序:

  1. 明确客户端最终错误、请求 ID、时间范围和受影响比例。
  2. 确认 DNS 返回和实际边缘/负载均衡节点。
  3. 分别验证客户端到边缘、边缘到后端两段连接。
  4. 对齐 access log、error log、健康状态、后端日志和追踪。
  5. 检查最近发布、路由、证书、缓存键、超时与重试变更。
  6. 只在获得证据后调整参数;先限流/回滚,再做长期修复。

十五、性能与容量 ​

至少监控:

  • 新连接率、活跃连接、连接复用率、握手时延;
  • 每秒请求/stream、并发、等待队列、拒绝数;
  • 上游连接失败、重置、超时和重试;
  • p50/p95/p99 总时延与 upstream 时延;
  • 各实例请求、连接、CPU、内存和队列分布;
  • CDN 请求/字节命中率、回源率、回源带宽、purge 延迟;
  • WebSocket 连接数、连接时长、消息率、待发送字节和重连率。

容量不能只按 QPS 估算。长连接系统常由连接数 × 每连接内存 + 心跳包速率 + TLS 状态 + 文件描述符决定;大对象 CDN 则可能由带宽、缓存空间和回源峰值决定。

十六、安全风险与加固 ​

  • 只接受可信代理写入的客户端地址字段,防止 IP 伪造。
  • 限制请求行、字段、body、WebSocket frame/message 和 gRPC message 大小。
  • 入口和后端都配置连接、并发、速率和队列上限。
  • TLS 终止后到后端继续验证服务身份;敏感链路使用 mTLS。
  • 防止开放正向代理和未经认证的 CONNECT。
  • CDN 缓存键纳入认证与内容协商边界,避免私有数据泄漏。
  • WebSocket 在握手时验证 Origin、认证与授权;连接建立后每条业务消息仍需授权。
  • RPC 重试必须配合幂等和重试预算,避免放大故障。
  • 对 Host、path、转发字段和协议转换做规范化,防范请求走私与路由绕过。
  • 日志中脱敏 Authorization、Cookie、Token、查询参数和业务负载。

十七、实现与平台差异 ​

  • 云负载均衡的健康检查、调度算法、源地址保留、跨区、连接迁移、配额和计费均属厂商实现,必须查对应产品版本文档。
  • Nginx 开源版、Nginx Plus、HAProxy、Envoy、Traefik 和云网关能力不同,尤其是主动健康检查、动态发现和高级重试。
  • HTTP/2 WebSocket 需要 RFC 8441,HTTP/3 WebSocket 需要 RFC 9220;端到端每一跳都要支持。
  • gRPC 经不理解 HTTP/2 trailers 的代理时可能出现状态丢失。
  • WebSocket、SSE 和流式 RPC 的默认空闲超时在不同代理中差异很大。
  • 四层负载均衡是否保留源地址、是否使用连接跟踪、回程路径要求均依赖转发模式。

十八、关联章节 ​

检索关键词 ​

HTTP RPC 区别 gRPC HTTP/2 deadline retry WebSocket 握手 Sec-WebSocket-Accept WebSocket frame mask ping pong 正向代理 反向代理 API 网关 四层 七层 负载均衡 NAT DSR full proxy 健康检查 readiness draining 负载均衡算法 consistent hash least request CDN 缓存键 回源 purge Forwarded X-Forwarded-For 502 503 504 排障

权威来源 ​

本章于 2026-09-07 完成规范与主题核验。Docker/Nginx 实验因当前未执行而明确标注为预期结果;实际部署需以使用中的代理、云负载均衡和 CDN 版本文档及受控实验为准。

ICP 备案号待补充 · Gitee 主页