参赛心得
整个赛季经过两个月长跑后已经结束,漫长的赛程对于每位参赛选手的毅力和技术都带来了巨大的考验。经过反复地猜想、实践与分析,最终获得A榜1800w和B榜276w、排名第10的成绩。
题目解析
本次赛题场景非常清晰,一个 Consumer 通过 dubbo 以固定并发调用3个Provider,其中每个 Provider 的处理能力不同且动态变化,我们需要实现一套负载均衡策略来促使其在有限时间内成功响应更多的请求。
测试过程:
- PTS 作为压测请求客户端向 Gateway(Consumer)发起 HTTP 请求,Gateway(Consumer)加载用户实现的负载均衡算法选择一个 Provider,Provider 处理请求,返回结果。
- 每个 Provider 的服务能力 (处理请求的速率) 都会动态变化。
- 三个 Provider 的每个 Provider 的处理能力会随机变动以模拟超售场景
- 三个 Provider 任意一个的处理能力都小于总请求量
- 三个 Provider 的会有一定比例的请求处理超时(5000ms)
- 三个 Provider 的每个 Provider 会随机离线(本次比赛不依赖 Nacos 的健康检查机制,也即是无地址更新通知)
- 评测分为预热和正式评测两部分,预热部分不计算成绩,正式评测部分计算成绩。
- 正式评测阶段,PTS 以固定 RPS 请求数模式向 Gateway 发送请求,1分钟后停止。
- 以 PTS 统计的成功请求数和最大 TPS 作为排名依据。成功请求数越大,排名越靠前。成功数相同的情况下,按照最大 TPS 排名。
解题思路
通过赛题分析,我们需要实现一个强大的负载均衡算法,它可以:
- 自动探测 Provider 的服务能力,实时调整均衡策略
- 针对每个 Provider 独立限流
- 请求失败提前感知并快速失败
- 进行 Provider 自动离线与恢复(无效果)
- 通过提前预热计算,选择最佳并发区间
自动探测
这部分需要解决负载均衡的问题,要解决这个问题我们知道最大连接数和当前连接数两个指标。
一开始我们会为每个 Provider 都设置一个相同的最大连接数,然后基于最大可用连接算法进行负载均衡。
最后周期性统计每个 Provider 的请求数成功率和平均响应时间在保证总连接数不变的情况下,动态修改最大连接数使得每个 Provider 的成功率和响应时间相同。
限流
由于我们负载均衡时每个 Provider 的响应时间都相同,这使得我们能把 Provider 集群看作一个整体进行统一限流。若整个集群的请求成功率低于正常范围,则减少请求量;若请求成功率很高但QPS较低,则尝试增加请求量。
快速失败
快速失败是这次赛题最为关键的一部分,由于总请求数是相同的,若请求阻塞于无响应状态,则会导致评测时间内的总请求数大量降低。
我们根据周期内每个 Provider 的平均响应和最大响应计算下一个周期的超时时间。
设置超时最简单的方法是设置 timeout 参数,我们使用这种超时策略拿到A榜1730w的成绩。
RpcContext.getClientAttachment().setObjectAttachment("timeout", timeout);
做过超时日志分析的同学应该会发现,使用这种方式的超时时间精确度很低,线上的最小超时时间会在30-60ms区间波动。为了进一步增加请求利用率,我们对 dubbo 原有的超时进行了改进后将A榜超时控制在10ms左右,成为首个突破1800w的靓仔。
图表
最大连接数调整过程如下图:
平均响应时间和超时时间如下:
QPS变化:
总结与思考
我们在负载均衡这部份尝试了加权轮训、加权随机、最小连接数、最大可用连接,实际效果排序 最大可用连接>加权轮训=加权随机>最小连接数。通过日志分析后发现原因很简单, 最大可用连接算法能及时对最大连接数的变化做出反应,而最小连接数只有在活跃连接=最大连接的情况下才能有较及时的反应。
我们使用负载均衡算法保证每个 Provider 的响应时间和请求成功率趋于一致,这种策略并不总能保证集群拥有最佳的容量,或许可以探测每个Provider的最佳并发来作为权重。
最后,如果要说自己的参赛心得,我认为是突破自我、否定自己、多记录,多尝试、多分析,短短几百行代码,也是需要经历反反复复的雕琢。