第五届阿里天池中间件比赛经历分享

第五届阿里天池中间件比赛经历分享

本文记录了作者与队友们参加2019年第五届阿里天池中间件的经历。初赛排名175/4000+队伍,幸运进入决赛。虽然最终方案比较简单,但是过程很是曲折。最后通过高分选手开源的代码,总结下不足与经验。决赛正在进行中,本文会不断更新。

初赛 自适应负载均衡算法

题目

三个provider,200:450:650,一个consumer。provider内部通过信号量模拟处理能力的动态变化。信号量数小于线程池大小,当超过信号量数量的请求到达时,wait。当超时时,consumer会接收到异常,认定此次请求失败。最终的评定标准是,60秒内的成功请求数最大。

思路

  1. 最开始想根据cpu、waiting线程数 回调,成绩不理想。原因:拿到回调时已发生变化
  2. 后来直接根据线程的比例进行加权随机,成绩小幅提升,但由于是随机的,还是有可能造成某个producer收到过多请求,导致请求积压甚至超时,使得tps不高。
  3. 捕获请求超时的Exception。当发往某个provider的请求出现了超时说明该provider已经撑慢了,所以接下来进行加权随机时,将该provider剔除。等到一定时间之后(50ms),才能参加随机。但是这个方案的成绩几乎没有提升。经我们分析,发现Exception的出现纪律并不多,导致效果不是很好。而且在50ms的退让期间内,其他的provider也可能被撑爆。
  4. 最后做了个AOP,可以动态的变化权重,此方案也是最终的最高分,175名。一开始按照200:450:600的权重随机分配,当分配给某个provider后,将它的权重减一。当收到该provider的回复后,权重再加一。这样就权重就可以动态的变化:在某个请求发送后还未处理完成,也就是consumer收到回复前,该producer的权重就会缩小。该方案的压测日志可以看出,每个请求几乎都能得到回复,不会超时。也就是说,provider的处理能力还没有达到极限,瓶颈在consumer这里。
  5. 后来想了一个优化方案:既然consumer出现了瓶颈,我们是否能将计算权重的工作分散到provider上,然producer自己统计收到了多少请求,完成了多少请求,两数之差就是处理中的请求,然后让provider通过回调发送给consumer。但是这种方案的分数却急剧下降。通过日志的调试,发现问题是其实跟第一个方案是差不多的:当consumer收到回调时,provider的统计值以及产生了很大变化。导致consumer收到后把它当做权重来计算后,会产生很大误差。
    最后由于截止日期临近,成就就停留在了126.6w分,175名。但是也成功进入了决赛圈(前200/4000+队伍)。

大佬们的方案

初赛截止后,有部分选手开源了自己的代码。我们学习了一下,其实我们缺少最终要的一点,也是高分的队伍关注的一点就是如何感知provider的处理能力动态变化:
https://zhuanlan.zhihu.com/p/71394061
https://mp.weixin.qq.com/s/hGTBRfdivxFCqJ-c4gPVaw
总结一下:

  1. 我们本应该想到却没有想到的就是使用一个令牌队列,提前按照线程数比例填满队列。就不用每次再进行权重的计算,而是直接从队列中取令牌,然后直接发往对应的provider,当受到回复后归还令牌。这样就大大减少了计算权重的时间。而且请求线程数总共1024,所以队列中三种令牌之和应该就是1024,这样每个provider就绝对不会撑爆。这种方案,成绩能打到127-128w,进入前100名。不过这也只是个小优化,并没有解决感知provider处理能力的问题。
  2. 如何捕捉provider处理能力变化:
    方案一:https://mp.weixin.qq.com/s/hGTBRfdivxFCqJ-c4gPVaw
    写Scala的老王借鉴TCP拥塞窗口的概念,将令牌和RTT存入跳表,跳表会根据RTT排序,此时的问题是:由于每次只从队列头部取令牌,所以当某个provider产生了一个大RTT,该provider的令牌就会排到后面,很难被取到(因为小RTT的令牌的不断地放回到它的前面),相当于权重下降后很难回复。所以改进措施是:当收到一个RTT小于3ms的回复时,将该provider提权,即从跳表的尾部抽出一个令牌,重新放到跳表中间位置。该方案能够达到130W分,最终老王获得了初赛第六。
    方案二:https://zhuanlan.zhihu.com/p/71394061

基于以下思路:

  • CPU负载与线程数存在着某种联系:[1,2,3]与[200,450,650]。即线程数与CPU核心数量的比例大致相同。
  • CPU负载与请求数存在一个正比关系。不管几核的CPU,只要没有请求,CPU负载都为零,这个很符合逻辑是不是;而对于处理相同的请求数,理论上讲CPU的核心越大,负载越小。但反过来讲,当CPU负载差不多的时候,双核的CPU所处理的请求数是不是就必然是单核CPU的两倍呢?不一定。

采用如下公式:

某privider的线程数=单位时间内请求数*log(最大线程数)*最大线程数/JVM的CPU负载
某provider的线程数=该provider线程分配系数/所有provider线程分配系数之和*1024

其中log(最大线程数)是CPU核心数的变种计法。由于log(1)=0,所以就借用了最大线程数来近似替代CPU核心数。

该方案分数能到128W+

你可能感兴趣的:(中间件)