在比赛即将开始之前,就有很多选手跑来问我:“中间件挑战赛怎么还不开始啊?“,就在本周,我可以很高兴告诉大家,第五届中间件性能挑战赛开始啦!

第五届中间件性能挑战赛初赛题意剖析_java

背景介绍

中间件性能挑战赛是由阿里云发起,中间件、天池平台联合举办的工程视角品牌赛事,初衷是为热爱技术的年轻人提供一个挑战世界级技术问题的舞台。

自 2015 年开始,中间件性能挑战赛已经成功举办了四届,被历年大赛选手称为“中间件技术的风向标”,也是如今技术社区中为数不多的工程类程序设计竞赛。

如果你是一个新手,想要学习中间件技术,那你可以有机会体验到中间件产品开发的乐趣,接触到诸如网络通信,磁盘存储等技术架构,并与大牛们同台竞技,赛后更是可以学习到排名靠前选手的开源代码;如果你是一个中间件技术爱好者,那你更是有机会挑战 ¥200000 的总奖金池;无论你是什么身份,在这场赛事中,借助于评测系统,你的程序都可以得到一个直观的打分,你所要做的就是尽可能地提高它。你只要体验过一次,就不会忘记这个过程,你可能会在凌晨 3 点调试你的代码,并且不亦乐乎,也会为自己的一些优化取得更好的成绩而感到欣喜若狂。

参与到其中,你所要做的只有:理解赛题,设计架构,coding,优化,优化,优化!

赛制介绍

本次比赛依旧和往常的赛制保持一致,分成初赛和复赛两个赛季,彼此间赛题独立。其中

初赛:预热赛(6月10日- 6月17日)正式赛(6月17日-7月15日

复赛:预热赛(7月16日-7月23日) 正式赛(7月24日-8月20日)

截止你看到这篇文章,初赛赛题已经公布,选手可以本地开发并进行本地评测,预热赛开始后将会正式开放官方评测入口,届时可以在排行榜中看到自己的排名。

选手可以通过淘宝或阿里云账号登入官网,完成个人信息注册,即可报名参赛。官方入口:https://tianchi.aliyun.com/markets/tianchi/aliware2019。你可以选择单人作战,也可以叫上你的最多两位小伙伴一起组队。中间件性能挑战赛面向全社会开放,无论你是高等院校的学生,还是科研单位的研究员,还是互联网企业的开发者都可以报名参赛(阿里内部员工参赛会有单独的榜单排名)。

冠军:1支队伍,每支队伍奖金拾万,颁发获奖证书 亚军:1支队伍,每支队伍奖金伍万,颁发获奖证书 季军:1支队伍,每支队伍奖金叁万,颁发获奖证书 优胜奖:2支队伍,每支队伍奖金壹万,颁发获奖证书

参与奖:初赛最终排名入围 TOP200 所在队伍的选手将获得大赛限量版纪念T恤一件

极客奖:复赛最终排名入围 TOP20 所在队伍的选手将获得阿里中间件招聘优先推荐名额。

为了 T 恤!

初赛赛题介绍

由于复赛的赛题和评测仍未完全给出,所以本文主要针对初赛的赛题进行介绍和剖析。

自适应负载均衡的设计实现

负载均衡是大规模计算机系统中的一个基础问题。灵活的负载均衡算法可以将请求合理地分配到负载较少的服务器上。理想状态下,一个负载均衡算法应该能够最小化服务响应时间(RTT),使系统吞吐量最高,保持高性能服务能力。自适应负载均衡是指无论处在空闲、稳定还是繁忙状态,负载均衡算法都会自动评估系统的服务能力,更好的进行流量分配,使整个系统始终保持较好的性能,不产生饥饿或者过载、宕机。

赛题内容

Provider 是服务提供者,Gateway( Consumer ) 是服务消费者,Gateway 消费 Provider 提供的服务。Gateway 及 Provider 服务的实现 由赛会官方提供。 为简化流程,本次比赛不使用服务注册和发现机制,Gateway 通过 docker 的 dns 直连调用 Provider 服务。

Provider 服务接口:

  1. public interface HashInterface {


  2.  /**

  3.   * 计算给定字符串的 hash 值

  4.   * <li>

  5.   *     <ol>接口的响应时间符合负指数分布 </ol>

  6.   *     <ol>接口的并发度(允许同时调用的线程数)会随时间增加或减小,从而模拟生产环境可能的排队</ol>

  7.   * </li>

  8.   * @param input 要计算的字符串

  9.   * @return 字符串的 hash 值

  10.   */

  11.  int hash(String input);

  12. }

Consumer 在接收到客户端请求以后,会生成一个随机字符串,然后根据负载均衡算法选择一个 Provider 。 由 Provider 计算哈希值后返回,客户端会校验该哈希值与其生成的数据是否相同,如果相同则返回正常(200),否则返回异常(500)。

第五届中间件性能挑战赛初赛题意剖析_java_02

1、PTS 作为压测请求客户端向 Gateway(Consumer) 发起 HTTP 请求,Gateway(Consumer) 加载用户实现的负载均衡算法选择一个 Provider,Provider 处理请求,返回结果。 2、每个 Provider 的服务能力(处理请求的速率)都会动态变化:

  • 三个 Provider 的总处理能力会分别在小于/约等于/大于请求量三个状态变动;

  • 三个 Provider 任意一个的处理能力都小于总请求量。

3、评测分为预热和正式评测两部分,预热部分不计算成绩,正式评测部分计算成绩。 4、正式评测阶段,PTS 以固定连接数(1024) 向 Gateway 发送请求,1分钟后停止; 5、以 PTS 统计的成功请求数和最大 TPS 作为排名依据。成功请求数越大,排名越靠前。成功数相同的情况下,按照最大 TPS 排名。

选手扩展点

初赛赛题 fork 了一份最新版本 Dubbo 的代码,并为其新增了一些扩展接口,选手需要基于题目提供的扩展接口LoadBalance,实现一套自适应负载均衡机制。

  1. public interface LoadBalance {


  2.    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;


  3. }

要求能够具备以下能力: 1、Gateway(Consumer) 端能够自动根据服务处理能力变化动态最优化分配请求保证较低响应时间,较高吞吐量; 2、Provider 端能自动进行服务容量评估,当请求数量超过服务能力时,允许拒绝部分请求,以保证服务不过载; 3、当请求速率高于所有的 Provider 服务能力之和时,允许 Gateway( Consumer ) 拒绝服务新到请求。

详细的赛题描述:https://code.aliyun.com/middlewarerace2019/adaptive-loadbalance

赛题剖析

本次比赛的核心是 Dubbo 中 LoadBalance 接口的扩展,熟悉 Dubbo 的选手可能占据一定的优势,因为可以对整体 Dubbo 的工作流程有一个具象化的感知,不了解 Dubbo 的选手也可以将 LoadBalance 当做一个黑盒,即从多个 Invoker 中选择一个的过程。在 Dubbo 中,主要有两类角色:Consumer 与 Provider, 在此次赛题中 Gateway 对应 Consumer,有三种规格的 Provider :small、medium、large,代表了不同的机器配置规格。

LoadBalance 接口中,有三个入参 List<Invoker<T>>invokers, URL url, Invocationinvocation

  • invokers: Provider 的抽象,在运行时 invokers.size() == 3, LoadBalance 接口需要返回其中一个实例,这个过程就是负载均衡的过程。

  • url: 统一配置的抽象,具体在这里表现为对调用服务的抽象,可以获取调用服务的必要信息,选手不需要关心。

  • invocation:一次调用的抽象,可以从中获取到请求的方法名,请求参数等信息,选手不需要关心。

如赛题示例所示:

  1. public class UserLoadBalance implements LoadBalance {


  2.    @Override

  3.    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {

  4.        return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));

  5.    }

  6. }

该接口作用在 Consumer 端,返回 invokers 中的随机一个对象,便实现了最简单的负载均衡算法:随机负载均衡。另外常见的负载均衡算法还包括:

  • 轮询负载均衡

  • 加权随机/加权轮询负载均衡

  • 最小响应时间负载均衡

随机负载均衡一定不是最佳的,从后端机器的基本机器参数就可以发现每台机器的处理能力是有差异的。那使用加权类型的负载均衡算法是否可行呢?理论上应该比随机算法要好,但注意到题面中还提到一点:服务端的处理能力是变化的。除了固定的基本机器参数不同,三个服务提供方的处理能力还会变化,具体的逻辑在 internal-service/service-provider 的 HashServiceImpl 中已经提供给了大家,这样的代码模拟了一个由 cpu 繁忙、gc 耗时、io 等待、用户并发量变化等原因导致服务端处理能力变化的真实场景,提供给选手的变化逻辑仅供参考,在最终的评测逻辑中,不会一成不变,所以选手没必要 hack 这个变化过程。

Provider 有三个差异因素,其中包括:

一个固定差异:三台机器的机器参数配置不同

两个可变差异:三台机器上的服务端处理时间和可支持并发量会阶段性变化,且变化未知。

回到权重负载均衡,也不是不可行,至少这个权重不会是固定的机器配置相应的权重,而应该是一个变化的权重,至于如何计算这个权重,则交由选手去定夺了。

服务端处理能力的变化更加符合现实的业务场景,也是这次赛题出现的诱因,常规的负载均衡算法往往没有考虑到服务端处理能力的变化,而是在客户端一成不变看待服务端,相比之下自适应的负载均衡算法具有一定的反馈机制,将会是这次比赛制胜的关键。

或许也可以参考:最小响应时间负载均衡的实现思路,因为它不同于前几者,其加入了对端 rt 这一考虑因素,但似乎也不是最佳的设计方案,仅仅依赖于局部的最小 rt 做决策,往往会没法达到集群的最佳策略,不过选手可以在此基础进行优化。

辅助接口剖析

这次比赛的关键是如何设计自适应的负载均衡算法,服务端处理能力会发生变化,如何让客户端感知这些变化是 Dubbo 必须提供给选手的,为此,赛题提供了一些辅助接口的说明。

客户端回调

客户端接口:

public interface CallbackListener {    void receiveServerMsg(String msg);}

服务端接口:

public interface CallbackService {    void addListener(String key, CallbackListener listener);}

使用场景:

前面提到服务端的处理能力会发现变化,其中一部分参数可以由 Provider 自身进行评估,并反向通信发送给客户端,来影响客户端的负载均衡决策。打个比方,这个过程就像是服务端感觉自己快不行了,于是及时告诉客户端:我不行了,你别来调我了。

使用示例可以参考官方给出的 Demo : CallbackListenerImpl 和 CallbackServiceImpl,注意 CallbackListenerCallbackService 是配套使用的,它们注册进 Dubbo 工作流中是由框架本身以及评测程序保障的,选手只需要实现接口即可。

如果客户端回调,选手需要考虑传递什么参数给客户端,如何得到这些参数,客户端拿到参数如何改变自己的负载均衡策略等问题。

服务端限流

public interface RequestLimiter {    boolean tryAcquire(Request request, int activeTaskCount);}

限流器位于服务端,暴露给选手,旨在当服务端过载时用于保护服务端,当返回 false 时,服务端会拒绝请求,这个生命周期发生在提交给服务端业务线程池之前,所以拒绝的请求不会占用服务端的资源。

其拥有两个入参,其中 reuqest 包含了此次请求的必要信息,例如客户端 ip、请求方法等,activeTaskCount 则是代表此时服务端活跃的线程数。

返回值为 false 时,不提交给服务端业务线程池直接返回,客户端可以在 Filter 中捕获 RpcException,也可以不处理,评测程序不会将异常响应计入得分,返回值为 true 则代表不限流。

客户端/服务端过滤器

  1. public interface Filter {


  2.    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;


  3.    default Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {

  4.        return result;

  5.    }


  6. }

官方示例:

客户端过滤器 TestClientFilter

服务端过滤器 TestServerFilter

由于此次比赛,选手不能直接修改消费者和提供者的代码,所以提供了过滤器的扩展接口,选手可以在过滤器中加入自己逻辑,去操控调用流程。

接口说明

  • 辅助接口均以 SPI 的形式加载,不强制选手使用,只是为了方便一部分负载均衡算法的实现

  • Dubbo 拥有众多的扩展点,选手可以在不影响赛题题意的前提下适当使用,详情参考 Dubbo 官方文档:用户文档 -> 开发者指南 -> SPI 扩展实现

赛题展望

本次比赛是一个简化的负载均衡模型,具有单一的客户端,服务端集群规模也较小,使用程序模拟来达到处理能力的变化。在工程界也有一些较为前瞻性的负载均衡算法,选手可以参考现有 RPC 框架的负载均衡的思想,在比赛特定背景下设计出最合适的负载均衡模型。到正式比赛前谁也无法论定最优的实践是什么,这就是工程类比赛的魅力,一切用跑分说话,Benchmark Everything。