ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

负载均衡策略全解析:内置方案不够用时如何自定义

负载均衡策略全解析:内置方案不够用时如何自定义

前阵子在帮一个朋友排查网关抖动问题,聊到负载均衡时他随口问了一句:“咱这轮询用得挺好啊,为什么要搞那么复杂?” 我当时笑了笑,回了一句:“等你遇到一个节点被慢查询堵死、其他节点闲得发慌的时候,你就明白了。”

负载均衡说白了就一件事:把进来的流量合理地分给后面一堆节点去处理。但“合理”这两个字,才是真正麻烦的地方。市面上成熟的负载均衡器——不管是用 Nginx 做七层转发、用数据库中间件做读写分离,还是在微服务网关里挑实例——内置策略翻来覆去就那几种,但每种策略背后都有它的适用边界和隐含假设。这篇文章我打算把常用策略一次性捋清楚,再重点聊聊一个很多人卡住的问题:当内置策略都不够用的时候,怎么设计一套自己的策略?

如果你是刚接触网关、微服务或者高可用架构的新人,这篇能帮你建立对负载均衡策略的完整认知;如果你已经在用 Nginx、网关或自研代理,但总觉得流量分配“差点意思”,这篇里关于自定义策略的设计方法和踩坑经验,应该能给你一些直接能用的思路。

1. 常用负载均衡策略全盘点:原理、适用场景与典型坑点

1.1 轮询与加权轮询:最基础的平权方案

轮询(Round Robin)是所有策略里最直觉的一种:请求按顺序轮流发给每个后端节点,A、B、C、A、B、C,循环往复。它最大的优点就是简单,不需要关心节点当前状态,也不需要额外采集数据,只要节点列表不变,分发就是绝对公平的。

加权轮询(Weighted Round Robin)是它的升级版。现实中后端节点很少规格完全一致,可能是8核16G和4核8G混着部署,或者新老机器混用。这时候给每个节点配一个权重,比如新机器weight=3,老机器weight=1,调度器按权重比例分配流量,让算力大的节点多接请求。

但轮询和加权轮询有一个共同的问题:它们都是静态策略,调度时完全不看节点实际状态。就算某个节点已经CPU跑满了、内存快爆了,轮询照样会把新请求送过来,该卡还是卡,该超时还是超时。所以这类策略更适合后端节点处理能力均衡、请求耗时可预测的场景,比如纯静态文件服务、无状态计算任务。

这里补充一个容易踩坑的小细节:加权轮询不同实现方式的“平滑性”差别很大。有的实现是纯概率式加权(每次都按权重比例随机挑),有的是平滑加权轮询(比如Nginx的实现)。平滑加权轮询的目的是避免短时间内同一个高权重节点被连续集中命中,如果自己实现加权轮询,千万别粗暴地“先发给权重最大的节点N次,再发给下一个”,那样会在单节点上制造明显的流量毛刺。

1.2 最少连接与加权最少连接:动态感知的调度

最少连接(Least Connections)开始引入“动态”的概念了:调度器会记录每个后端节点当前活跃连接数,新请求到达时优先发给活跃连接数最少的节点。这个策略在处理长连接、请求处理时间差异大的场景下,比轮询均衡得多,因为它是真正在看“谁现在最闲”。

不过,最少连接有一个隐蔽的坑——连接数少不等于负载低。举个例子:A节点上只有1个连接,但这是个复杂的SQL聚合查询,可能要跑两秒钟;B节点上挂着5个连接,但全是Redis的快速读写,每个只需要几毫秒。按最少连接策略,调度器会不停地把新请求塞给A节点,结果A节点越积越多,响应越来越慢。如果后端是数据库中间件或者复杂业务服务,这个坑会踩得非常惨。

加权最少连接(Weighted Least Connections)在这个思路上加上了权重维度,在算“谁最闲”的时候把节点能力差异也考虑进去。但仍然没有解决刚才那个问题——它看的是“数量”而不是“压力”。连接数这个指标太粗糙,只能当入门级动态策略来用。

1.3 IP哈希与一致性哈希:会话保持与扩容难题

IP哈希(IP Hash)的做法是对客户端IP做哈希计算,把特定IP的请求固定映射到同一个后端节点。典型场景是会话保持:早期很多应用把用户会话存在单机内存里,没有做Session共享,如果同一个用户每次请求落到不同节点,登录态就丢了。IP哈希能用最简单的方式解决这个问题——同一个用户永远打同一个节点。

但IP哈希有三个天生缺陷:

  • IPv4地址网段分布不均匀,某些大流量来源会集中在一个网段或一个IP上(比如公司出口NAT),哈希之后可能把大量流量都砸向同一个节点,直接把它打爆。
  • 移动互联网环境下客户端IP频繁变化(Wi-Fi切4G、弱网重连等),会话保持效果大打折扣。
  • 节点增减会导致映射关系大规模失效,普通哈希取模的方式,节点从3个变成4个时,大部分流量都会被重新映射到新节点,如果节点上有本地缓存,命中率会骤降。

一致性哈希(Consistent Hashing)就是为了解决最后这个问题出现的。它的核心是构建一个哈希环,节点和请求都映射到环上,每个请求由顺时针遇到的第一个节点处理。节点变化时只需要迁移环上相邻区间的数据,影响范围大大缩小。这也是很多缓存中间件和自研网关在节点路由上的默认选择。

不过一致性哈希也不是银弹——它只能保证“哈希结果的稳定性”,并不保证“节点之间的负载均衡”。如果节点在环上分布不均匀,照样会出现某几个节点接走大量请求的情况,所以工程实践里一般会引入虚拟节点技术,给每个真实节点在环上多放几百个影子节点来抹平分布差异。

1.4 响应时间与自适应类策略:让数据说话

再往下走,就是一边调度一边收集反馈数据的策略了。最快响应时间(Fastest Response Time)会统计每个节点最近一段时间处理请求的平均响应时间,新请求优先交给响应最快的节点。它的理念很直接:响应快说明现在闲,交给它处理,整体体验最好。

这类策略的实际效果依赖于统计数据的可靠性。如果统计窗口太短,抖一下就频繁切换目标;如果窗口太长,又不能及时反映节点当前状态。实践中一般会配合滑动窗口和最低采样数来避免“样本太少但是数据看起来很好”的情况。一些商业负载均衡器还支持自适应策略,把节点的CPU、内存、磁盘IO、网络带宽、当前连接数、平均响应时间全部汇总成一个综合负载指标,再按指标倒序分发流量。这种策略最理想,但对监控数据采集的完整度要求也最高。

我在下面整理了一张对比表,把常用策略放在一起对照着看会非常直观:

策略名称核心原理是否动态典型适用场景最容易踩的坑
轮询依次轮流分发否同配置无状态服务节点负载不均时仍然硬塞
加权轮询按权重比例分发否集群节点规格混合权重分配拍脑袋,不够准
最少连接优先发给活跃连接最少节点是HTTP长连接、数据库连接池连接数≠真实负载
IP哈希按来源IP固定映射否会话保持大网段IP导致单点热点
一致性哈希哈希环路由否分布式缓存、路由寻址环分布不均匀需虚拟节点
最快响应时间统计历史响应耗时是接口RT差异大的业务统计窗口不合理导致调度抖动

2. 内置策略的四个典型翻车场景:为什么需要自定义

讲了这么多内置策略的“坑”,有些人可能会想:那我把这些策略组合着用不就行了?比如外层用IP哈希做会话保持,内层再用最少连接动态分发。这种做法在简单场景下确实能解决问题,但当你面对的是真实生产环境时,组合内置策略通常解决不了“调度意图”层面的问题。下面分享四个我之前实际遇到过的场景,看完你就能理解为什么一定要走到自定义这一步。

2.1 翻车场景一:轮询把流量打到了热点节点

某次我负责的一个业务集群做了垂直拆分,把接口分成了普通查询和重度计算两类。当时CLB(容器负载均衡)默认配的是加权轮询,所有节点权重一样。结果每天下午业务高峰,集群里总有一两个节点CPU冲到90%以上,其他节点却只有30%。查了半天才发现,重度计算接口虽然只占总量的20%,但它独占资源,轮询策略把这些重计算请求相对均匀地撒给了所有节点——这本身没问题——但是重计算请求一旦落到某个节点上,该节点的资源占用会瞬间飙升,而轮询完全感知不到,仍然继续往这个热点节点塞新请求。

这个场景要的不是“哪种策略更均衡”,而是要让调度器能识别并避开正在过载的节点,内置策略里没有一种直接提供这个能力。

2.2 翻车场景二:最少连接被慢请求拖垮

另外一次是在数据库代理场景。我们用最少连接策略做读写分离中间件,假设后面挂了6个数据库实例。上线初期一切正常,后来某天DBA在其中一个实例上跑了一个超长的分析任务,占用了大量连接资源。按最少连接的逻辑,这个实例因为“空闲连接”最多反而成了首选目标,新请求继续涌入,直接把业务拖垮了。

问题根源在于:**最少连接只是连接数量的守恒,不是资源耗用的守恒。**数据库连接池里的连接数量恒定,不代表每个连接承载的负载是恒定的——一个跑两分钟的慢查询和一个跑两毫秒的快查询,在“连接数”这个指标上没有任何区别。只有引入真实的资源指标(比如活跃线程数、当前查询耗时、锁等待时间等)才能反映实例的真实压力,而这些指标负载均衡器默认是拿不到的,必须通过扩展链路自己去采集。

2.3 翻车场景三:IP哈希在移动网络下的尴尬

还有一个经典场景是移动端API网关,客户端通过公网访问。一开始我们想用IP哈希做最简单的会话保持,但上线后很快就发现问题:大量用户来自运营商NAT网关后的同一个出口IP,这些IP在哈希表上可能恰好落在同一个节点,直接把那个节点打到CPU满载。更麻烦的是,用户切网(Wi-Fi切5G、飞行模式恢复)后IP就变了,会话保持目标也没达到。

这里核心矛盾是:IP哈希假设“一个IP是一个稳定的客户端”,但真实网络环境里这个假设经常不成立。这种情况要么换用基于Cookie、Token等业务标识的会话保持方案,要么就需要自定义一个策略——比如对IP先做一层预处理(截取C段、配合端口哈希等),再把业务标识作为附属维度一起参与调度。

2.4 翻车场景四:统一策略无法表达业务优先级

最让我决定“必须自定义”的一次经历,是一个混合业务场景的网关:同一套网关既要转发线上交易接口(延迟敏感,如果后端出问题必须立刻切流量),又要转发批量离线任务(对延迟不太敏感,但要求尽量不丢)。统一用最少连接,离线任务会把连接数低但CPU其实快被打满的节点选走,等到真实交易请求过来时,节点已经无力处理。

这类场景的关键词是**“多优先级感知”**。调度策略需要知道流量的来源与业务等级:VIP客户请求应当优先路由到低负载节点,普通请求可以接受稍差一点的服务,离线任务则要主动避开繁忙节点。这个意图无法用任何单一内置策略完整表达,只能通过自定义策略,在调度算法里加入“业务等级权重”和“按等级差异化打分”的逻辑。

3. 自定义策略的设计框架:从业务目标到打分函数

聊完了为什么需要自定义,下面进入正题:该怎么做。自定义负载均衡策略没有想象中那么玄,它的本质其实就是三步——定目标、采指标、算选择。把这三件事想清楚,自定义策略就成了一大半。

3.1 先把目标说清楚

很多人在设计自定义策略下手第一步就错了,他们急着去写算法、调参,却忘了先回答一个问题:你到底想让调度器优化什么?

目标选项通常绕不开这几个:

  • 最小化平均响应时间:让每个请求都快。
  • 最小化长尾延迟(P99):让最慢的那批请求也可控。
  • 最大化整体吞吐量:在资源有限的情况下尽量多处理请求。
  • 成本优先:优先使用便宜的节点(比如竞价实例),贵节点兜底。
  • 稳定性优先:宁可牺牲一点平均性能,也要避免任何单节点过载。

目标不同,自定义方向的差别非常大。想做成本优先,你的打分函数里就要给便宜节点加较大权重,贵节点只有当便宜节点全满时才被选到;想做P99优化,你就要重点采集响应延迟的分位数而不是平均值,还要设计快速熔断机制。如果一上来就想着“综合一下”,最后往往什么都优化不好。

我习惯的做法是:在写第一行代码之前,先输出一句明确的调度策略描述,比如“在交易流量高峰期,优先将请求调度到CPU使用率低于60%、最近5分钟平均RT小于200ms的节点,并在节点连续3次健康检查失败时快速摘除”。这句话写清楚了,后面的所有设计都是围绕它展开的。

3.2 指标采集、归一化与滑动窗口

目标确定之后,下一步是确定调度器能看到哪些指标。常见可选指标包括:

  • CPU使用率:直接反映计算资源压力。
  • 内存使用率:内存吃紧往往伴随GC频繁或OOM风险。
  • 活跃连接数:反映并发处理压力。
  • 平均响应时间(RT):反映服务自身处理能力和队列积压情况。
  • 队列长度:衡量请求在节点上排队等待的时间。
  • 带宽/磁盘IO:IO密集场景下比CPU更有代表性。

但不同指标的量纲完全不同——CPU是0到100的百分数,RT可能是几十到几千毫秒,连接数可能是几个到几百个。如果直接拿这些数字做加权求和,最后一定是量纲大的指标主导全部决策。所以自定义策略里几乎一定会做一次归一化处理。

最简单的归一化是线性归一化,把指标映射到0到1区间:(当前值 - 最小值) / (最大值 - 最小值)。但线性归一化对离群值非常敏感,如果一个节点的RT飙到5000ms,其他节点都是200ms,最高值一来所有节点的归一化结果都会变得很极端。更稳的办法是用百分位映射:比如把RT的历史数据先画出P50、P90、P99,然后根据当前RT落在哪个区间打分,落在P99以上直接给最低分甚至标记为不健康。这种打分方式对异常值更鲁棒。

还有一个新手容易忽略的点:指标采样的时间粒度。如果你从监控系统拿到的节点CPU利用率是5分钟粒度的平均值,用它来做毫秒级的调度决策,相当于拿着昨天的天气预报决定今天出门穿不穿外套——数据早就过时了。更合理的做法是让节点Agent每隔几秒上报一次当前状态,调度器端再对这些散点做滑动窗口平滑(比如取最近30秒的均值),既滤掉了瞬时抖动,又能保证数据新鲜度。

3.3 打分公式与加权随机选择

当多个指标都归一化好之后,就可以设计打分公式了。预设一个最简单的场景:我们要避免高CPU、高RT的节点,同时兼顾连接数。打分逻辑可以这样写:

def calculate_score(node): # node.cpu_normalized: 0~1,越大压力越大 # node.rt_normalized: 0~1,越大响应越慢 # node.conn_normalized: 0~1,越大连接数越多 load_score = (0.5 * node.cpu_normalized + 0.3 * node.rt_normalized + 0.2 * node.conn_normalized) # 希望选中压力小的节点,所以取负相关 return 1.0 - load_score

这个公式表达的意思非常清楚:CPU压力权重最高(0.5),响应时间次之(0.3),连接数参考一下(0.2)。拿到每个节点的得分之后,怎么选择?很多人第一反应是“选得分最高的那个”。但在实践中,最好不要做最大者通吃,因为这样会造成少量得分最高的节点被集中打满流量,反而引发新的热点。更稳定的做法是加权随机:得分高的节点获得更高的被选概率,但得分略低的节点也有一定概率被选中。这样流量分配会呈现一种自然的概率分布,既偏向好节点,又不至于把所有鸡蛋放在一个篮子里。

3.4 兜底与降级设计

自定义策略最容易翻车的环节不在策略本身,而在策略失效时你怎么办。

举个例子:如果你采集指标的Agent挂了,或者节点上报接口超时,导致所有节点的指标数据都为空,这时候你的打分公式怎么算?我见过不少实现直接把这类节点当成0分处理,结果就是所有流量都被打到一个节点上,直接雪崩。正确的做法是为“数据缺失”设置独立的降级路径:当某个节点指标数据缺失或过期超过阈值时,把它从候选集合中临时摘除,或者给它一个保守的默认分数(比如所有节点得分的平均值),而不是默认给0分。

同样的道理也适用于健康检查。如果你依赖HTTP健康检查来摘除故障节点,健康检查本身也有误报概率。在关键场景里,我会把健康检查和调度策略分开处理:健康检查只负责“硬摘除”(明显不可用的节点立刻下线),调度打分只负责“软避让”(压力大的节点降低被选概率)。这样一个节点即使被打出低分,也还保留一点概率接收流量,不至于因为监控误报导致节点完全空转。

4. 三条可落地的自定义实现路径

方法框架搭好之后,最常被问的问题就是:我到底应该在哪里写我的自定义策略?这一节我给出三条实际可落地的路径,按实施成本从低到高排列,你可以根据自己的技术栈选。

4.1 路径一:OpenResty Lua快速定制

如果你已经在用Nginx或OpenResty做七层网关,那么最轻量级的自定义方式就是写在OpenResty的balancer_by_lua阶段。这个阶段允许你完全接管upstream节点的选择逻辑,不再局限于Nginx自带的round_robin、least_conn等指令。

下面是一段简化示例,展示了如何在OpenResty里自定义一个简单的加权随机均衡器,权重来自后端节点上报的活跃连接数:

-- balancer_by_lua_block 中调用 local nodes = { { pool = "backend_a", conn = 12, capacity = 100 }, { pool = "backend_b", conn = 5, capacity = 100 }, { pool = "backend_c", conn = 80, capacity = 100 }, } local function score(node) -- 连接数越低得分越高 return math.max(0.1, 1 - node.conn / node.capacity) end -- 计算所有节点的得分比例,然后按比例随机选 local total_weight = 0 for _, n in ipairs(nodes) do n.weight = score(n) total_weight = total_weight + n.weight end local r = math.random() * total_weight local cursor = 0 local selected_pool for _, n in ipairs(nodes) do cursor = cursor + n.weight if r <= cursor then selected_pool = n.pool break end end -- 选中之后设置当前请求要转发的upstream peer if selected_pool then ngx_balancer.set_current_peer(selected_pool) end

这个实现里的核心逻辑是:节点连接数越少、离容量上限越远,得分就越高,被选中的概率也越大。真正落地时,conn指标通常来源于一个共享缓存(比如ngx.shared.DICT),由后端的Agent或采集任务定时写入。OpenResty方案的最大优点是不用改后端任何代码,纯网关层改造,上线风险低,非常适合快速验证自定义策略的效果。

需要注意的一点:balancer_by_lua是在每个请求的upstream选择阶段执行的,里面的逻辑必须足够轻量。不要在现网请求链路上做重计算、不要远程调用Redis获取指标(每条请求都访问Redis会把Redis拖垮)。正确的姿势是让采集器定时把节点状态同步到一个共享内存字典,Lua代码只从共享内存里读、做纯内存计算,整个过程控制在微秒级别。

4.2 路径二:网关/框架扩展点

如果业务跑在Java技术栈,用的又是Spring Cloud这样自带负载均衡的微服务框架,那更自然的做法是在客户端负载均衡器层面扩展。Spring Cloud中的LoadBalancer接口就是设计给开发者做自定义的——你可以实现自己的ReactiveLoadBalancer,拿到当前可用的服务实例列表,然后按自己的逻辑挑一个实例返回。

这类扩展的实现思路依然是那套“打分+加权随机”的办法。我写过的一个简化版本是:服务实例的元数据里带了权重配置和实时的CPU水位,客户端在做服务发现时拿到这些数据,然后走一遍打分逻辑即可。因为客户端负载均衡是拿注册中心里的实例列表来选的,它天然就能感知到实例的心跳状态,配合自定义权重就能实现非常灵活的调度策略。

Dubbo这类RPC框架也提供类似的SPI扩展点,很多团队在内部就是通过自定义LoadBalance来做到按机房优先、按调用链级别优先级路由的。如果你已经在框架里做了服务注册发现,优先使用框架提供的扩展点,而不是再架设一层独立的网关——不然两层负载均衡叠加,理解和排查问题的成本都会翻倍。

4.3 路径三:自研调度器完整实现

如果现有的网关、框架都无法满足你的场景(比如调度逻辑极度特殊、需要依赖一套深度定制的指标体系),那最后一条路就是自己写一个独立的调度器。这个调度器最基本的能力就是:拿到所有节点状态,根据策略算出一个目标节点,再把结果返回给上层调用方。

下面给出一个完整的、可直接跑通的最小调度器核心实现,用的Python伪代码风格,方便理解:

class Node: def __init__(self, node_id, cpu, rt, conn, healthy=True): self.node_id = node_id self.cpu = cpu # 0~100 self.rt = rt # 毫秒 self.conn = conn self.healthy = healthy class CustomBalancer: def __init__(self, nodes, window_size=30): self.nodes = nodes self.window_size = window_size self.rt_history = {n.node_id: [] for n in nodes} def normalize(self, value, low, high): # 线性归一化,并做边界裁剪 if high <= low: return 0.0 return max(0.0, min(1.0, (value - low) / (high - low))) def safe_rt(self, node): # 如果某个节点没有RT历史数据,用所有节点的平均RT兜底 if len(self.rt_history[node.node_id]) == 0: all_rt = [self.rt_history[n.node_id] for n in self.nodes] all_rt = [x for sublist in all_rt for x in sublist] if not all_rt: return 500 # 完全冷启动,给一个保守值 return sum(all_rt) / len(all_rt) recent = self.rt_history[node.node_id][-self.window_size:] return sum(recent) / len(recent) def score(self, node): if not node.healthy: return 0.0 cpu_score = 1 - self.normalize(node.cpu, 0, 100) rt_score = 1 - self.normalize(self.safe_rt(node), 0, 5000) conn_score = 1 - self.normalize(node.conn, 0, 1000) # 权重:CPU最重要,RT其次,连接数参考 return 0.5 * cpu_score + 0.3 * rt_score + 0.2 * conn_score def choose(self): scores = [self.score(n) for n in self.nodes] total = sum(scores) if total <= 0: # 兜底:全部不可用或全为0分时随机选一个健康的 import random healthy_nodes = [n for n in self.nodes if n.healthy] return random.choice(healthy_nodes or self.nodes).node_id import random r = random.uniform(0, total) cursor = 0 for n, s in zip(self.nodes, scores): cursor += s if r <= cursor: return n.node_id return self.nodes[-1].node_id

这份代码里有两个值得留意的设计:一是safe_rt函数在节点没有任何历史RT数据时,会退回使用全集群平均RT,避免冷启动节点被当成“零RT”而疯狂接流量;二是choose的最后兜底逻辑,当所有节点都不可用时返回一个随机健康候选,宁可短暂分配错误也不能让整个请求链路空转。这两个细节在自研调度器里几乎是必备的安全阀。

5. 上线前的实测与避坑清单

自定义策略写完之后,不要立刻推到生产环境。我在这个环节踩过的坑,说几个有代表性的。

5.1 指标新鲜度决定策略上限

有一次我把节点CPU数据采集频率设置为每分钟一次,策略在压测环境表现很好,但上线后经常看到有节点被打满,另一些却在闲置。后来发现原因很简单:压测环境机器少、指标刷新快,一旦上了生产,机器增多、上报间隔变长,调度器看到的CPU数据已经滞后了一分钟。在这期间某个节点可以接收大量请求瞬间冲到100%,但调度器还拿着旧数据把它当最优节点,继续猛推流量。

解决方案是把采集周期压缩到5秒一报,同时调度端对超过10秒没有更新的节点临时降权。宁可少接一点流量,也不能拿过期数据做决策。

5.2 冷启动与默认值

新扩容一个节点进入集群时,它的RT历史是空的,连接数是0,CPU也是0。很多自定义策略会把这个节点当成“完美节点”,呼啦一下把所有流量都切过去,结果新节点被瞬时打瘫。正确的做法是为新节点加一个冷启动保护期,比如前两分钟只给它分配正常流量的20%,同时用一个保守默认分数参与打分(而不是最高分)。等它积累了足够的状态数据之后,再逐步恢复它的正常权重。

这个思想和熔断很像:没有足够数据做决策时,永远选择最保守的方案。

5.3 全链路压测与对照验证

上线之前,至少做两个验证:一是用生产流量的录制回放做全链路压测,对比默认策略和自定义策略下的吞吐量、P99延迟、节点CPU均衡度;二是在灰度环境里做A/B对照——把集群的一半流量走默认策略,一半走自定义策略,观察一段时间。其次,压测过程中要故意模拟故障(把某节点直接kill掉、把某个节点CPU压到满负荷),看自定义策略是否能快速感知并避开故障节点。

我习惯把自定义策略的调度决策全部打日志,哪怕是A/B阶段。这样一旦出问题,可以直接回放每一个请求当时被哪个节点接收、为什么选这个节点、当时集群状态是什么样。有这层数据兜底,排查问题会快很多。

最后再分享一条我对自定义策略的个人看法:能用内置策略解决的场景,尽量别自定义。自定义意味着你要自己维护指标采集、逻辑计算、兜底降级、故障容忍这一整套链路,复杂度是指数级上升的。只有当业务意图确实无法用现有策略表达(比如多优先级调度、资源感知路由、跨节点成本优化),才值得走自定义这条路。但如果真走到了这一步,按照“定目标、采指标、打分选节点、加兜底”这个框架来做,整个事情不会失控——这也是我自己踩过一遍坑之后沉淀下来的经验。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进