一不小心,用力过猛,成了榜一大哥...
你好呀,我是歪歪。
我之前不是说了嘛,天池大赛又开始了。
就是这玩意:
这个比赛是从 2015 年开始办的,那个时候还叫做天池大赛。去年的时候 2020 年,不知道怎么改名叫云原生编程挑战赛了。
我觉得这个名字没有天池大赛好听,所以我还是叫它天池吧。
这次的比赛有三根赛道:
【赛道1】针对冷热读写场景的RocketMQ存储系统设计 【赛道2】实现一个柔性集群调度机制 【赛道3】Less is more — Serverless创新应用赛
之前的文章里面简单的介绍了一下,然后有朋友来找我,说自己确实很感兴趣,但是搞了半天题都读不懂,怎么办?
你问我就对了。
我早几年的时候也是读完题就懵逼了,通篇读完都不知道要考啥,怎么答题,在哪答题。
没关系,这篇文章,我带你上分。
哦不,上道。
那么,这里有三根道,虽然主办方说是一个人可以重复报名,但是我不可能带大家把三根道都上了。
毕竟,第三根道,我也没闹明白是怎么回事。
排除掉第三根道后,剩下的两根赛道,我决定带大家卷一卷第二根。
因为什么呢?
因为第二根人少呀。比第一根赛道的人少了整整 600 多个团队。
好家伙,给人一种报名就直接领先 600 人的错觉。
先说好啊,我不会告诉你解题方法,只是分析赛题,让你把 Demo 跑起来,有点参与感。
跑一跑,提交一下 Demo 就有分,万一跑进了前 100,还白嫖一件大赛纪念T恤,舒服的很。
为什么我不给你分享解题方法呢?
主要是我怕你们和我抢名次,抢奖金。
好了,话不多说,我们操练起来。
先看题
第二根赛道的题目是实现一个柔性集群调度机制,听起来就很牛逼的样子。
但是我根据自己的理解换个接地气的说法:实现一种自适应的负载均衡策略。
负载均衡我就不说了,先说说这个“自适应”。
比如说啊,为了应对零点秒杀场景或者突发事件,应用需要极速的去扩容,可能需要数千甚至数万的机器数堆起来,提升性能以满足用户的需要。
但是在扩容的同时也会带来很多问题呀。
比如其中一个问题就是:服务器的性能或者说对外提供的能力,参差不齐。
节点服务能力不均等,这其实就是在云原生场景下集群大规模部署的一个常见问题。
你别问我为什么知道,因为官网上就是这样的说的,只是我剥离了那些听起来比较高大上的部分。
把背景脱干净了,大概就是我上面说的这个意思。
而如果我们的应用之间是通过 Dubbo 实现 RPC 调用,那么就需要 Dubbo 提供一种调度机制来解决上面的这个问题。
然后我把官网上的描述拿过来:
Dubbo 期待基于一种柔性的集群调度机制来解决这些问题。
这种机制主要解决的问题有两个方面:
一是在节点异常的情况下,分布式服务能够保持稳定,不出现雪崩等问题 二是对于大规模的应用,能够以最佳态运行,提供较高的吞吐量和性能。
从单一服务视角看,Dubbo 期望的目标是对外提供一种压不垮的服务,即在请求数特别高的情况下,可以通过选择性地拒绝一些请求来保证总体业务的正确性、时效性。
从分布式视角看,要尽可能降低因为复杂的拓扑、不同节点性能不一导致总体性能的下降,柔性调度机制能够以最优的方式动态分配流量,使异构系统能够根据运行时的准确服务容量合理分配请求,从而达到性能最优。
如果你对 Dubbo 不熟悉,其实也不太妨碍你参赛,不需要你深入很多细节。
其实调用流程图很简单的:
看起来上面这么多线拉来拉去的,其实这次比赛主要考的就是 4.invoke
这一步。
拉代码
先不管你有没有读懂题吧,我们先把代码拉下来说,更加直观。
拉代码的方法赛事链接里面也有,可以说是手摸手教学:
我直接拉 reactive-cluster
的代码是拉不下来的:
那怎么办呢?
在 reactive-cluster
上有个超链接,你可以点击。
然后叫你创建一个账户,用来托管代码。
接着叫你配置 SSH 密钥,配置完成之后,你可以 fork 一下这个项目。
然后你会看到这个页面:
你别信它的这个自动刷新,等一小会自己主动刷新一下就行。
这个时候你就可以通过自己的仓库链接把代码拉下来了。
其实官方给的 Demo 是可以直接提交的,Demo 跑出来的分就是基准分数。
所以,你甚至都不用先把代码拉到本地,直接先提交一波,跑出个成绩来再说。
怎么提交成绩呢?
就是把自己 fork 下来的仓库的 git 地址粘贴在这里:
注意,这里的地址要是 git 开头的,而不是 https 开头的。
也就是这里的地址:
提交之后,等几分钟,成绩就出来了。
我上周五晚上连续用 Demo 提交了四次。
为什么我要反复提交呢?
因为我看了代码,知道 Demo 跑出来的分是有抖动的,运气好,可能会跑出来一个毕竟靠前的分。
比如我这四次的提交对应的分数分别是:
最高分 58388,在一行代码没有改的情况下,我用 Demo 加运气跑出了第三名的成绩:
那个时候我意识到:周五是开放作品提交入口的第一天,大家都在用 Demo 先走流程。
所以,我只需要对 Demo 进行一个极其小的优化,就可能在各大神发力之前,冲到第一名,截个图装逼够用了。
所以,等你提交了 Demo,拿到了分数,可以多提交几次试一试。但是提交多了就没有意思了,因为 Demo 跑出来的分只是个基线而已。
万一运气好,在比赛刚刚开始的阶段,直接冲进前 10 也不是没有可能。
其实,等你跑出自己的分,排行榜刷新之后看到自己的名字在排行榜上,就很有参与感了。
接下来就涉及到真枪实弹的去改代码了。
改代码
拉下来之后,需要你动手开发的是这几个类:
其他的地方,你一概不需要动,也动不了。
官网上有这样的一个图:
中间的 Consumer,就是 gateway,也就是我上面说的那两个核心类。
上面这个图啥意思呢?
就是你得明白你开发的程序是位于整个测评调用的哪个位置,所以,我根据自己的理解,把上面这个图给你画的再细一点,只体现我们需要开发的部分。
仅针对我们需要开发的类来说,一个请求的流程应该是这样的:
测评程序发起调用,一个响应正常的情况会按照上面的图去依次触发我们需要开发的代码。
在这个流程里面,哪些能动哪些不能动,我相信上面这个图已经描述的很清楚了。毕竟,为了直观的说明白,就这图画出就花了我半小时时间。
首先,我们看看可以看不能改的部分,也就是接口定义和接口实现。
测试接口是怎么样的呢?
测试接口位于 internal-service 项目里面:
这个接口的功能就是计算给定字符串的 hash 值。
同时你需要注意的是方法上的注释中的两个关键信息:响应时间、并发度变化。
接口实现是这样的:
可以看到无论怎样都是有一个睡眠时间的,只是时间长短不同而已。
而这个时间 rtt 就通过一个公式算出来的,这个公式就长这样:
按照官方的说法:接口的响应时间符合负指数分布。
想必说的就是这个公式了,反正我也看不懂,但是我感到十分震惊。
站在多个服务提供者的角度来说,这个公式里面有两个变量:
configs.index concurrentCount
其中这个 configs.index 就是配在配置文件里面的:
配置文件里面有 samll、medium、large 三种类型的配置,每个类型的 index 都不一样。
所以,站在多个服务提供者的角度 index 是一个变量。
那么站在一个服务提供者的角度,index 是一个常量,变化的只有 concurrentCount 这一个玩意了。
而这是个啥玩意?
maxPermit 是 10w,concurrent.availablePermits() 是可用的数,那么 concurrentCount 就是一个从 0 开始增加的数。
最开始的时候为 0,在公式里面作为一个被乘数,那么 rtt 就是 0,这个没有问题吧?
然后这个请求就会被立即返回。
但是,总有时候算出来的 rtt 是大于 5000 的对吧?
那么这个请求就会在 5s 之后才返回。
5s 啊,这得耽误多少事啊!
但是这个地方我们又不能改,你说咋办呢?愁人...
哦,对了,你知道 Dubbo 调用超时之后,服务提供端虽然还在执行,但是客户端已经出现超时异常了,也就是说客户端不会死等服务端返回的。这事你知道吧?
前面都提到 configs.index 了,那么还有一个参数也就一起说了:
这个 failedProportion 是干啥的?
failed 大家都能看明白,Proportion 是啥意思?
就是比例、比重的意思,这是个四级词汇哈,大家记下来,以后要考的。
failedProportion,就是失败率的意思。
那么,比如配置文件中的 large 对应参数是 15,怎么理解呢?
看代码:
com.aliware.tianchi.GameChannelHandler
GameChannelHandler 这个类接受到请求后,有一个 if 判断,用到了前面的失败率。
所以根据代码的写法,large 对应参数是 15,含义就是请求到 large 这台配置的服务器时,该请求有 15% 的几率被直接返回,不会走到接口实现,表示请求失败。
意思就是随机丢弃一部分请求。
那么真实的业务场景是什么呢?
假设你的业务场景要的机器非常非常多,多到单一机房都满足不了了。
或者说你是为了保证服务的高用性,做了异地多活。
这种涉及到多机房异地部署的问题,就一定涉及到网络稳定性、丢包的问题。
什么,你说你可以牵裸光纤来保证网络的问题?
光纤挖断的事情又不是没有出现过。
而且你的格局小了,这可是阿里的比赛把格局放大一点,得把眼界提升到全世界的范围。
比如,要是机房之间跨国家了呢?
所以官方的赛题解析里面是这样说的:
https://developer.aliyun.com/article/788075
涉及到多机房异地部署,对于同城部署,机房之间的网络尚且可以使用租用裸光纤等方式保障网络的稳定,但是一旦多个机房部署在不同的城市、甚至是不同的国家,网络丢包严重等问题会越来越严重。
那么怎么模拟这种情况呢?
我感觉,就是前面我说的 GameChannelHandler 里面的代码,随机丢弃一部分请求,模拟丢包的情况。
官方希望看到的解决方案是一种能快速对调用进行失败返回的机制。
这里可以有两种处理方式:
直接将失败的结果返回给调用端,也就是快速失败。 可以尝试发起重试,至于重试哪台机器,可以再次通过负载均衡选择或者直接进行重试,目的是把服务的可用性拉高。
具体怎么实现,就看各位自己的想法了。
好了,接下来看看我们可以动的代码的这一部分。
也就是我们要把目光转移到 pullbase-cluster 这个项目了。
先看看需要我们搞事情的负载均衡接口:
这个负载均衡策略其实就是一个随机负载均衡策略。
所以我在前面说,你用 Demo 提交每次出来的分数,运气好的话,可以跑的高一点。
道理就在这里,因为这里是随机负载均衡,有可能你刚好随机到正确的机器上。
简单来说,你在这里要写代码,从三个提供者中选一个当前还能快速响应的提供者出来。
至于你怎么分析出哪一个是最优的,就得自己思考了,这玩意我就不能告诉你了。
关于 Dubbo 的负载均衡策略,我在之前的文章里面详细介绍过,前后加起来还是写了好几万字的,有兴趣的可以去翻一下这个合集,熟悉现有的负载均衡策略,相信对你解题能有一点点帮助。
而我自己重新看这篇文章的时候,发现文末居然也是宣传天池大赛...
我这自来水,应该获得铁粉认证才对。
要是主办方看到了,记得给我打钱。不打钱也行,能蹭个周边也是挺好的啊。
好了,我们接着看另外一个需要我们着重开发的地方:
com.aliware.tianchi.UserClusterInvoker
巧了,这个知识点我也刚好写过,在这里也可以派上用场。
至少能让你比较具象的认知到 Cluster 是个什么玩意:
这篇文章回答了关于 Dubbo Cluster 的这几个问题:
而当你了解了 Dubbo 的粘滞连接是个什么玩意之后,你会发现,Demo 中的 select 方法对应的这么大一坨代码,只有被额外框起来的 157 行有用,其他的没有任何卵用:
而你再仔细一看 doSelect 方法的第四个入参 selected 在我们的 UserClusterInvoker 里面是 null。
所以,doSelect 方法下面的这么一大坨是没有用上的。
于是真正有用的方法就是这一行代码:
Invokerinvoker = loadbalance.select(invokers, getUrl(), invocation);
所以,我们就可以把 Demo 改成这样:
是不是这样看起来就直观多了?
而且也算是优化后的代码,毕竟去掉了若干个没有用的分支判断逻辑,虽然这样的优化很可能微乎其微,你可以试着提一次代码,看看分数有没有提高一点点。
我写这个的意思就是,这个地方你得改,大刀阔斧的去改。
比如,刚刚说的 selected 参数为 null,所以我说下面那一段代码是没有用的。
但是如果它不为 null 呢,是不是就得用起来?
总之,在 ClusterInvoker 中将会传入全部 Provider 信息,需要选手基于一定规则选择最佳 Provider 进行调用或者拒绝请求。
当我们收集到足够多的信息,确认现在有的 Provider 已经超负荷了,那么就可以在这里操作起来,保证请求不会发到该 Provider 上。
然后,我们关注一下这一行 Demo 代码:
String testKey = invoke.getAttachment("TestKey");
类似这样的代码也同样出现在下面两个 Filter 中:
这其实就是在 Dubbo 的请求上下文中拿指定信息。
既然是拿东西,那么肯定是有放的地方。
谁在放呢?
TestServerFilter 它在放:
到这里,就顺利引出了最后一个问题了,放的啥玩意?
官方在赛题介绍里面说到了这个项目:
https://github.com/oshi/oshi
这个项目我之前没有了解过,所以也不过多评论了。
但是我知道它可以收集到很多环境相关的数据,而这些数据,可以通过 Result appResponse 中的 attachment 传递给调用方。
比如这样,我就可以把内存使用率传递给调用方:
SystemInfo systemInfo = new SystemInfo();
GlobalMemory memory = systemInfo.getHardware().getMemory();
long totalByte = memory.getTotal();
long acaliableByte = memory.getAvailable();
appResponse.setAttachment("totalMemory", formatByte(totalByte));
appResponse.setAttachment("MemoryUsage", (new DecimalFormat("#.##%")).format((double)(totalByte - acaliableByte) * 1.0D / (double)totalByte));
内存率、CPU使用率各种杂七杂八的消息都可以收集起来,然后你就可以分析了。
举个简单的例子,不一定对哈,因为我也没验证过。比如你收集到三个 provider 的 CPU 使用率了,有的高有的低,那么下一个请求应该发给哪一个呢,这真是一个让人需要深思熟虑、想起来就头疼的问题。
你明白我意思吧?
程序跑起来
最后,再手摸手的教你把程序在本地跑起来。
官网上其实已经把本地开发的流程说的很清楚了。
首先你需要一个安排一个 Nacos 启动起来。
如果你之前对于 Nacos 不了解,没关系,它完全不影响你对赛题的理解,只是个注册中心而已。
可以看看 Nacos 官网的快速启动,在本地跑一个起来就行:
https://nacos.io/zh-cn/docs/quick-start.html
然后看我框起来的地方,叫你启动 Provider。
但是你看程序里面就知道至少需要启动三个 provider,每个 provider 的启动参数都不一样:
com.aliware.tianchi.policy.BaseConfig#loadConf
比如启用 large 配置时,需要在启动的时候指定:
-Dquota=large
同时要勾选右上角我打箭头的地方,这样才能同时启动多个服务提供者。
不必担心端口占用的问题,每套配置的启动时的端口都是不一样的,这一点在程序里面也有体现。
provider 启动成功之后,再启动 Consumer,发起下面的调用:
如果页面显示 OK,Consumer 侧有日志输出,说明启动成功,可以开始自己玩去吧。
哦,对了。当你在 pullbased-cluster 项目里面修改好了代码之后,怎么本地发布到 internal-service 服务去调试呢?
很简单,点击 install 就行,比如下面这样,我加一个 -1 的操作:
这个时候再看 internal-service 的该类,哦了:
你要是之前对 Dubbo 不了解,可以趁着这个机会学一学,因为你要重点开发的地方,也是 Dubbo 的核心逻辑之一。
哪怕你想不到下手的角度,你去看看 Dubbo 现有的负载均衡策略、集群策略,顺便了解一下 Dubbo 的 Filter 机制,以及 SPI 原理。比赛拿不到名次,但是把这些东西搞懂了,这也不爽的抠脚吗?
你要之前对 Nacos 不了解,也可以趁机学一学,一举多得,美滋滋:
哦,对了,你也不能写几行代码就想着提到 git 上去跑跑分吧?
毕竟一天只有 10 次跑分的机会,你得珍惜。
所以,怎么在本地跑分,确保自己做的不是反向优化呢?
这问题,官方肯定也想到了:
关于 wrk 压测工具的安装,我就不说了吧,网上一搜一大把。
跑出来的结果大概长这样,如果你不明白这个压测命令的意思,看不懂跑出来的结果。
太好了,你又可以学一波 wrk 压测工具的使用,再次美滋滋。
多好的契机啊,而且 wrk 这工具,30 分钟从 0 到入门会使用,完全没问题,中间还附带送你 10 分钟的摸鱼时间,轻松学会,我觉得学习成本不高。
最好是在 linux 上跑,但是如果你是 windows 也可以装的,我就是在 windows 装了一个 Ubuntu 本地跑:
毕竟,我感觉我那 2C4G 的小服务器还没有我本机性能好。
最后,再说一嘴赛题限制:
虽然限制二我也没看懂啥意思,但是当你想到一个下手角度的时候,看看是不是和限制冲突了。
别兴高采烈的做出来后,成绩被取消了,瓜兮兮。
报名报名
好了,前面说了这么对,如果你有一丝丝心动,那就报名吧。
再申明一下,我真的是自来水,因为我历年从这个比赛中学到了很多东西,所以我也想把这个东西让更多的人知道,我真没收钱。
你可以通过下面的链接或者二维码直接进入到报名环节:
https://tianchi.aliyun.com/s/bc4139996efe10afb844b64ba08cad48
最后到这个页面就算是报名成功了:
多说一句的是记得做实名认证,主办方的逻辑是报名阶段可以不实名认证,但是参赛阶段需要进行一个实名认证。
这无可厚非嘛,校验参赛人员真实性的同时还可以进行一波拉新活动。
好了,就这样的吧。
最后,朋友,我真心建议你去看一下,选个赛道,报个名,玩一玩,跑不出来好成绩没有关系,重要的是最后真的能学到很多的东西,特别是看排名前面的选手的答辩、代码、解析。这些东西,在绝大部分工作中是很难学到的。
这是你逃离 crud boy 的一次契机。
荒腔走板
这期的荒腔走板说个和赛事相关的吧。
这是我写文章的时候截的图:
参赛多年,这是第一次站在排行榜第一位的位置,诚惶诚恐啊。
我前面说了:上周五是开放作品提交入口的第一天,大家都在用 Demo 先走流程。
所以,我只需要对 Demo 进行一个极其小的优化,就可能在各大神发力之前,冲到第一名,截个图装逼够用了。
所以,接下来我要开始装逼了。
你看我的作品提交记录:
58388 这个分是我用 Demo 跑出来的,当时排行榜第三。
于是我紧接着再提交了一次,看看运气还有没有,结果 58202,我知道应该很难靠运气去跑到第一了。
接着,我本来是再写文章的,但是我还是决定试一试,先做个小优化,趁着参赛早这个优势,先趁乱拿下榜单第一。
因为这次要改造的两个重点地方,恰好我之前写文章的时候也重点分析过。所以我上手起来会比较快一点。
所以花了差不多 1 小时的时间写了一个版本,在 23 点 07 的时候提交了测评。
最后跑出来的分我都傻眼了,我只看到开头的数字是 2,以为跑出来比 Demo 的分还低。
当时就愣住了:我这一波居然是反向优化?不应该啊?
但是我觉得还是把代码给回退回去,结果回退的时候有个地方还弄错了,跑出了个 975 分:
我想了很久,也没想明白为什么我优化后的代码跑出来的分比 Demo 还低。
但是我就先放着不管,继续写文章去了。
等到 0 点,排行榜刷新之后,我又看了一眼排行榜,想看看有没有大神开始发力了。
结果...
我发现我是第一名了,当时忘记截图。我才发现,我的分数比大家多一位。
害,原来我领先了别的选手 20 多万分。
我一看思路对了,马上又提交了几版做测试:
你看这个分数变化,从 Demo 的 5.7w 到优化后的 28.2w,再到 56w,再到 121.4w,再到 135w。
基本上每次分数都是在翻倍,而且每次提交的时间间隔都很短。
说明我在干什么?
是的,我在调参数:
其实这个优化点还是比较容易想到的,只是想到之后,最合理的参数配置是什么,得自己去揣摩。我就懒得揣摩了,自己提交,让成绩说话就完事。
最后,再加上一些其他的杂七杂八的小优化,分数定格在了 157.5w,排在榜一,领先其他队伍 150w 分:
但是,我清楚的知道,这个分数很快就会被超越过去。肯定有大神能跑出一个让人瞠目结舌的分数。
没关系,我的目的已经达到了。
毕竟,我目前就只是想跑到榜一,截个图拿来做封面装逼而已。
最后,如果你也有兴趣玩一玩,可以加我个人微信,备注【比赛】,相互讨论。如果人多的话,我会考虑拉一个仅限参赛者加入的微信小群。
当然了,你不参赛也可以加,做个朋友圈的点赞之交,也挺好。
最后说一句(求关注)
好了,看到了这里了,转发、在看、点赞随便安排一个吧,要是你都安排上我也不介意。写文章很累的,需要一点正反馈。
给各位读者朋友们磕一个了: