想象你走进一座生意火爆的美食城:门口的兰州拉面、黄焖鸡和麻辣香锅三个档口一字排开。最开始只有拉面一个档口,中午客人一拥而入,师傅手都抖了,队伍还是越排越长——这就是典型的“单机瓶颈”。后来老板学聪明了:多开几个档口,让客流分着走。可是怎么分?老板想到了两种办法:第一种,给每个顾客手机里装一个“档口小程序”,大家自己看排队情况、自己决定去哪家——这就是客户端负载均衡;第二种,在门口高薪聘请一位“领位员”,所有顾客先来报到,由领位员统一查看全局、安排座位——这就是服务端代理负载均衡。在互联网世界里,前者叫 Ribbon,住在每个调用方的程序里;后者叫 Envoy,以 Sidecar 身份蹲在服务旁边。这篇文章就借这座美食城的故事,把负载均衡的来龙去脉、健康检查、权重路由、重试容错,以及两大主角的选型对比,一锅端给你。

岗位关联

学透负载均衡,你就能在流量放大时从容扩容、在服务抖动时快速定位故障,这是架构师处理高并发与高可用问题的核心内功之一。

目录
  1. 可视化演示
  2. 负载均衡基础
  3. Ribbon原理
  4. Envoy代理
  5. 健康检查机制
  6. 权重路由策略
  7. 重试与容错
  8. 选型对比实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Ribbon进程内负载均衡与Envoy Sidecar代理的架构差异、四种均衡策略的请求分发模式及健康检查与重试机制,揭示客户端与服务端负载均衡的核心区别。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Ribbon vs Envoy架构对比 Ribbon:客户端进程内LB,直接选择服务实例发请求 Envoy:Sidecar代理模式,请求经由Envoy转发到目标 核心差异:进程内vs进程外,侵入性vs透明代理 Ribbon: Client→LB(in-process)→Server | Envoy: Client→Envoy→Envoy→Server 架构模式决定运维边界与扩展性

负载均衡基础

想象一座刚开业的美食城只有一家兰州拉面档口。中午客人涌进来,档口只有三个灶头,师傅手再快也赶不上点单的速度——队伍越排越长,客人越等越气,最后骂骂咧咧地走了。老板很快明白一个道理:再能干的师傅,单口灶台的产能也有天花板。于是他把档口从一家开到三家,可新的问题来了:客人该去哪家?

把“档口”换成“服务器”,把“排队点单”换成“网络请求”,这就是负载均衡要解决的核心问题:单机存在瓶颈,与其让一台机器硬扛,不如让一个集群共同分摊流量,让每台机器都轻轻松松。按分工层次,负载均衡分为四层(传输层)和七层(应用层):四层看源目 IP 和端口,转发快,典型如 LVS;七层看 HTTP 的 URL 和 Header,能精细分流,典型如 Nginx。按决策位置,又分成客户端负载均衡(Ribbon)和服务端代理负载均衡(Envoy/Nginx),这也是本文的两大主角。

集群带来的不只是吞吐翻倍,还有可用性的数学提升——只要不是所有机器同时宕机,服务就能继续:

回到美食城:老板开了三家分店,顾客知道只要还有一家开门,就不至于白跑一趟。负载均衡就是“让所有档口都在工作,且任何一家倒下都不至于塌方”的组织方法。接下来我们看两种主流组织方式:让顾客自己挑(Ribbon),还是请领位员统一安排(Envoy)。

Acluster=1−(1−A0)n\displaystyle A_{cluster} = 1 - (1 - A_0)^n
单机可用性 99% 的服务器,10 台组成集群后整体可用性提升到约 99.9999%——只要有一台活着,美食城就还能营业。
  1. 单机可用率 $A_0$一台服务器在一年中能正常服务的时间占比,比如 99%,对应每年约 3.65 天的故障时间。
    A0=正常运行时间总时间\displaystyle A_0 = \frac{正常运行时间}{总时间}
  2. 单机不可用率 $1 - A_0$这台服务器“某时刻恰好处于宕机状态”的概率,也就是顾客来了却发现档口关门的概率。
    1−A0=0.01\displaystyle 1 - A_0 = 0.01
  3. 整组同时宕机 $(1 - A_0)^n$假设各机器独立运行,n 台同时不可用的概率是单机不可用率的 n 次方——就像三个档口同时关门一样稀有。
    (1−A0)n=0.01n\displaystyle (1 - A_0)^n = 0.01^n
  4. 取补集得到集群可用性整体可用 = 1 减去“全部宕机”的概率,即至少有一台存活,系统就还能响应。
    Acluster=1−0.01n\displaystyle A_{cluster} = 1 - 0.01^n

Ribbon原理

美食城老板给所有顾客配了一个小程序:打开就能看到各家档口当前的排队人数和出餐速度。你端详一番,决定今天去人最少的黄焖鸡。每个顾客的选择自己拍板,不经过任何人协调——Ribbon 就是这样一个“小程序”。

Ribbon 是 Spring Cloud 体系中经典的客户端负载均衡器,它以 jar 包的形式“住”在调用方进程里,为每一次远程调用挑选目标实例。它不是靠猜,而是定时从注册中心(Eureka、Nacos 等)拉取服务实例列表,缓存到本地内存。当你的订单服务要调用用户服务时,Ribbon 就从缓存列表里按策略挑一个地址。最经典的策略是轮询(Round Robin),像转盘一样挨个来:

除了轮询,Ribbon 还支持随机、响应时间加权等策略,并可以自定义。它的最大特点是“去中心化”:每个调用方自己决策,链路最短、零额外跳数。但就像每个顾客手机里的信息可能不同步,各客户端的缓存列表也可能不一致,决策分散意味着没有全局视角——这是它的优点,也是隐患。

Snext=(Scurrent+1) mod n\displaystyle S_{next} = (S_{current} + 1) \bmod n
轮询公式表示“上一条请求发给 0 号,下一条发给 1 号……发到最后一个再绕回 0 号”,人人有份、雨露均沾。
  1. 当前选中下标 $S_{current}$记录上一条请求被派给了列表里的第几个实例。
    Scurrent∈{0,1,2,…,n−1}\displaystyle S_{current} \in \{0, 1, 2, \dots, n-1\}
  2. 下标加一 $S_{current} + 1$把指针拨向下一个实例,这是轮询“挨个来”的灵魂。
    Scurrent+1\displaystyle S_{current} + 1
  3. 对实例总数取模 $\bmod n$当指针走到列表末尾(等于 n)时,取模让它回到 0,形成环形队列。
    (Scurrent+1) mod n\displaystyle (S_{current} + 1) \bmod n
  4. 实例总数 $n$当前健康的服务实例个数,这个数字来自注册中心拉取的最新列表。
    n=可用实例数量\displaystyle n = \text{可用实例数量}

Envoy代理

顾客自己挑档口总出幺蛾子:有人信息滞后,有人爱扎堆。于是老板高薪聘请了一位领位员站在门口,顾客进门先报到,领位员看一眼全局,统一安排座位——这就是 Envoy 的思路。

Envoy 是云原生时代最流行的服务端代理,它不住在客户端进程里,而是以 Sidecar 模式蹲在业务容器旁边(同一个 Pod 里的另一个容器),替业务进程收发流量。业务代码完全不用改,Java、Go、Python 一视同仁。Envoy 把架构清晰地切成两层:控制面(如 Istio 的 pilot)负责下发配置,数据面(Envoy 自己)负责转发流量。这套配置下发基于 xDS 协议:CDS 发现集群列表、EDS 发现每个集群的端点、RDS 发现路由规则。

配好之后,请求从客户端进来,先被 iptables 透明劫持到 Sidecar,再按路由规则分发给目标服务。Envoy 按权重把流量分给各个端点,分流概率的公式如下:

领位员的代价是“所有顾客都从门口过”:多一跳就多一份延迟,门口要是堵了,整座美食城都歇菜。所以选用 Envoy 必须配套健康检查、超时和熔断,把这位管家保护得明明白白。

Pi=wi∑j=1nwj\displaystyle P_i = \frac{w_i}{\sum_{j=1}^{n} w_j}
第 i 个档口能分到多少客人,取决于它的权重占所有档口权重总和的比例——权重越高,被领位员推荐的次数越多。
  1. 第 i 个节点的权重 $w_i$相当于档口的“受欢迎程度”,可以静态配置,也可以动态算出来。
    wi>0\displaystyle w_i > 0
  2. 所有权重之和 $\sum_{j=1}^{n} w_j$把 n 个档口的受欢迎程度加总,作为比例尺的“分母”。
    ∑j=1nwj=w1+w2+⋯+wn\displaystyle \sum_{j=1}^{n} w_j = w_1 + w_2 + \cdots + w_n
  3. 分流概率 $P_i$第 i 个节点分到的流量占比,权重越大、概率越高。
    Pi=节点i的权重总权重\displaystyle P_i = \frac{\text{节点i的权重}}{\text{总权重}}
  4. 归一化特性所有节点概率加起来正好等于 1,保证每一份流量都有去处。
    ∑i=1nPi=1\displaystyle \sum_{i=1}^{n} P_i = 1

健康检查机制

某个档口水管爆了、大厨请假,但老板没来得及关灯。如果领位员还一个劲地把客人往那儿领,客人们只能对着黑灯瞎火的档口干瞪眼——更糟的是,客人还以为这家店在营业,白白浪费了时间。

健康检查就是让系统自动识别“黑灯档口”。它有两套手法:主动探测,定时向每个档口发起 HTTP 探活请求或 TCP 连接,看它是否回应;被动检查,观察线上真实请求的失败率,失败次数多了就把它拉进黑名单。档口偶尔也会抖动——网络闪断、GC 停顿都可能导致一两秒无响应,不能一抖就摘。所以主流设计都采用“连续失败阈值”:连续失败 k 次才判定宕机。这个设计的数学依据是:

Ribbon 和 Envoy 都做健康检查,但姿势不同:Ribbon 靠客户端在自己的调用结果里被动统计失败,再配合注册中心的心跳剔除;Envoy 由控制面下发主动探测配置,探测失败后按阈值摘除,并把结果广播给整个网格。就像小程序只能凭顾客反馈猜测“暂停营业”,而领位员可以亲自去敲档口的门——各有一套,但目标一致。

Premove=pk\displaystyle P_{remove} = p^k
如果单次探测失败概率是 0.3,连续 3 次都失败的概率只有 0.027——说明“连续 3 次失败”大概率不是运气,而是档口真出事了。
  1. 单次探测失败概率 $p$一次探活请求超时或返回错误码的概率,包含网络抖动、GC 停顿等瞬态因素。
    p=P(单次失败)\displaystyle p = P(\text{单次失败})
  2. 连续失败次数 $k$判定宕机前允许连续失败的次数,k 越大越保守、k 越小越激进。
    k=连续失败阈值\displaystyle k = \text{连续失败阈值}
  3. 连续 k 次失败概率 $p^k$若每次失败互相独立,连续 k 次失败的概率呈指数级下降,因此“连续失败”能过滤大部分瞬时抖动。
    pk=p×p×⋯×p\displaystyle p^k = p \times p \times \cdots \times p
  4. 摘除与恢复一旦触发阈值,节点被摘除;恢复后还要“慢启动”,先放少量流量试探,确认稳定再逐步加压。
    摘除条件: failCount≥k\displaystyle \text{摘除条件: } failCount \geq k

权重路由策略

三个档口口味不一样:麻辣香锅天天排队,新开的兰州拉面没什么人。如果平均分客,香锅门口挤爆、拉面门口冷清,客人体验都很差。老板一拍大腿:按比例分人!香锅 50%、拉面 30%、黄焖鸡 20%。

权重路由就是“按比例分人”。权重可以在配置文件里静态指定,比如灰度发布时把新版本权重设为 5%,让一小撮用户先尝鲜;也可以动态计算,让性能好的节点多接客。最常见的动态权重以平均响应时间为依据:响应越快的节点权重越高,公式如下:

权重大的节点也不能被连续轰炸,否则它再快也会被压垮。生产上常用平滑加权轮询(Smooth WRR):每轮先给所有节点累加权重,选中当前累计权重最大的节点,再让它减去总权重。就像拉面店叫号,排队多的号也不能连续叫两次。权重路由在微服务里最重要的应用是灰度发布:5% 流量先访问新版本,观察 P99 和错误率没问题,再逐步把权重调到 100%。

Wi=RminRi\displaystyle W_i = \frac{R_{min}}{R_i}
如果三个档口的平均出餐时间分别是 2 分钟、3 分钟、6 分钟,它们的权重就是 3 比 2 比 1——出餐越快,被推荐的概率越高。
  1. 本节点平均响应时间 $R_i$滑动窗口内第 i 个实例处理请求的平均耗时,是动态权重的“原材料”。
    Ri=∑响应时间请求次数\displaystyle R_i = \frac{\sum \text{响应时间}}{\text{请求次数}}
  2. 全局最短响应时间 $R_{min}$所有节点里响应最快的那个,作为基准线。
    Rmin=min⁡{R1,R2,…,Rn}\displaystyle R_{min} = \min\{R_1, R_2, \dots, R_n\}
  3. 响应时间倒数为权重 $W_i$响应越短、$W_i$ 越大,体现“能者多劳”。
    Wi=RminRi∈(0,1]\displaystyle W_i = \frac{R_{min}}{R_i} \in (0, 1]
  4. 归一化后使用把算出的权重代入分流概率公式,按比例分发流量。
    Pi=Wi∑j=1nWj\displaystyle P_i = \frac{W_i}{\sum_{j=1}^{n} W_j}

重试与容错

顾客被领位员派到黄焖鸡,结果里面坐满了,他退回来重新排队。退一次两次还好;如果几十个人同时在门口反复横跳、换来换去,走廊就会被堵得水泄不通——比某个档口排队更可怕。

重试是容错最常用的手段,但必须有限度。设计重试策略时有三要素:最大重试次数、退避时间(指数退避加随机抖动)、全局超时上限。即使单次失败概率很高,只要重试次数有限,整体仍然有机会成功;但重试再多也不会让失败率归零,重试 m 次后仍然失败的概率是:

重试还有一颗隐藏地雷——幂等性。GET 查询重试十遍都没问题,但扣款、下单这类写操作,重试一次就可能产生两笔订单。所以只有幂等接口才允许自动重试,否则要为请求引入全局幂等键。再往上就是熔断降级:断路器有关闭、打开、半开三个状态,正常时放行,失败率超标就快速失败,半开时放少量流量试探恢复。Ribbon 的重试依赖应用代码(如 Spring Retry),没有全局视图,容易层层叠加成重试风暴;Envoy 可以在代理层统一配置 retry_policy,把次数、退避、超时集中管起来。记住:重试是止痛药,不是营养品——开药前先确认不会中毒(幂等)。

Pfail=pm+1\displaystyle P_{fail} = p^{m+1}
如果每次调用失败率是 20%,重试 2 次之后整体失败率从 20% 降到 0.8%——代价是请求耗时最多变为原来的 3 倍。
  1. 单次失败概率 $p$一次调用因超时、连接拒绝等原因失败的概率。
    p=P(单次调用失败)\displaystyle p = P(\text{单次调用失败})
  2. 总尝试次数 $m + 1$初始 1 次加上最多重试 m 次,比如重试 2 次意味着最多试 3 次。
    总尝试次数=m+1\displaystyle \text{总尝试次数} = m + 1
  3. 全部失败的概率 $p^{m+1}$每次尝试都失败,才最终放弃;这是指数退避之外的第二个“指数”。
    Pfail=p×p×⋯×p\displaystyle P_{fail} = p \times p \times \cdots \times p
  4. 实践的取舍m 越大成功率越高,但耗时和上游压力也越大,必须设上限来防止重试风暴。
    m=min⁡(配置上限,剩余超时预算/单次超时)\displaystyle m = \min(\text{配置上限}, \text{剩余超时预算} / \text{单次超时})

选型对比实践

开连锁分店时,老板面临抉择:该给每个顾客配“档口小程序”(Ribbon 模式),还是每家店门口都站一个领位员(Envoy 模式)?这个问题的答案,藏在流量模型和团队能力里。

先说性能:Ribbon 是进程内直连,请求从客户端直接到服务端,零额外跳数,延迟最低;Envoy 多一跳代理,每次请求都要经过 Sidecar 转发,延迟必然多出几十微秒到几毫秒。再说治理:Ribbon 的决策分散在各个客户端,没有全局视图,出问题要靠日志拼图;Envoy 由控制面统一管理,流量、健康、路由全部集中可观测,配合 Dashboard 一目了然。接入成本上,Ribbon 只是 Spring Cloud 的一个依赖;Envoy 需要注入 Sidecar、部署控制面、处理 iptables 劫持,运维复杂度明显更高。

选型口诀:Spring Cloud 体系内、延迟敏感的同步调用,优先 Ribbon;多语言混合架构、南北向入口、需要统一灰度与可观测性的云原生平台,选 Envoy。一个容易忽略的细节是:代理本身也会挂,端到端可用性必须串联计算:

最后分享一次生产复盘:某电商大促期间,订单服务的多个下游同时变慢,客户端层层重试叠加,把订单服务打成了雪崩。事后整改三板斧——限制重试次数、全局超时熔断、非幂等请求一律不重试,并对代理层做了双活部署。美食城的生意要做大,健康检查、权重路由、有限重试这三件套一个都不能少;无论你信“小助手”还是“领位员”,这套基本功都是活下去的底线。

---

Ae2e=Aproxy×Aservice\displaystyle A_{e2e} = A_{proxy} \times A_{service}
服务本身可用性 99.9%,加上一个 99.99% 的代理,端到端可用性变成约 99.89%——多一跳不是免费的,代理必须高可用部署。
  1. 代理可用性 $A_{proxy}$领位员自己不出错的概率,包括它所在节点的存活率和转发成功率。
    Aproxy=0.9999\displaystyle A_{proxy} = 0.9999
  2. 服务可用性 $A_{service}$后端业务实例整体可用的概率,健康检查、权重路由都是为了抬升它。
    Aservice=0.999\displaystyle A_{service} = 0.999
  3. 串联相乘 $A_{proxy} \times A_{service}$请求必须依次经过代理和服务,任何一环失败都算失败,所以概率相乘。
    Ae2e=0.9999×0.999=0.9989\displaystyle A_{e2e} = 0.9999 \times 0.999 = 0.9989
  4. 结论每增加一个必经过的环节,可用性都会打折,这提醒我们在引入代理时要同步做高可用冗余。
    Ae2e<min⁡(Aproxy,Aservice)\displaystyle A_{e2e} < \min(A_{proxy}, A_{service})

AI 应用场景

电商大促的弹性扩容与流量分发

阶段:流量预测驱动的容量规划与执行(AIOps 中“预测—决策—执行”环路的执行端)

解决的问题:大促前手工修改 Nginx 上游列表既慢又易错,扩容实例难以被流量感知。借助注册中心 + Ribbon,新扩容的实例注册后立即被客户端发现,请求自动摊到新节点上,无需人工干预。

具体案例:618 大促,订单服务由 20 个实例快速扩展到 80 个。Ribbon 轮询策略把峰值流量均匀分给所有健康实例,配合注册中心告警,扩容后 30 秒内流量分布完全均衡。

推荐系统新版本的灰度发布

阶段:在线学习模型迭代上线(模型版本管理的 A/B 实验环节)

解决的问题:推荐模型 v2 上线,直接全量切换风险极大。用 Envoy 路由权重把 5% 流量切到新版本,实时对比 P99 延迟与 CTR 指标,确认稳定后再平滑提升权重到 100%,实现低风险发布。

具体案例:某内容平台推荐服务用 Envoy 配置了 v1/v2 两个集群,v2 权重从 5% 起步,每 30 分钟观察一次业务指标,4 小时后全量切换,全程零故障。

混沌工程故障演练与自愈恢复

阶段:异常检测告警触发后的自动容灾恢复

解决的问题:传统故障靠人肉发现,反应慢。通过健康检查 + 熔断的组合,系统能在故障发生数十秒内自动摘除异常节点、切换流量,业务无感。

具体案例:某支付团队在演练中人为 kill 掉一个核心服务实例,Envoy 主动健康检查连续 3 次失败后立即摘除节点;同时 Ribbon 客户端被动统计到失败率超标触发熔断,请求自动转向健康实例,演练期间业务成功率保持 99.99%。

Python 演示

python75 行
# 负载均衡模拟器: 健康检查 + 平滑加权轮询 + 有限重试# 模拟美食城三个档口的客流分配过程import randomimport time  class Stall:    """档口: 一个后端服务实例"""     def __init__(self, name, weight):        self.name = name        self.weight = weight        self.alive = True        self.current = 0  class FoodCourt:    """美食城: 统一负责健康检查与客流分发"""     def __init__(self, stalls):        self.stalls = stalls        self.total = sum(s.weight for s in stalls)     def health_check(self, down_rate=0.15):        """模拟档口随机挂出"暂停营业"的牌子"""        for s in self.stalls:            if random.random() < down_rate:                s.alive = False     def pick(self):        """平滑加权轮询: 每轮先累加权重, 再选出最大者发客"""        best = None        for s in self.stalls:            if not s.alive:                continue            s.current += s.weight            if best is None or s.current > best.current:                best = s        if best is None:            return None        best.current -= self.total        return best     def serve(self, max_retry=2):        """带有限重试的领位流程, 防止重试风暴"""        for n in range(max_retry + 1):            stall = self.pick()            if stall is None:                print("  -- 所有档口都暂停营业, 客人离开")                return False            if random.random() < 0.2:                print("  -- %s排队太久, 顾客回来重试(%d)" % (stall.name, n + 1))                continue            print("  %s接待了这位顾客" % stall.name)            return True        print("  -- 重试次数用尽, 降级到备用方案")        return False  def main():    random.seed(42)    court = FoodCourt([        Stall("兰州拉面", 3),        Stall("黄焖鸡米饭", 2),        Stall("麻辣香锅", 1),    ])    for i in range(10):        print("第%d批顾客进场" % (i + 1))        court.health_check()        court.serve()        time.sleep(0.2)  if __name__ == "__main__":    main()
目的:用一个可运行的 Python 小程序,把正文讲的健康检查、平滑加权轮询、有限重试三个机制串起来,模拟美食城档口的客流分配。
  1. s.current += s.weight每轮先给健康档口的“账本”累加权重,相当于记录它的实时票数。
  2. best.current -= self.total被选中的档口要减去总权重,防止权重大的档口被连续轰炸,实现“平滑”。
  3. s.alive = False健康检查把宕机档口标记为不可用,后续轮询直接跳过它。
  4. if stall is None:所有档口都不可用时返回 None,调用方必须准备降级方案,而不是死等。
  5. for n in range(max_retry + 1)最多重试 max_retry 次,配合后面的“重试次数用尽”提示,避免无限重试引发重试风暴。
  6. random.seed(42)固定随机种子,让演示结果可复现,方便你对比不同参数的效果。

常见误区

误区负载均衡不就是 Nginx 或云 LB 吗?在系统入口放一台负载均衡器就完事了,Ribbon 这种东西没必要学。

正解负载均衡按决策位置分为客户端型和服务端型。Nginx 是服务端集中式,站在大门口统一接客;Ribbon 是客户端进程内式,住在每个调用方手机里。前者管南北向入口流量,后者管微服务之间的东西向调用,两者解决的是不同场景的问题,不能互相替代。

为何常见:大多数入门教程和面试题都从 Nginx 讲起,而南北向流量最直观,容易让人以为负载均衡只有“入口网关”一种形态。

误区只要把服务实例从 3 个加到 30 个,负载均衡会自动让系统变得坚不可摧。

正解负载均衡只负责“分活”,不负责“修路”。如果健康检查失效,请求会源源不断发给已宕机的节点,错误率反而上升;各客户端缓存的服务列表不同步时,流量还会被“楔”到个别节点上,形成雪崩。增节点必须配合注册中心、健康检查和优雅上下线,否则只是把风险摊到更多机器上。

为何常见:集群扩容的收益直观可见,但“分活不均”和“坏节点未摘除”这两个隐患不到故障时刻不会暴露。

误区调用失败就自动重试,多试几次总能成功,反正不会更糟。

正解重试不是免费的保险。上游已经超时拥堵时,重试只会雪上加霜,形成重试风暴;对扣款、下单等非幂等接口重试,还会产生重复订单等严重副作用。正确做法是限制最大重试次数、使用指数退避加抖动、只对幂等请求重试,并设置全局超时预算。

为何常见:本地调试时重试确实经常有效,让人忽略了分布式环境下的排队效应和副作用累积。

误区Envoy 是云原生标配,功能全面、治理集中,Ribbon 是过时技术,应该全面换成 Envoy。

正解Envoy 多一跳代理,延迟和运维复杂度都在 Ribbon 之上;Ribbon 进程内直连、零额外跳数,在 Spring Cloud 体系内依然高效。选型取决于流量模型:内部高频低延迟调用适合进程内,跨语言、入口流量、需要统一观测才适合代理。技术没有新旧,只有合不合适。

为何常见:技术潮流让从业者容易“喜新厌旧”,而忽略了延迟预算、运维成本和团队掌握度这些真问题。

面试题

Q1. Ribbon 在微服务调用中扮演的角色是?

单选题 · easy

  • A.服务端集中式负载均衡器
  • B.客户端进程内负载均衡器← 正确答案
  • C.网关层的流量分发组件
  • D.域名解析层的分发器

答案:B。Ribbon 以 jar 包形式运行在调用方进程内,从注册中心拉取列表并自行决策,属于客户端负载均衡。A 是 Nginx/Envoy 一类的角色,C 是网关,D 是 DNS 负载均衡,都不符合。

Q2. Envoy 最常见的部署模式是?

单选题 · easy

  • A.与应用同机部署的 Sidecar 代理← 正确答案
  • B.独立于应用的集中式代理
  • C.嵌入业务代码的 SDK 类库
  • D.部署在 DNS 层的域名代理

答案:A。Envoy 以 Sidecar 模式挂在业务容器旁边,与应用同 Pod 部署,替业务收发流量。B 是 Nginx 式的集中代理,C 是 Ribbon 式的 SDK 形态,D 是 DNS 层的设施,都不对。

Q3. 轮询策略选择下一个实例的方法是?

单选题 · mid

  • A.当前下标加一后对总数取模← 正确答案
  • B.生成随机数对实例数取模
  • C.选择响应时间最短的实例
  • D.选择权重最大的那个实例

答案:A。轮询的公式是 next = (current + 1) mod n,像转盘一样挨个分发。B 是随机策略,C 是响应时间加权,D 是权重优先,均与轮询无关。

Q4. 健康检查设置“连续失败 3 次才摘除”的主要目的是?

单选题 · mid

  • A.让故障节点尽快被摘除
  • B.过滤掉瞬时的网络抖动← 正确答案
  • C.减少健康检查的探测流量
  • D.让客户端缓存尽快过期

答案:B。网络闪断、GC 停顿等瞬态抖动很常见,若单次失败立即摘除,节点会频繁上下线。连续失败阈值的数学依据是 p 的 k 次方,指数级过滤抖动。A 是单次失败就摘除的效果,C、D 与阈值设定无直接关系。

Q5. 动态权重通常依据哪个指标计算?

单选题 · mid

  • A.实例 CPU 使用率的高低
  • B.平均响应时间的倒数← 正确答案
  • C.实例内存占用的大小
  • D.服务最近一次的启动时间

答案:B。主流动态权重用响应时间倒数为权重,公式 W_i = R_min / R_i,响应越快权重越大。CPU 和内存是资源类指标,通常做限流而不是路由权重;启动时间与权重无关。

Q6. 下列哪类请求最适合自动重试?

单选题 · hard

  • A.用户账户的扣费操作
  • B.幂等查询类的读取请求← 正确答案
  • C.订单创建类的写入请求
  • D.短信验证码的发送请求

答案:B。幂等请求重试任意次结果不变,安全可靠。扣费、下单、发短信都是非幂等写操作,重试可能重复扣款、重复下单,必须引入幂等键或干脆禁止自动重试。

Q7. 四层负载均衡主要依据什么信息转发?

单选题 · easy

  • A.HTTP 请求的 URL 路径
  • B.源目 IP 与端口四元组← 正确答案
  • C.应用层的会话 Session
  • D.消息队列中的消息主题

答案:B。四层工作在传输层,只看 IP 和端口,速度快;七层工作在应用层,才解析 URL、Header、Session 等业务信息。D 与负载均衡层次无关。

Q8. 负载均衡的常见分发策略包括哪些?

多选题 · mid

  • A.轮询与加权轮询策略← 正确答案
  • B.加权随机采样策略← 正确答案
  • C.一致性哈希分担策略← 正确答案
  • D.全量广播复制策略

答案:ABC。轮询、随机、哈希都是经典的负载均衡策略,各有适用场景:轮询均匀、加权体现能力差异、一致性哈希保证会话亲和。全量广播是消息队列的扇出模型,不是负载均衡策略。

Q9. Envoy 相比 Ribbon 的优势有哪些?

多选题 · hard

  • A.支持多语言无侵入接入← 正确答案
  • B.流量治理集中便于观测← 正确答案
  • C.每一跳的延迟必然更低
  • D.路由配置支持动态热更新← 正确答案

答案:ABD。Envoy 以 Sidecar 形式接入,业务代码零侵入,多语言通吃;控制面集中治理,流量观测性强;xDS 协议支持配置动态下发。但多一跳代理必然增加延迟,C 恰恰是 Ribbon 的优势,故不选。

Q10. 关于平滑加权轮询算法,下列说法正确的有?

多选题 · extreme

  • A.每轮先给各节点累加自身权重← 正确答案
  • B.被选中的节点要减去本轮总权重← 正确答案
  • C.大权重节点永远不会被连续选中
  • D.小权重节点永远不会被选中

答案:AB。平滑加权轮询每轮先令每个节点 current += weight,再选 current 最大的节点,选中后令该节点 current -= total,因此 A、B 正确。C 错误:大权重节点仍可能连续两次被选中(如权重 5:1:1 时前两轮都是 A),只是不会长期垄断;D 错误:小权重节点每隔若干轮也会被选中,保证长期比例公平。