健康检查是负载均衡的”安全网”——它决定了一台出问题的服务器多快被摘除、多快被恢复。设计不当的健康检查比没有健康检查更危险:假阳性把健康的服务器误摘除,假阴性把故障的服务器继续留在池中。
本文从工程视角讲解健康检查的设计决策:频率、超时、阈值、检查方式的选择,以及故障转移过程中的连接排空和优雅处理。
一、主动健康检查
1.1 检查类型
主动健康检查由负载均衡器主动发起探测请求到后端服务器。三种常见的检查类型:
TCP 检查:最基础——只检查端口是否可连接。
LB → [SYN] → Backend:8080
LB ← [SYN-ACK] ← Backend:8080 ✓ 健康
LB → [RST] → Backend:8080 (关闭检查连接)
LB → [SYN] → Backend:8080
LB ← [RST] ← Backend:8080 ✗ 不健康(端口未监听)
LB → [SYN] → Backend:8080
(超时无响应) ✗ 不健康(网络不通/进程挂死)
HTTP 检查:检查应用层是否正常——发送 HTTP 请求,验证响应状态码和可选的响应体。
LB → GET /health HTTP/1.1
LB ← HTTP/1.1 200 OK ✓ 健康
{"status": "ok", "db": "ok"}
LB → GET /health HTTP/1.1
LB ← HTTP/1.1 503 Service Unavailable ✗ 不健康
{"status": "degraded", "db": "down"}
gRPC 检查:使用 gRPC Health Checking Protocol(grpc.health.v1)。
// gRPC 标准健康检查协议
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3;
}
ServingStatus status = 1;
}1.2 三种检查类型的比较
| 维度 | TCP 检查 | HTTP 检查 | gRPC 检查 |
|---|---|---|---|
| 检测精度 | 低(只检查端口) | 高(检查应用状态) | 高(检查服务状态) |
| 性能开销 | 最低 | 中 | 中 |
| 能检测到的故障 | 进程崩溃、端口未监听 | + 应用异常、依赖故障 | + 特定服务异常 |
| 检测不到的故障 | 应用死锁、依赖失败 | 响应体不含依赖检查时 | 同 HTTP |
| 适用场景 | 纯 TCP 服务 | HTTP/REST 服务 | gRPC 微服务 |
1.3 健康检查端点设计
一个好的健康检查端点应该区分存活性(Liveness)和就绪性(Readiness):
// Go 示例:分层健康检查
package main
import (
"encoding/json"
"net/http"
"sync/atomic"
"time"
)
var ready atomic.Bool
type HealthResponse struct {
Status string `json:"status"`
Checks map[string]string `json:"checks,omitempty"`
Uptime string `json:"uptime,omitempty"`
}
func livenessHandler(w http.ResponseWriter, r *http.Request) {
// Liveness: 进程是否存活?
// 只要进程能响应就返回 200
// 不检查依赖——依赖故障不应导致进程被杀
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(HealthResponse{Status: "alive"})
}
func readinessHandler(w http.ResponseWriter, r *http.Request) {
// Readiness: 能否处理请求?
// 检查所有关键依赖
checks := make(map[string]string)
allOK := true
// 检查数据库
if err := checkDB(); err != nil {
checks["database"] = err.Error()
allOK = false
} else {
checks["database"] = "ok"
}
// 检查 Redis
if err := checkRedis(); err != nil {
checks["redis"] = err.Error()
allOK = false
} else {
checks["redis"] = "ok"
}
status := "ready"
code := http.StatusOK
if !allOK || !ready.Load() {
status = "not_ready"
code = http.StatusServiceUnavailable
}
w.WriteHeader(code)
json.NewEncoder(w).Encode(HealthResponse{
Status: status,
Checks: checks,
})
}
// 启动时标记为未就绪,初始化完成后标记就绪
func main() {
ready.Store(false)
// 初始化(加载缓存、建立连接池等)
go func() {
initialize()
ready.Store(true)
}()
http.HandleFunc("/healthz", livenessHandler)
http.HandleFunc("/readyz", readinessHandler)
http.ListenAndServe(":8080", nil)
}关键设计原则:
- Liveness 不检查依赖。数据库挂了不应该导致进程被重启——重启解决不了数据库的问题,反而会造成启动风暴。
- Readiness 检查所有关键依赖。只有当服务能够正常处理请求时才返回 200。
- 健康检查不应有副作用。不要在健康检查中写日志、更新计数器或做任何状态变更。
- 健康检查应该快速返回。超过 1 秒的健康检查本身就是性能问题。如果依赖检查慢,用异步方式更新状态。
1.4 参数调优
健康检查的三个关键参数:间隔(interval)、超时(timeout)、阈值(threshold)。
时间线:
|--间隔--|--间隔--|--间隔--|--间隔--|--间隔--|
检查1 检查2 检查3 检查4 检查5
✓ ✗ ✗ ✗ → 连续失败 3 次,标记不健康
(fall=3)
... ✗ ✓ ✓ → 连续成功 2 次,标记健康
(rise=2)
检测到故障的最短时间 = interval × fall
恢复服务的最短时间 = interval × rise
参数选择指南:
| 参数 | 保守值 | 激进值 | 推荐值 | 说明 |
|---|---|---|---|---|
| interval | 10s | 1s | 3-5s | 检查频率 |
| timeout | 5s | 1s | 2-3s | 单次检查超时 |
| fall | 5 | 2 | 3 | 判定不健康的连续失败次数 |
| rise | 3 | 1 | 2 | 判定恢复的连续成功次数 |
| 故障检测时间 | 50s | 2s | 9-15s | interval × fall |
| 恢复时间 | 30s | 1s | 6-10s | interval × rise |
# Nginx(商业版 Plus 支持主动健康检查)
upstream backend {
zone backend 64k;
server 10.0.2.10:8080;
server 10.0.2.11:8080;
}
server {
location / {
proxy_pass http://backend;
health_check interval=5s fails=3 passes=2
uri=/readyz match=healthy;
}
}
match healthy {
status 200;
body ~ "ready";
}
# HAProxy 健康检查配置
backend app_back
option httpchk GET /readyz
http-check expect status 200
# inter: 检查间隔, fall: 失败阈值, rise: 恢复阈值
server app1 10.0.2.10:8080 check inter 3s fall 3 rise 2
server app2 10.0.2.11:8080 check inter 3s fall 3 rise 2
# 慢启动:恢复后 30 秒内逐渐增加权重
server app3 10.0.2.12:8080 check inter 3s fall 3 rise 2 slowstart 30s1.5 高级健康检查模式
依赖级联检查。有时需要检查间接依赖的健康状态:
# Python 示例:分级健康检查
from flask import Flask, jsonify
import redis
import psycopg2
import time
app = Flask(__name__)
# 缓存依赖检查结果(避免每次检查都查数据库)
_cache = {"result": None, "timestamp": 0, "ttl": 5}
def cached_check(func, ttl=5):
"""缓存检查结果,避免频繁探测依赖"""
key = func.__name__
now = time.time()
if _cache.get(key) and now - _cache[key]["ts"] < ttl:
return _cache[key]["result"]
result = func()
_cache[key] = {"result": result, "ts": now}
return result
def check_database():
try:
conn = psycopg2.connect(
host="db.internal", dbname="app",
connect_timeout=2
)
cur = conn.cursor()
cur.execute("SELECT 1")
cur.close()
conn.close()
return {"status": "ok"}
except Exception as e:
return {"status": "error", "message": str(e)}
def check_redis():
try:
r = redis.Redis(host="redis.internal", socket_timeout=2)
r.ping()
return {"status": "ok"}
except Exception as e:
return {"status": "error", "message": str(e)}
@app.route("/readyz")
def readiness():
db = cached_check(check_database)
cache = cached_check(check_redis)
all_ok = db["status"] == "ok" and cache["status"] == "ok"
code = 200 if all_ok else 503
return jsonify({
"status": "ready" if all_ok else "not_ready",
"checks": {
"database": db,
"redis": cache,
}
}), code
@app.route("/healthz")
def liveness():
# 只检查进程是否存活
return jsonify({"status": "alive"}), 200部分降级模式。当非关键依赖故障时,服务仍然可以提供部分功能:
@app.route("/readyz")
def readiness_with_degradation():
db = cached_check(check_database)
cache = cached_check(check_redis)
search = cached_check(check_elasticsearch)
# 数据库是硬依赖——挂了就不接流量
if db["status"] != "ok":
return jsonify({"status": "not_ready", "reason": "database"}), 503
# Redis 和 ES 是软依赖——挂了仍可接流量(降级模式)
status = "ready"
if cache["status"] != "ok" or search["status"] != "ok":
status = "degraded" # 降级但仍可服务
return jsonify({
"status": status,
"checks": {
"database": db,
"redis": cache,
"elasticsearch": search,
}
}), 200 # 200 表示仍然可以接收流量二、被动健康检查
2.1 基于真实流量的检测
被动健康检查不主动发送探测请求,而是观察真实流量的响应来判断后端健康状态:
主动检查: LB 定期发送 GET /health 探测
优点: 无流量时也能发现故障
缺点: 探测请求 ≠ 真实请求,可能存在差异
被动检查: LB 观察真实请求的成功/失败
优点: 基于真实流量,无额外开销
缺点: 无流量时无法检测故障
# Nginx 被动健康检查
upstream backend {
server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.2.11:8080 max_fails=3 fail_timeout=30s;
# max_fails=3: 30 秒内失败 3 次标记不健康
# fail_timeout=30s: 标记不健康后 30 秒不发送请求
# 30 秒后自动重试,如果成功则恢复
}
2.2 Envoy 的 Outlier Detection
Envoy 的异常点检测(Outlier Detection)是被动健康检查的高级形式,结合了多种检测策略:
# Envoy Outlier Detection 配置
clusters:
- name: app_cluster
outlier_detection:
# 连续失败触发驱逐
consecutive_5xx: 5 # 连续 5 个 5xx 响应
interval: 10s # 检测间隔
base_ejection_time: 30s # 基础驱逐时间
max_ejection_percent: 50 # 最多驱逐 50% 的节点
enforcing_consecutive_5xx: 100 # 100% 执行(不是采样)
# 成功率触发驱逐
success_rate_minimum_hosts: 3 # 至少 3 台才启用
success_rate_request_volume: 100 # 至少 100 个请求
success_rate_stdev_factor: 1900 # 低于平均值 1.9 个标准差
# 延迟触发驱逐
consecutive_local_origin_failure: 5 # 本地错误(连接失败等)驱逐时间递增机制。每次驱逐的时间按指数增长,避免频繁的驱逐-恢复震荡:
第 1 次驱逐: base_ejection_time × 1 = 30s
第 2 次驱逐: base_ejection_time × 2 = 60s
第 3 次驱逐: base_ejection_time × 3 = 90s
...
上限: max_ejection_time (默认 300s)
2.3 主动 + 被动结合
最佳实践是同时使用主动和被动健康检查:
| 维度 | 仅主动 | 仅被动 | 主动 + 被动 |
|---|---|---|---|
| 无流量时检测 | ✓ | ✗ | ✓ |
| 真实请求异常检测 | ✗ | ✓ | ✓ |
| 额外网络开销 | 有 | 无 | 有 |
| 故障检测速度 | 取决于 interval | 第一个失败请求 | 最快 |
| 误判风险 | 探测与真实差异 | 偶发错误触发 | 相互校验 |
# HAProxy: 主动 + 被动结合
backend app_back
# 主动检查
option httpchk GET /readyz
http-check expect status 200
# 被动检查 (observe 指令)
option httpclose
default-server inter 5s fall 3 rise 2 on-marked-down shutdown-sessions
server app1 10.0.2.10:8080 check observe layer7
server app2 10.0.2.11:8080 check observe layer7
# observe layer7: 观察 HTTP 响应来被动判断健康
# observe layer4: 观察 TCP 连接来被动判断健康三、假阳性与假阴性
3.1 两种错误的代价
| 错误类型 | 定义 | 后果 | 严重程度 |
|---|---|---|---|
| 假阳性 | 健康的服务器被标记为不健康 | 可用容量减少,剩余服务器负载增加 | 中等 |
| 假阴性 | 故障的服务器被认为健康 | 部分请求失败,用户受影响 | 严重 |
假阳性更常见。网络抖动、瞬间 CPU 毛刺、GC 暂停都可能导致健康检查超时。如果阈值设得太低(fall=1),一次偶发超时就会摘除一台健康的服务器。
假阴性更危险。应用层死锁(进程在但无法处理请求)、内存泄漏(GC 压力大导致请求变慢但仍能响应健康检查)——这些故障 TCP 检查无法发现。
3.2 减少误判的策略
减少假阳性(避免误摘除):
├── 增加 fall 阈值(3-5 次)
├── 增加 timeout(给慢响应留余地)
├── 分离健康检查端口(避免业务端口被打满影响检查)
└── 限制最大驱逐比例(至少保留 50% 的节点)
减少假阴性(避免漏检测):
├── 使用 HTTP 检查替代 TCP 检查
├── 健康检查端点检查依赖(数据库、Redis)
├── 结合被动检查(真实流量异常检测)
└── 监控健康检查响应时间(变慢可能是故障前兆)
3.3 雪崩保护
最危险的情况是健康检查导致的级联故障——当多台服务器同时被判定不健康时,剩余服务器负载暴增,也开始超时,也被摘除,最终所有服务器都被摘除:
正常状态: 4 台 RS,每台 250 QPS(总 1000 QPS)
↓ 网络抖动,2 台健康检查超时
中间状态: 2 台 RS,每台 500 QPS
↓ 负载翻倍,响应变慢,健康检查也超时
灾难状态: 0 台 RS,100% 流量失败
防护措施:
# Envoy: 限制最大驱逐比例
outlier_detection:
max_ejection_percent: 30 # 最多驱逐 30%,保证至少 70% 可用
# HAProxy: 至少保留一台
backend app_back
option allbackups # 所有 backup 都可以同时使用
server app1 10.0.2.10:8080 check
server app2 10.0.2.11:8080 check
server backup1 10.0.2.20:8080 check backup # 只在主服务器都不可用时启用四、故障转移与优雅下线
4.1 连接排空(Connection Draining)
当一台服务器需要下线时(升级、维护),不能直接停止——必须等待已有请求处理完毕:
#!/bin/bash
# graceful_shutdown.sh — 优雅下线脚本
SERVER="10.0.2.10:8080"
# 1. 标记为不健康(健康检查端点返回 503)
curl -X POST http://$SERVER/admin/drain
# 2. 通知 LB 停止分配新请求
# HAProxy: 通过 Runtime API
echo "set server app_back/app1 state drain" | socat stdio /var/run/haproxy.sock
# 3. 等待已有连接结束
echo "等待连接排空..."
while true; do
ACTIVE=$(ss -tn state established | grep "$SERVER" | wc -l)
echo "剩余活跃连接: $ACTIVE"
if [ "$ACTIVE" -eq 0 ]; then
break
fi
sleep 5
done
# 4. 停止服务
echo "所有连接已排空,停止服务"
ssh $SERVER "systemctl stop app"4.2 应用层优雅关闭
// Go 示例:优雅关闭 HTTP 服务器
package main
import (
"context"
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
)
var draining atomic.Bool
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if draining.Load() {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/api/", apiHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 监听关闭信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-quit
// 1. 标记为 draining,健康检查返回 503
draining.Store(true)
// 2. 等待 LB 检测到并停止发送新请求
// 这个时间应该 >= LB 的健康检查间隔 × fall 阈值
time.Sleep(15 * time.Second)
// 3. 优雅关闭(等待已有请求完成,最多 30 秒)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
server.ListenAndServe()
}4.3 滚动更新中的健康检查
在 Kubernetes 滚动更新中,健康检查参数直接影响更新速度和可用性:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 最多 1 个 Pod 不可用
maxSurge: 1 # 最多多出 1 个 Pod
template:
spec:
terminationGracePeriodSeconds: 60 # 优雅关闭时间
containers:
- name: app
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10 # 启动后等 10 秒再检查
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 3 # Readiness 检查更频繁
failureThreshold: 2 # 更快摘除
successThreshold: 2 # 需要连续成功 2 次才标记就绪
timeoutSeconds: 2
# 启动探针(1.20+)
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 2
failureThreshold: 30 # 最多等 60 秒启动PreStop Hook——确保在 Pod 被摘除前有足够时间排空:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
# 等待 10 秒让 Endpoints 更新传播到所有节点
# 然后 SIGTERM 触发应用的优雅关闭4.4 故障转移的测试方法
健康检查和故障转移必须定期测试,而不是等到真正故障时才发现不工作:
#!/bin/bash
# test_failover.sh — 故障转移测试脚本
VIP="10.0.1.100"
BACKEND="10.0.2.10"
echo "=== 故障转移测试 ==="
# 1. 基准:确认所有后端正常
echo "Step 1: 确认基准状态"
curl -s http://$VIP/health | jq .
echo "当前后端分布:"
for i in $(seq 1 100); do
curl -s http://$VIP/api/whoami
done | sort | uniq -c
# 2. 模拟故障:停止一台后端
echo "Step 2: 停止 $BACKEND"
ssh $BACKEND "systemctl stop app"
# 3. 观察故障检测时间
echo "Step 3: 等待 LB 检测到故障..."
START=$(date +%s)
while true; do
RESP=$(curl -s -o /dev/null -w "%{http_code}" http://$VIP/api/test)
if [ "$RESP" = "200" ]; then
NOW=$(date +%s)
ELAPSED=$((NOW - START))
echo " ${ELAPSED}s: 仍在返回 200"
else
NOW=$(date +%s)
ELAPSED=$((NOW - START))
echo " ${ELAPSED}s: 检测到 $RESP,故障检测完成"
break
fi
sleep 1
done
# 4. 验证流量已切走
echo "Step 4: 验证流量分布"
for i in $(seq 1 50); do
curl -s http://$VIP/api/whoami
done | sort | uniq -c
# 应该不再出现故障后端
# 5. 恢复
echo "Step 5: 恢复 $BACKEND"
ssh $BACKEND "systemctl start app"
# 6. 等待恢复检测
echo "Step 6: 等待 LB 检测到恢复..."
sleep 15
for i in $(seq 1 50); do
curl -s http://$VIP/api/whoami
done | sort | uniq -c
echo "=== 测试完成 ==="五、监控与告警
5.1 健康检查指标
# HAProxy 统计接口
echo "show stat" | socat stdio /var/run/haproxy.sock | \
awk -F, '{print $2, $18, $19, $20}'
# server_name chkfail chkdown lastchg
# app1 12 3 1234
# 关键指标:
# chkfail: 健康检查失败总次数
# chkdown: 被标记为 down 的总次数
# lastchg: 最后一次状态变化的时间(秒)
# check_duration: 健康检查响应时间
# 告警规则
# 1. 健康检查失败率 > 10%/分钟 → Warning
# 2. 任何 RS 被标记 down → Alert
# 3. 可用 RS 数量 < 总数的 50% → Critical
# 4. 健康检查响应时间 > 正常值 3 倍 → Warning(故障前兆)5.2 健康检查仪表盘
# Prometheus 告警规则
groups:
- name: health_check_alerts
rules:
- alert: BackendDown
expr: haproxy_server_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "后端 {{ $labels.server }} 不健康超过 1 分钟"
- alert: TooFewHealthyBackends
expr: |
count(haproxy_server_up == 1) by (backend)
/ count(haproxy_server_up) by (backend) < 0.5
for: 30s
labels:
severity: critical
annotations:
summary: "后端组 {{ $labels.backend }} 健康节点不足 50%"
- alert: HealthCheckSlow
expr: haproxy_server_check_duration_seconds > 2
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.server }} 健康检查响应变慢"六、总结
HTTP 检查是默认选择。TCP 检查太粗糙(检测不到应用层故障),gRPC 检查只适用于 gRPC 服务。HTTP 检查能覆盖大多数场景,且可以通过响应体传递详细的健康信息。
分离 Liveness 和 Readiness。Liveness 只检查进程存活,Readiness 检查所有依赖。LB 用 Readiness 端点做健康检查——依赖故障应该让流量转移,而不是杀掉进程。
主动 + 被动结合是最佳实践。主动检查保证无流量时也能发现故障,被动检查能在第一个失败请求时立即响应。两者互补。
设置最大驱逐比例。永远不要允许健康检查摘除所有服务器。设置
max_ejection_percent=30-50%,宁可让部分请求失败,也不要让所有流量无处可去。优雅下线是三步操作:标记 draining → 等待 LB 检测 → 排空已有连接 → 停止进程。跳过任何一步都会导致请求失败。K8s 的 preStop hook + terminationGracePeriodSeconds 是这个流程的标准化实现。
定期测试故障转移。健康检查和故障转移是否正常工作,只有通过测试才能确认。混沌工程(随机停止后端实例)应该成为常规实践,而不是等到真正故障时才发现检查配置有问题。
健康检查本身不应成为负担。当后端有 1000 个 Pod 时,每 3 秒一次 HTTP 健康检查意味着每秒 333 个额外请求。如果检查端点执行了数据库查询,这个开销不可忽视。使用缓存、异步更新状态、分离健康检查端口都是有效的优化手段。
参考文献
- Envoy Documentation: Outlier Detection (envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/outlier)
- HAProxy Documentation: Health Checks (docs.haproxy.org/2.8/configuration.html#5.2-check)
- Kubernetes Documentation: Pod Lifecycle (kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/)
- gRPC Health Checking Protocol (github.com/grpc/grpc/blob/master/doc/health-checking.md)
下一篇:全局负载均衡:GSLB、DNS 调度与 Anycast
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】L4 负载均衡:IPVS、LVS 与连接级调度
系统讲解 L4 负载均衡的内核实现:IPVS 的工作原理与三种转发模式(NAT/DR/TUN)、调度算法选择、LVS 高可用方案(Keepalived + VIP)、云环境中的 L4 LB(NLB/MetalLB),建立传输层负载均衡的工程能力。
【网络工程】HAProxy 工程:高级配置、ACL 与运维
系统讲解 HAProxy 的工程实践:Frontend/Backend/Listen 配置模型、ACL 规则引擎的高级用法、Runtime API 的动态管理能力、多线程模型与性能调优、SSL 终止与健康检查策略,建立 HAProxy 从配置到生产运维的完整体系。
【网络工程】Envoy 架构剖析:xDS、Filter Chain 与热重启
系统剖析 Envoy 代理的架构设计:per-Worker 线程模型与事件循环、Filter Chain 的分层扩展机制、xDS 协议族的动态配置发现、热重启的实现原理与零停机更新、Envoy 在 Service Mesh 中的数据面角色,建立 Envoy 从架构到运维的完整理解。
【网络工程】反向代理模式:TLS 终止、透传与重加密
系统解剖反向代理的三种 TLS 处理模式——终止、透传与重加密。从架构对比到 SNI 路由、证书管理、性能影响与安全权衡,给出生产环境的工程选型依据。