Skip to content

Latest commit

 

History

History
128 lines (97 loc) · 9.39 KB

README.MD

File metadata and controls

128 lines (97 loc) · 9.39 KB

本项目不是生产环境可用!

介绍

本项目是一个点对点rpc的demo,尽可能的进行性能优化,然后和rocketmq remoting进行性能对比。希望通过这个简单模型的分析,得出rocketmq remoting的性能改进方向。

本项目相对于rocketmq remoting有3点改进。

  1. 尽可能减少对象创建、数组复制、线程切换。可以看到,在批量关闭的情况下,大部分场景对比rocketmq remoting都有少量提升。
  2. 网络IO批量写,包括客户端和服务端都有批量。服务端目前有AUTO、ENABLE、DISABLE 3种模式。异步场景下TPS提升了十多倍。
  3. 优化了异步背压能力。关于异步背压见下面专门的段落说明。

rocketmq批量操作可以在3个层面进行:业务用户层面、消息处理层面、网络层面。这3种方式各有优劣。让用户来处理(业务用户层面)效果是最好的,但是使用复杂度就上升了,有些业务场景下甚至办不到。

本项目在网络IO层面进行自动批量处理,具体的做法类似TCP里面的Nagle算法,数据包来了以后,先不要写IO,等1ms,看看有没有后续的包可以一起批量写出去。这样做有时候提升了吞吐,但缺点也和Nagle类似:在某些场景下会大幅降低性能。 对于这个项目,具体是单线程(或少量线程)同步发送的场景。试想,如果客户端是单线程同步调用的,要拿到结果才会进行下一次调用,在服务器端白白多等了1ms(但是又等不到任何后续的数据),所以理论上tps就不会超过1000了。 于是这个批量的功能是不能默认打开的,让用户来设置是否打开这个功能也比较难,大部分用户很难说清楚是否应该打开,为了保险起见就只能关闭它,那就没有意义了。

面对这样的问题,本项目的改进方案是提供一个AUTO模式,程序按TCP连接统计近期的数据,根据一个算法来推测这个连接打开批量是否划算,在运行过程中会不断调整。 最终的效果可以看下面的测试结果表格,可以看到,AUTO模式的吞吐量在单线程同步发送时,和DISABLE模式接近,而在异步发送时,和ENABLE模式接近。

测试方式和结果

运行NettyClientBenchmark和RmqBenchmark的main方法可以得到测试结果。测试在Mac上运行,测试时间均为10秒。 Simple RPC客户端自动批量阈值为200(所以同步发送的时候,线程数是否大于200,程序的逻辑会有差异)。

客户端128线程

rpc框架 调用方式 参数 服务端批量模式 结果(TPS)
RMQ Remoting 同步调用 默认 101,374
RMQ Remoting 异步调用 默认 101,997
Simple RPC 同步调用 默认 自动 118,032
Simple RPC 同步调用 默认 开启 121,644
Simple RPC 同步调用 默认 关闭 103,662
Simple RPC 异步调用 默认 自动 1,132,824
Simple RPC 异步调用 默认 开启 1,176,219
Simple RPC 异步调用 默认 关闭 107,453
Simple RPC 异步调用 使用IO线程处理请求 开启 1,343,342

客户端256线程

rpc框架 调用方式 参数 服务端批量模式 结果(TPS)
RMQ Remoting 同步调用 默认 108,762
RMQ Remoting 异步调用 默认 96,250
Simple RPC 同步调用 默认 自动 241,368
Simple RPC 同步调用 默认 开启 242,345
Simple RPC 同步调用 默认 关闭 97,612
Simple RPC 异步调用 默认 自动 1,188,924
Simple RPC 异步调用 默认 开启 1,171,117
Simple RPC 异步调用 默认 关闭 103,415

客户端32线程

rpc框架 调用方式 参数 服务端批量模式 结果(TPS)
RMQ Remoting 同步调用 默认 84,892
RMQ Remoting 异步调用 默认 104,381
Simple RPC 同步调用 默认 自动 92,168
Simple RPC 同步调用 默认 开启 22,147
Simple RPC 同步调用 默认 关闭 94,399
Simple RPC 异步调用 默认 自动 1,155,895
Simple RPC 异步调用 默认 开启 1,179,657
Simple RPC 异步调用 默认 关闭 108,141

客户端1线程

rpc框架 调用方式 参数 服务端批量模式 结果(TPS)
RMQ Remoting 同步调用 默认 11,192
RMQ Remoting 异步调用 默认 114,825
Simple RPC 同步调用 默认 自动 13,240
Simple RPC 同步调用 默认 开启 668
Simple RPC 同步调用 默认 关闭 13,520
Simple RPC 异步调用 默认 自动 1,246,949
Simple RPC 异步调用 默认 开启 1,320,533
Simple RPC 异步调用 默认 关闭 111,999
Simple RPC 异步调用 使用IO线程处理请求 开启 1,528,428

以上测试为了方便,是在Mac本机运行的。 也在Linux上进行了测试(使用ServerStarter和ClientStarter),客户端和服务端分别位于两台不同的服务器,不调整任何参数的情况下tps是80万,调整一下参数可以上百万。

关于异步背压

当rpc客户端(消息生产方)的异步调用速率大于响应速率(同时受服务器处理速率和网络速率的影响)的时候,客户端降低调用速率,使得tps自动适应最大响应速率,而不至于失败,这就是异步背压。

一个典型的例子就是TCP的流量控制,它会协商窗口的大小,控制客户端的发送速率,不至于淹没服务端,对上面的应用而言,TCP协议提供了可靠的服务。 但TCP的客户端和服务端是一对一的,消息服务器的客户端和服务器端是多对多的关系,这使得窗口协商非常困难。

我们无法通过简单的方式提供一个绝对可靠的异步背压,但是可以通过一些手段和配置,使得大部分情况下大tps异步调用不至于失败。 可以用本项目做一个实验,在服务端处理RPC请求的时候sleep1毫秒,在这种情况下,rocketmq remoting异步调用会大量失败,而simple rpc则可以全部成功。

Simple RPC具有基本异步背压能力,核心要点是:

  1. 通过semaphore来控制pending(已经发出但是没有收到响应)的请求数量,如果无法从semaphore获取到permit,即使是异步调用也应该堵塞,等待permit的释放。
  2. 客户端buffer(semaphore)要小于服务端buffer。
  3. 客户端异步发送超时时间要足够长(要大于服务端处理客户端所有pending数据的时间)。

要点2举例:如果客户端允许发送出去1000个请求,客户端在1ms内发出1000个请求,从1001个请求开始,由于无法获取到semaphore而堵塞等待; 而服务端的处理请求的Executor有10个线程(假设处理需要5ms),队列100,最多hold住110个请求,那么客户端发送过来的另外890个请求都无法进入Executor而快速失败, 快速失败的响应到达客户端以后会释放890个semaphore的permit,堵塞的异步发送程序(第1001个请求)开始得到permit继续发送,然后继续失败。最后的结果是,大部分的请求都是失败的。

要点3举例:如果客户端semaphore为1000,服务端处理能力为500tps,那么超时时间应该至少大于2秒。因为异步操作是几乎不需要时间的,这1000个请求马上会送出去,然后异步发送的程序由于获取不到permit陷入堵塞。 按服务器的处理能力,最后一个发送的请求要在2秒以后才能返回,所以超时时间至少要有2秒。

具体到rocketmq,一个异步发送的程序可能因为以下原因失败:

  1. DefaultMQProduerImpl.send方法提交异步发送任务进线程池,会被拒绝(默认队列50000)。
  2. 获取semaphoreAsync超时(默认65535个),此处获取semaphore有等待,用的是tryAcquire(long timeout, TimeUnit unit)
  3. 服务端待处理消息队列满(默认队列10000)
  4. 客户端超时(默认3000ms)
  5. 服务端超时(受brokerFastFailureEnable、osPageCacheBusyTimeOutMills控制,默认1000ms)
  6. 其它跨线程处理时,由于线程池处理队列已满,而被拒绝。比如netty不同的event loop之间(视具体配置,我没有细看)

有以下问题:

  1. 上面原因1的Executor不利于背压堵塞;
  2. 上面原因1的50000小于2的65535,结果是semaphore还有permit,但是1的Executor已经放不进去了;
  3. 客户端buffer明显大于服务端;
  4. 服务端超时1000ms小于客户端3000ms,客户端还愿意等,但服务端就清理掉了,不过这个osPageCacheBusy一般不容易触发,所以不常见;

TODO

  1. simple rpc需要更好的AUTO算法,更好的适应各种工况
  2. 当前的AutoBatchWriteHandler不能很好的向write操作的future的listener发出通知。
  3. simple rpc服务端和客户端最好具备简单的窗口机制。
  4. rocketmq测试的过程中多次出现"createChannel: connect remote host[127.0.0.1:8888] success, AbstractBootstrap$PendingRegistrationPromise@4f40e8cb(success)",说明rocketmq在这里有一点并发问题
  5. 自动批量对于同步调用提升不那么大(除非线程很多),而rocketmq异步调用在大tps场景异步调用容易出错,需要做适当的改造以具备基本背压能力。