Day 15 / 共 20 天 · 第 3 周收官
健康检查 + 服务发现
后端会挂、会新增。健康检查负责"剔除坏节点",服务发现负责"知道有哪些节点"。今天讲两种健康检查 + 四种服务发现,最后串讲 LDS→RDS→CDS→EDS 全链路,收官第 3 周。
📍 你在整门课的位置 · 第 3 周收官(D14 会从后端里挑一个 → 今天保证"挑到的都是活的",并串讲 W3 全链路)
D11 xDS 总览→
D12 配置订阅→
D13 集群管理→
D14 负载均衡→
D15 健康检查/发现
L01
两种健康检查
| 主动健康检查 | 被动异常点检测 | |
|---|---|---|
| 方式 | 主动定时发探测包 | 观察真实业务请求的成败 |
| 判定 | 探测响应码/超时 | 连续 5xx / 成功率低于阈值 |
| 代码 | health_checkers/ | upstream/outlier_detection_impl |
"体检" vs "看病历"
主动健康检查像"定期体检"——不管有没有病,定时给每个后端发个探测请求,看它还活着吗。被动异常点检测像"看病历"——不额外探测,而是观察真实请求:这个后端最近连续返回 5xx?或成功率明显低于其他节点?那就临时把它踢出去。两者互补:主动能在没流量时也发现死节点;被动能捕捉"探测正常但实际处理业务出错"的问题。生产常同时开。
L02
主动健康检查
// source/extensions/health_checkers/common/health_checker_base_impl.h:44
class HealthCheckerImplBase : public HealthChecker {
class ActiveHealthCheckSession { // :59 每个 Host 一个会话
virtual void onInterval() PURE; // :80 子类实现:发起一次探测
virtual void onTimeout() PURE; // :82 超时处理
void setUnhealthy(UnhealthyType type); // :129 标记不健康
};
void setUnhealthyCrossThread(const HostSharedPtr& host, ...); // :147 跨线程通知
};
读法:每个后端一个
ActiveHealthCheckSession,定时器到点触发 onInterval()(子类发探测请求),成功/超时更新健康状态。状态变化通过 setUnhealthyCrossThread 跨线程通知集群管理器(呼应 Day 13 的跨线程传播),进而重建各 Worker 的可用主机列表。HTTP 健康检查在 health_checkers/http/,还有 tcp/grpc/redis。🤔 只做"定期体检"会漏诊什么?
主动健康检查探测的往往是
/healthz——但真实业务接口 /api 可能报错(数据库连不上),而 /healthz 照样返回 200。"体检项目"没覆盖到的病,体检查不出来。💡 本质:被动检测 = 看"真实病历/投诉记录"
被动异常点检测不额外探测,而是盯着真实请求的成败:这个后端最近连续 5xx?成功率明显低于同组其他节点?就临时"停诊"(弹出 eject)它。反复出问题的节点,停诊时间指数增长(退避),避免它一放回就又出错。主动(体检)+ 被动(病历)互补,生产常同时开。
📝 举个例子:一次弹出 + 退避
后端 B 连续 5 次返回 5xx → 弹出
30s(不再给它发请求)→ 期满放回,又连续 5xx → 弹出 60s → 再犯 → 120s…指数退避,直到它稳定;若主动体检恢复成功,可提前取消弹出。被动异常点检测:连续故障 → 弹出,弹出时长指数退避(30→60→120s),防止坏节点反复被放回又立刻出错。
L03
被动异常点检测
// source/common/upstream/outlier_detection_impl.h:122
class DetectorHostMonitorImpl : public DetectorHostMonitor {
void putResult(Result result, absl::optional<uint64_t> code); // :138 每次请求结果上报累积
};
// 判定维度(outlier_detection_impl.cc):
// :216 consecutive_5xx(连续 5xx 次数)
// :226 success_rate_request_volume + 成功率统计法(低于 均值−N×标准差 则弹出)
// :236 enforcing_*(各判据的生效概率,可灰度)
"弹出(eject)"与指数退避
当某后端触发阈值(比如连续 5 次 5xx),异常点检测会把它临时"弹出"集群一段时间(不再给它发请求),期满放回。反复出问题的节点,弹出时间会指数增长(退避)——第一次弹 30 秒,再犯弹 60 秒、120 秒…避免一个持续故障的节点反复被放回又立刻出错。还支持"主动健康检查成功则取消弹出"的联动。这套"被动观测 + 弹出 + 退避"让集群能自愈——坏节点自动隔离,好了自动回归。
L04
四种服务发现
每种集群类型对应一种"端点从哪来"(source/extensions/clusters/):
| 类型 | 端点来源 |
|---|---|
| static | 配置里写死的 IP:Port,启动即就绪 |
| strict_dns | 周期解析 DNS,A 记录里每个 IP 都成为一个 Host |
| logical_dns | 周期解析 DNS,但只取第一个 IP 作逻辑主机 |
| eds | 通过 EDS xDS 从控制面动态获取端点 |
读法:static 最简单(写死);strict_dns 适合"每个 IP 都要单独连";logical_dns 适合大型无状态服务(只维护一个逻辑主机,避免为海量 IP 建海量连接);eds 最动态(控制面推)。生产 K8s 环境几乎都用 eds。
L05
STRICT_DNS 解析循环
// source/extensions/clusters/strict_dns/strict_dns_cluster.cc
// :39 dns_refresh_rate_ms_(默认 5000ms)
// :120 resolve_timer_ 到点触发 startResolve()
// :132 dns_resolver_->resolve(...) 异步 DNS 查询
// 回调里把解析出的所有地址转成 Host 集合
// :192 updateAllHosts(hosts_added, hosts_removed, priority) 增删差量更新
// :139 根据 TTL 计算下次刷新间隔并重新 arm 定时器
读法:DNS 类集群就是一个"定时解析→差量更新主机集"的循环。
updateAllHosts 只增删变化的主机(不是全量重建),配合 Day 13 的跨线程传播更新各 Worker。解析是异步的(不阻塞事件循环,Day 04 会讲)。L06
EDS 动态端点
// source/extensions/clusters/eds/eds.cc:31
class EdsClusterImpl : public SubscriptionBase<ClusterLoadAssignment> {
// :197 onConfigUpdate():收到 ClusterLoadAssignment
// :262 update(cluster_load_assignment) 应用端点
// :162 updateLocalityEndpoints():按 locality(地域/可用区)批量重建主机集合
};
读法:EDS 集群本身就是一个 xDS 订阅者(
SubscriptionBase<ClusterLoadAssignment>,呼应 Day 12)——控制面推端点变化,它 onConfigUpdate 应用。按 locality 组织(同可用区优先,降跨区延迟)。这是 K8s Pod 增删时端点实时更新的机制。🚶 第一人称·你现在是一个 HTTP 请求,跟着 W3 学的配置链走完全程(这张图把第 3 周五天串成一条线):
一个请求的配置解析链:LDS→RDS→CDS→EDS→LB。健康检查(本日)夹在其中,确保 EDS 端点表里只有活节点。
口诀:听 → 路 → 群 → 点 → 挑
听(LDS 命中监听器) → 路(RDS 选路由/集群) → 群(CDS 定义集群) → 点(EDS 给健康端点) → 挑(LB chooseHost)。这五个字能默背下来,第 3 周就通了。
L07
全链路串讲 LDS→RDS→CDS→EDS
第 3 周大串讲:一个请求进来的完整配置解析链——
① LDS 决定它命中哪个 Listener(Day 11)
② RDS 的路由表决定路由到哪个 Cluster(Day 09 Router)
③ CDS 定义该 Cluster 的属性(LB 策略/健康检查/发现类型,Day 13)
④ EDS 提供该 Cluster 当前有哪些健康端点(今天)
⑤ 最后 LB 从这些健康端点里
这条链 + xDS 动态下发 + 线程本地无锁读取,就是 Envoy 作为"可编程数据面"的完整图景。所有配置都能被控制面(Istio/Higress)热更新。
① LDS 决定它命中哪个 Listener(Day 11)
② RDS 的路由表决定路由到哪个 Cluster(Day 09 Router)
③ CDS 定义该 Cluster 的属性(LB 策略/健康检查/发现类型,Day 13)
④ EDS 提供该 Cluster 当前有哪些健康端点(今天)
⑤ 最后 LB 从这些健康端点里
chooseHost 选一个真正转发(Day 14)这条链 + xDS 动态下发 + 线程本地无锁读取,就是 Envoy 作为"可编程数据面"的完整图景。所有配置都能被控制面(Istio/Higress)热更新。
L08
第 3 周收官 🎓
🧠 第 3 周(xDS 与上游)你已掌握
- Day 11 xDS 总览:五大发现服务、管理服务器、ACK/NACK
- Day 12 配置订阅:Subscription/GrpcMux、ADS 一条流、热更新闭环
- Day 13 Cluster Manager:线程本地集群、无锁读、CDS 驱动
- Day 14 负载均衡:轮询/最少请求 P2C/EDF 加权/一致性哈希
- Day 15 主动+被动健康检查、四种服务发现、LDS→RDS→CDS→EDS 全链路
你现在理解了 Envoy 作为"可编程数据面"的动态配置全貌。第 4 周进入扩展生态——扩展机制、Wasm 插件(Higress 的基础)、可观测性、Buffer、Bazel。
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '44,150p' source/extensions/health_checkers/common/health_checker_base_impl.h | head -40
ls source/extensions/clusters/
grep -n 'onConfigUpdate\|updateLocalityEndpoints' source/extensions/clusters/eds/eds.cc | head
明天预告 · Day 16(第4周开始):扩展机制——Envoy 的可插拔架构核心:
Registry::RegisterFactory 工厂模式、category() 类别、编译期静态注册 + 裁剪。理解它才能看懂 Wasm、过滤器等一切扩展。