文档章节

Motan中使用异步RPC接口

Java公众号_Kirito的技术分享
 Java公众号_Kirito的技术分享
发布于 2017/12/27 18:00
字数 1304
阅读 29
收藏 0

行业解决方案、产品招募中!想赚钱就来传!>>>

这周六参加了一个美团点评的技术沙龙,其中一位老师在介绍他们自研的 RPC 框架时提到一点:RPC 请求分为 sync,future,callback,oneway,并且需要遵循一个原则:能够异步的地方就不要使用同步。正好最近在优化一个业务场景:在一次页面展示中,需要调用 5 个 RPC 接口,导致页面响应很慢。正好启发了我。

为什么慢?

大多数开源的 RPC 框架实现远程调用的方式都是同步的,假设 [ 接口1,...,接口5]的每一次调用耗时为 200ms (其中接口2依赖接口1,接口5依赖接口3,接口4),那么总耗时为 1s,这整个是一个串行的过程。

多线程加速

第一个想到的解决方案便是多线程,那么 [1=>2] 编为一组,[[3,4]=>5] 编为一组,两组并发执行,[1=>2] 串行执行耗时400ms,[3,4] 并发执行耗时200ms,[[3,4]=>5]总耗时400ms ,最终 [[1=>2],[[3,4]=>5]] 总耗时400ms(理论耗时)。相比较于原来的1s,的确快了不少,但实际编写接口花了不少功夫,创建线程池,管理资源,分析依赖关系...总之代码不是很优雅。

RPC 中,多线程着重考虑的点是在客户端优化代码,这给客户端带来了一定的复杂性,并且编写并发代码对程序员的要求更高,且不利于调试。

异步调用

如果有一种既能保证速度,又能像同步 RPC 调用那样方便,岂不美哉?于是引出了 RPC 中的异步调用。

在 RPC 异步调用之前,先回顾一下 java.util.concurrent 中的基础知识: CallableFuture

  
    
  
  
  1. public class Main {

  2.    public static void main(String[] args) throws Exception{

  3.        final ExecutorService executorService = Executors.newFixedThreadPool(10);

  4.        long start = System.currentTimeMillis();

  5.        Future<Integer> resultFuture1 = executorService.submit(new Callable<Integer>() {

  6.            @Override

  7.            public Integer call() throws Exception {

  8.                return method1() + method2();

  9.            }

  10.        });

  11.        Future<Integer> resultFuture2 = executorService.submit(new Callable<Integer>() {

  12.            @Override

  13.            public Integer call() throws Exception {

  14.                Future<Integer> resultFuture3 = executorService.submit(new Callable<Integer>() {

  15.                    @Override

  16.                    public Integer call() throws Exception {

  17.                        return method3();

  18.                    }

  19.                });

  20.                Future<Integer> resultFuture4 = executorService.submit(new Callable<Integer>() {

  21.                    @Override

  22.                    public Integer call() throws Exception {

  23.                        return method4();

  24.                    }

  25.                });

  26.                return method5()+resultFuture3.get()+resultFuture4.get();

  27.            }

  28.        });

  29.        int result = resultFuture1.get() + resultFuture2.get();

  30.        System.out.println("result = "+result+", total cost "+(System.currentTimeMillis()-start)+" ms");

  31.          executorService.shutdown();

  32.    }

  33.    static int method1(){

  34.        delay200ms();

  35.        return 1;

  36.    }

  37.    static int method2(){

  38.        delay200ms();

  39.        return 2;

  40.    }

  41.    static int method3(){

  42.        delay200ms();

  43.        return 3;

  44.    }

  45.    static int method4(){

  46.        delay200ms();

  47.        return 4;

  48.    }

  49.    static int method5(){

  50.        delay200ms();

  51.        return 5;

  52.    }

  53.    static void delay200ms(){

  54.        try{

  55.            Thread.sleep(200);

  56.        }catch (Exception e){

  57.            e.printStackTrace();

  58.        }

  59.    }

  60. }

最终控制台打印:

result = 15, total cost 413 ms

五个接口,如果同步调用,便是串行的效果,最终耗时必定在 1s 之上,而异步调用的优势便是,submit任务之后立刻返回,只有在调用 future.get() 方法时才会阻塞,而这期间多个异步方法便可以并发的执行。

RPC 异步调用

我们的项目使用了 Motan 作为 RPC 框架,查看其 changeLog ,0.3.0 (2017-03-09) 该版本已经支持了 async 特性。可以让开发者很方便地实现 RPC 异步调用。

1 为接口增加 @MotanAsync 注解

  
    
  
  
  1. @MotanAsync

  2. public interface DemoApi {

  3.    DemoDto randomDemo(String id);

  4. }

2 添加 Maven 插件

  
    
  
  
  1. <build>

  2.    <plugins>

  3.        <plugin>

  4.            <groupId>org.codehaus.mojo</groupId>

  5.            <artifactId>build-helper-maven-plugin</artifactId>

  6.            <version>1.10</version>

  7.            <executions>

  8.                <execution>

  9.                    <phase>generate-sources</phase>

  10.                    <goals>

  11.                        <goal>add-source</goal>

  12.                    </goals>

  13.                    <configuration>

  14.                        <sources>

  15.                            <source>${project.build.directory}/generated-sources/annotations</source>

  16.                        </sources>

  17.                    </configuration>

  18.                </execution>

  19.            </executions>

  20.        </plugin>

  21.    </plugins>

  22. </build>

安装插件后,可以借助它生成一个和 DemoApi 关联的异步接口 DemoApiAsync 。

  
    
  
  
  1. public interface DemoApiAsync extends DemoApi {

  2.  ResponseFuture randomDemoAsync(String id);

  3. }

3 注入接口即可调用

  
    
  
  
  1. @Service

  2. public class DemoService {

  3.    @MotanReferer

  4.    DemoApi demoApi;

  5.    @MotanReferer

  6.    DemoApiAsync demoApiAsync;//<1>

  7.    public DemoDto randomDemo(String id){

  8.        DemoDto demoDto = demoApi.randomDemo(id);

  9.        return demoDto;

  10.    }

  11.    public DemoDto randomDemoAsync(String id){

  12.        ResponseFuture responseFuture = demoApiAsync.randomDemoAsync(id);//<2>

  13.        DemoDto demoDto = (DemoDto) responseFuture.getValue();

  14.        return demoDto;

  15.    }

  16. }

<1> DemoApiAsync 如何生成的已经介绍过,它和 DemoApi 并没有功能性的区别,仅仅是同步异步调用的差距,而 DemoApiAsync 实现的的复杂性完全由 RPC 框架帮助我们完成,开发者无需编写 Callable 接口。

<2> ResponseFuture 是 RPC 中 Future 的抽象,其本身也是 juc 中 Future 的子类,当 responseFuture.getValue() 调用时会阻塞。

总结

在异步调用中,如果发起一次异步调用后,立刻使用 future.get() ,则大致和同步调用等同。其真正的优势是在submit 和 future.get() 之间可以混杂一些非依赖性的耗时操作,而不是同步等待,从而充分利用时间片。

另外需要注意,如果异步调用涉及到数据的修改,则多个异步操作直接不能保证 happens-before 原则,这属于并发控制的范畴了,谨慎使用。查询操作则大多没有这样的限制。

在能使用并发的地方使用并发,不能使用的地方才选择同步,这需要我们思考更多细节,但可以最大限度的提升系统的性能。


本文分享自微信公众号 - Kirito的技术分享(cnkirito)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

Java公众号_Kirito的技术分享
粉丝 0
博文 186
码字总数 257943
作品 0
杭州
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
用vertx实现高吞吐量的站点计数器

工具:vertx,redis,mongodb,log4j 源代码地址:https://github.com/jianglibo/visitrank 先看架构图: 如果你不熟悉vertx,请先google一下。我这里将vertx当作一个容器,上面所有的圆圈要...

jianglibo
2014/04/03
3.9K
3
浅入浅出Android(003):使用TextView类构造文本控件

基础: TextView是无法供编辑的。 当我们新建一个项目MyTextView时候,默认的布局(/res/layout/activity_main.xml)中已经有了一个TextView: <TextView 运行效果如下: 修改其文本内容...

樂天
2014/03/22
590
1
CDH5: 使用parcels配置lzo

一、Parcel 部署步骤 1 下载: 首先需要下载 Parcel。下载完成后,Parcel 将驻留在 Cloudera Manager 主机的本地目录中。 2 分配: Parcel 下载后,将分配到群集中的所有主机上并解压缩。 3 激...

cloud-coder
2014/07/01
6.8K
1
Go-node

Go-node 是一个用 Go 语言实现的 Erlang/OTP node 已支持的功能: Publish listen port via EPMD Handle incoming connection from other node using Erlang Distribution Protocol Spawn E......

匿名
2013/01/25
1.5K
1

没有更多内容

加载失败,请刷新页面

加载更多

SPSSAU 付费数据研究报告服务

SPSSAU-付费数据分析报告服务(周老师提供) 本文分享自微信公众号 - SPSSAU(spssau)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起...

SPSSAU
2017/11/08
0
0
芋艿-springcloud gateway

http://www.iocoder.cn/Spring-Cloud/Spring-Cloud-Gateway/?github springcloud gateway 官方文档 https://cloud.spring.io/spring-cloud-gateway/reference/html/#gatewayfilter-factories......

Java搬砖工程师
22分钟前
5
0
新零售小程序制作流程

最近有很多小伙伴们都在观望新零售小程序,其实新零售小程序制作还是比较简单的,只要你能熟知以下的新零售小程序制作流程,你也可以制作出属于自己的小程序。下面木鱼小铺(www.muyu007.cn)...

木鱼小铺小程序1
23分钟前
0
0
bat增加自定义参数

#xxx.bat --tag=dev1010 --context=3 --cpu=3 --memory=3 --build=1 --update=1 --api-version=1 @echo off setlocal enabledelayedexpansion set COMMANSLINE="%" :STR_VISTOR for /f "toke......

_snake_
25分钟前
3
0
谷歌SEO推广团队,这样管理更高效!

如今不论是外贸企业还是专业的海外推广公司都会组建自己的Google SEO推广团队,可以更有效的做好网站SEO,但是要发挥谷歌SEO推广团队的最大效能,我们并不能随意的让团队成员听之任之,随波逐...

一尘SEO
27分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部