作者简介
刘诚,携程酒店研发性能架构师。2014年加入携程,致力于通过架构的演进,控制企业硬件成本。
在QCon2019北京大会上第一次得知RSocket。印象深刻的是Netifi公司通过他们研发的RSocket帮助企业实现微服务,在40,000RPS的场景下,Istio需要每月3495美金,而Netifi每月只要388美金,同时性能提升10倍,这无疑对任何企业都是极具吸引力的。
Netiffi的创始人在会上也谈到,使用Netiffi的Broker,得益于RSocket协议,无需独立部署监控、服务发现、健康检查、负载均衡等等中间件。如果是跨云部署,例如谷歌云与亚马逊云之间,或者亚马逊云与企业本地数据中心,都只要通过Netiffi的Broker即可无缝沟通,无需处理复杂的适配问题。
回来查询了不算丰富的资料后发现,Istio的技术专家发文称RSocket Broker的service mesh比Istio有将近10倍的速度提升。考虑到Istio专家的观点还有一定说服力的,那么RSocket真的有那么厉害?
我们决定到生产上面去实践RSocket,看看性能到底如何。现在已经支持RSocket的service框架有Spring Flux:
Dubbo3.0 snapshot
压测对比的是Dubbo2.7。Dubbo2.7的样例代码如下:
Dubbo3的样例代码如下:
SpringFlux的样例代码如下:
分别压测QPS200,400和800的情况,结果显示CPU、内存和响应时间基本一致。是不是压测方式有问题,为什么性能一点也没有提高?
在解答上面的问题之前,我们先来看看RSocket到底是什么?
官方定义:RSocket是基于reactive stream flow control的双向的、多路的、基于消息的、二进制通讯协议。它提供了4种交互模式:
request - response:一个请求,一个响应。现在的Restful服务既是如此;
fire-and-forget:对于那些不关心结果的请求,直接返回;
request – stream:一个请求,多次结果返回;
channel:服务器可以发多个请求给客户端,客户端也可以发多个结果给服务器;
几个特点:
可取消:请求和响应都可取消,能够高效的清理系统资源;
可中断后继续:如果被调用方卡住了,请求方可断开后,过一会再过来重试;
可租赁:响应者可根据自己的实际情况来控制调用方的频率,其实就是响应式编程中的背压的实现;
上面只是定义了RSocket协议,在具体的实现上面是非常灵活的。
进一步查询资料后发现,现有JAVA的RSocket实现一般都是基于TCP长连接。熟悉Dubbo的朋友,立刻就会想到Dubbo不就是基于TCP长连接进行服务调用的么。是的,但是不同之处在于RSocket是一系列的协议规范,原先的Dubbo虽然也是基于TCP长连接实现的,但是并没有完全按照RSocket的规范来进行实现。
更加确切的来说,那个时候应该还没有RSocket。这个也就帮助我们理解为什么Dubbo3开始接入RSocket,以及阿里为什么也是RSocket的拥护者之一。至此就能理解为什么性能没提高了,在我们实践的场景中,只是把原来基于HTTP的请求方式变成了基于TCP的实现。就生产结果而言,并没有性能大幅提升,更别提10倍的提升。
那RSocket只有TCP长连接的优势?
作为一名一线业务开发者,可能更关心的是使用RSocket协议写业务代码时的优劣势。就我个人而言,感觉还是很棒的。例如下面这个传入参数为Mono,返回也为Mono类型的接口定义方式。
熟悉响应式编程的同学应该知道Mono是Pivotal Reactor Core中的一种类型。是一种特殊的发布者,最多只发布一次。
如果应用本身就是以非堵塞的方式写的,那这里就可以直接使用reactor core的所有API。当然也有同学会说,即使不返回Mono,例如Dubbo2.7中的返回CompletableFuture,我们只要自己内部转换一下即可。然后应用里面照样可以使用reactor core或者rxjava等响应式编程的框架。
的确如此,但是如果是Flux呢?可以多次、不断地往流里面写入结果的呢?CompletableFuture还能支持么?显然不行。因为flux本身就定义为最多可以发布n次。
那为什么要用flux?
flux是响应式编程中的一种常用对象,其实就是request -> stream一种实现。一个请求发起后,结果可以分批写回。好处是什么?例如:A服务调用B服务,B服务调用C和D服务,但是D服务很慢,如果是request -> response模式,那必须要等到C和D完成后,才能返回结果给A。如果是Request -> stream模式,则可以先把C的结果返回给A,然后等D的结果拿到了,在返回给A。这样就可以高效的利用系统资源,减少等待。
熟悉Dubbo的同学可能会说,request -> stream这种模式也不是RSocket独有的吧,例如Dubbo就可以使用下面的方式来实现:
看完上面的代码,然后我们可以思考一下如何用上面提供的API去实现下面的功能。你就会发现flux的这种Reactive Functional Programming的编程方式大大降低了编程的难度与代码量,提升了代码的可读性:
但是这还不是全部的好处,下面我们来看看RSocket的另外一种使用场景。
响应式编程中有一个比较有名的功能叫背压。例如:当上游服务调用下游服务,而下游服务来不及处理的时候,可以选择性的限制上游服务的调用。
而我们日常在刷手机的时候,经常会由于手机卡顿,无论是APP导致的还是网络导致的,重复点击或者刷新页面的情况。而HTTP本身是无状态的,所以只要有请求,无论是有效的还是无效的,服务器都会进行处理直到完成。
但是如果有背压,那我们就可以一定程度上减少APP的无效和重复的请求。
例如:用户查看订单列表,如果一下子过来10个请求,其实只要返回最后一个即可,前面9个都可以忽略。如果实现了,服务器就会减少流量,对硬件成本的控制有着非常积极的作用。InfoQ的文章中就提到:Facebook的工程师现在就是这样实现APP与服务器之间的通讯。
之所以称之为展望,因为这个也是我们下一个实践的目标。
https://qconsp.com/system/files/presentation-slides/rsocket_and_spring_cloud_gateway-spencer.gibb_.pdf
https://projectreactor.io/docs/core/release/reference/
https://www.infoq.com/news/2018/10/rsocket-facebook/
8月24日上海
火热报名中
↓↓↓
万字长文全面解析GraphQL,携程微服务背景下的前后端数据交互方案
单个场景秒级返回,携程机票持续集成之线上场景回放优化
跨多业务线挑战下,携程订单索引服务的1.0到2.0
携程机票 React Native 整洁架构实践
React Hook的实现原理和最佳实践