日期:2026-11-08
我们的推理服务上线初期是单实例,扛不住就堆副本,前面挂个简单负载均衡做随机轮询。问题很快暴露:单副本一挂,轮询就把请求打到有问题的实例上,整批超时;更隐蔽的是热点 key,比如某些高频缓存查询,全挤在同一副本上,那台机器的缓存命中率奇高、邻居却空着,扩容一次就发现负载极不均衡。我们当时被这个不均衡坑过,加机器反而没提速,排查半天才定位到是路由没打散。
这个项目要解决的,就是把"请求怎么去哪个副本"这件事从瞎轮询,变成可预期、可容错、能均摊的路由策略。目标不只是高可用,还要让缓存和显存这类有状态资源在副本间分布得更合理。
落地后,网关或接入层按请求特征算一致性哈希,把请求稳定映射到哈希环上的副本节点。副本增减时,只有少量 key 需要迁移,不会全量重分布。每个副本挂健康探测,疑似故障的节点被摘出环,流量自动绕开,恢复后再平滑放回。
针对热点 key,我们在哈希环上做了虚拟节点打散,同一个逻辑 key 不再永远落在单机,而是按时间片或采样在几个虚拟节点间轮转,把热点摊开。同时请求级加了有限重试,落到故障节点的请求快速换一个健康节点,重试放大用令牌桶控着,不让一次故障演变成重试风暴。
后台看的是单副本负载方差、缓存命中率、故障摘流时延和重试率这几条线。负载方差一高,基本就是路由没打散或者出现热点,运维一眼能定位。
绕不开的是一致性哈希环的抖动。虚拟节点数设少了,增减副本时迁移不均;设多了,环的维护成本和内存涨。我们按副本规模选了二百五十六个虚拟节点每物理副本,实测迁移波动在可接受区间,且摘流时影响面可控。
紧接着要处理的是故障无损摘流。探测不能太灵敏,一次网络抖动就摘节点会来回横跳。我们用了连续三次探活失败才摘、连续五次成功才放回的稳态策略,配合摘流前的优雅排空,把在途请求先放完再移除,避免硬摘导致在途请求失败。
再往下是热点打散。纯一致性哈希对热点 key 无效,因为同一个 key 永远去同一节点。我们给高频 key 加了一个时间片维度的二次哈希,key 在时间窗口内稳定,跨窗口轮换到不同虚拟节点,既保了短时间内的缓存局部性,又把长期热点摊平。
最后是重试放大。故障节点被摘之前,重试可能已经打爆邻居。我们用令牌桶限制单客户端的重试速率,且重试只换健康节点,绝不重试已经失败的同节点,把放大系数压在两倍以内。
案例片段(已脱敏): 一致性哈希与热点打散配置(节选):
routing: algo: consistent_hash vnodes_per_replica: 256 health: fail_threshold: 3 # 连续3次探活失败摘流 recover_threshold: 5 # 连续5次成功放回 drain_before_remove: true hotspot: enable: true time_slice_sec: 30 # 热点key时间片轮换 retry: token_bucket: 2.0 # 重试速率令牌桶 only_healthy_target: true上线后日志:单副本负载方差下降 63%,缓存命中率由 54% 升至 71%。
方案跑满一个月,单副本负载方差下降了约六成三,原来那种"一台热死、邻居闲死"的情况基本消失。缓存命中率从五成四提到七成一,直接省下可观的回源计算和向量检索成本。故障摘流时延,从探测到流量完全绕开,控制在两秒以内,用户侧几乎无感。
重试率在全副本健康时接近零,模拟单副本宕机演练时,重试放大系数稳定在两倍以内,没有出现重试风暴把邻居也带崩的情况。全年几次真实副本故障,均无用户侧可见的批量失败。文中数据为项目复盘口径,均为示意值,非审计级精确数字。
我们当时纠结过要不要上更复杂的自适应路由,后来觉得一致性哈希加打散已经够用,过度设计只会增加运维心智负担,这点上克制是对的。
多副本路由别用纯随机轮询,热点 key 会全挤在一台,扩容都不见效。一致性哈希能把迁移影响面锁住,但虚拟节点数要按规模调,太少迁移不均,太多维护贵。
故障摘流不能太灵敏,连续失败才摘、连续成功才放回,摘之前先把在途请求排空,硬摘必出事。热点打散要保短时间局部性、跨时间片轮换,既利用缓存又摊平负载。重试必须限速率且只换健康节点,否则一次故障会被重试放大成雪崩。
回头复盘,这趟路由改造最划算的一笔是热点打散,之前加机器都不见效,根源就在路由没打散。一致性哈希本身不复杂,难的是虚拟节点数和摘流稳态参数,这俩调对了负载方差一下就平。我们当时纠结过要不要上更复杂的自适应路由,后来觉得够用就好,过度设计只会增加运维心智负担,这点上克制是对的。
顺带,虚拟节点数别照抄网上默认值,得按你副本规模算,我们当时抄了个别人的配置,迁移波动大,自己调了一轮才稳。摘流稳态参数也一样,连续失败才摘、连续成功才放回,这个节奏是踩过来回横跳的坑才定下来的。