【RPC 专栏】Motan 中使用异步 RPC 接口

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 【RPC 专栏】Motan 中使用异步 RPC 接口

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

  • [**中文详细注释的开源项目**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484404&idx=1&sn=109f263e51b81ca9f270846dd16f6b3a&chksm=fa497c45cd3ef55358b09beb6e18ba04737799d3c0bc32baaa0796dc707b1275c0c555a249ba&scene=21#wechat_redirect)

  • **[Java 并发源码合集](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484341&idx=1&sn=91d6fc7e8841a0f6046e1c2f4693a537&chksm=fa497c04cd3ef512f9249a5deb305a28b68d3ba44467f13fa8c6068711540b2f3e0a6f622ae3&scene=21#wechat_redirect)**
  • [**RocketMQ 源码合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484334&idx=1&sn=761e2659f474f06e7db935eae26e2b03&chksm=fa497c1fcd3ef509a02890b8e9f6bddb02e714f9c7e70cfbc37cd5bd75be64855225497fd3de&scene=21#wechat_redirect)
  • [**Sharding-JDBC 源码解析合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484360&idx=1&sn=0dae84944d2c388fdc1bbed868ac5b99&chksm=fa497c79cd3ef56f8487dda6d53e3772e0aa9812ee66376993c3445bc94920c01a03dd4a4b8f&scene=21#wechat_redirect)
  • [**Spring MVC 和 Security 源码合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484380&idx=1&sn=b4e0da1a314d77dcd170a25ed1ebb4c5&chksm=fa497c6dcd3ef57bcfb69a52c594bcb72e35d9bbe89fa87601b2a6c9f266d656b1ad2a5d4da4&scene=21#wechat_redirect)

  • [**MyCAT 源码解析合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484377&idx=3&sn=1323ac1a4099fac49c96686e58d1960d&chksm=fa497c68cd3ef57e5c3b683f9ead89f06ea5d01947672bfff8341cff2ab0c39c03274723c49a&scene=21#wechat_redirect)
  • 为什么慢?
  • 多线程加速
  • 异步调用
  • RPC 异步调用
  • 总结
  • 这周六参加了一个美团点评的技术沙龙,其中一位老师在介绍他们自研的 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 中的基础知识: Callable 和  Future

    
    public class Main {
        public static void main(String[] args) throws Exception{
            final ExecutorService executorService = Executors.newFixedThreadPool(10);
            long start = System.currentTimeMillis();
            FutureInteger resultFuture1 = executorService.submit(new CallableInteger() {
                @Override
                public Integer call() throws Exception {
                    return method1() + method2();
                }
            });
            FutureInteger resultFuture2 = executorService.submit(new CallableInteger() {
                @Override
                public Integer call() throws Exception {
                    FutureInteger resultFuture3 = executorService.submit(new CallableInteger() {
                        @Override
                        public Integer call() throws Exception {
                            return method3();
                        }
                    });
                    FutureInteger resultFuture4 = executorService.submit(new CallableInteger() {
                        @Override
                        public Integer call() throws Exception {
                            return method4();
                        }
                    });
                    return method5()+resultFuture3.get()+resultFuture4.get();
                }
            });
            int result = resultFuture1.get() + resultFuture2.get();
            System.out.println("result = "+result+", total cost "+(System.currentTimeMillis()-start)+" ms");
              executorService.shutdown();
        }
        static int method1(){
            delay200ms();
            return 1;
        }
        static int method2(){
            delay200ms();
            return 2;
        }
        static int method3(){
            delay200ms();
            return 3;
        }
        static int method4(){
            delay200ms();
            return 4;
        }
        static int method5(){
            delay200ms();
            return 5;
        }
        static void delay200ms(){
            try{
                Thread.sleep(200);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    最终控制台打印:

    result = 15, total cost 413 ms

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

    RPC 异步调用

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

    1 为接口增加 @MotanAsync 注解

    
    @MotanAsync
    public interface DemoApi {
        DemoDto randomDemo(String id);
    }
    

    2 添加 Maven 插件

    
    build
        plugins
            plugin
                groupIdorg.codehaus.mojo/groupId
                artifactIdbuild-helper-maven-plugin/artifactId
                version1.10/version
                executions
                    execution
                        phasegenerate-sources/phase
                        goals
                            goaladd-source/goal
                        /goals
                        configuration
                            sources
                                source${project.build.directory}/generated-sources/annotations/source
                            /sources
                        /configuration
                    /execution
                /executions
            /plugin
        /plugins
    /build
    

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

    
    public interface DemoApiAsync extends DemoApi {
      ResponseFuture randomDemoAsync(String id);
    }
    

    3 注入接口即可调用

    
    @Service
    public class DemoService {
        @MotanReferer
        DemoApi demoApi;
        @MotanReferer
        DemoApiAsync demoApiAsync;//1
        public DemoDto randomDemo(String id){
            DemoDto demoDto = demoApi.randomDemo(id);
            return demoDto;
        }
        public DemoDto randomDemoAsync(String id){
            ResponseFuture responseFuture = demoApiAsync.randomDemoAsync(id);//2
            DemoDto demoDto = (DemoDto) responseFuture.getValue();
            return demoDto;
        }
    }
    

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

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

    总结

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

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

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

    666. 彩蛋

    如果你对 RPC 并发感兴趣,欢迎加入我的知识一起交流。

    【RPC 专栏】Motan 中使用异步 RPC 接口

    目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:

    01. 调试环境搭建
    02. 项目结构一览
    03. API 配置(一)之应用
    04. API 配置(二)之服务提供者
    05. API 配置(三)之服务消费者
    06. 属性配置
    07. XML 配置
    08. 核心流程一览

    一共 60 篇++

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 【RPC 专栏】Motan 中使用异步 RPC 接口


     上一篇
    【RPC 专题】深入理解 RPC 之服务注册与发现篇 【RPC 专题】深入理解 RPC 之服务注册与发现篇
    点击上方“芋道源码”,选择“置顶公众号” 技术文章第一时间送达! 源码精品专栏 [**中文详细注释的开源项目**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=22474844
    2021-04-05
    下一篇 
    【RPC 专栏】深入理解 RPC 之协议篇 【RPC 专栏】深入理解 RPC 之协议篇
    点击上方“芋道源码”,选择“置顶公众号” 技术文章第一时间送达! 源码精品专栏 [**中文详细注释的开源项目**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=22474844
    2021-04-05