微信搜 「yes的练级攻略」干货满满,不然来掐我,回复【123】一份20W字的算法刷题笔记等你来领。 个人文章汇总:https://github.com/yessimida/yes 欢迎 star !

Hola,我是 yes。

在了解 Dubbo 之前有必要先来剖析一波 RPC ,先搞清 RPC 原理再去深入了解 Dubbo 会起到事半功倍的效果。

理解核心原理很重要,市面上所有 RPC 框架都逃不过这些核心。

搞清原理之后再看 Dubbo 就会有我说的那个熟悉感和“认可感”。

其实 RPC 不仅仅用在我们平日微服务的调用中,在很多和网络通信相关的场景都能用到 RPC。

比如消息队列客户端和 Broker 之间的交互,还有和一些其他中间件的交互都会用到  RPC。

你可能会说没啊?哪有 RPC 调用?

嘿嘿,你看这就是 RPC 的作用,让你无感知地完成了远程通信。

其实在这篇我被喷的上“热门”的文章中已经提过一次 RPC,也提到了 HTTP 和 RPC 的区别,不过那次的主角是 HTTP 。

这次咱们深入剖析一波 RPC,从根上来理解一波。

来,上车!

正文

RPC 全称是 Remote Procedure Call ,即远程过程调用,其对应的是我们的本地调用。

远程其实指的就是需要网络通信,可以理解为调用远程机器上的方法。

那可能有人说:我用 HTTP 调用不就是远程调用了,那不也叫 RPC 了?

不是的,RPC 的目的是:让我们调用远程方法像调用本地方法一样无差别。

来看下代码就很清晰,比如本来没有拆分服务都是本地调用的时候方法是这样写的:

    public String getSth(String str) {
         return yesService.get(str);
    }

如果 yesSerivce 被拆分出去,此时需要远程调用了,如果用 HTTP 方式,可能就是:

    public String getSth(String str) {
        RequestParam param = new RequestParam();
        ......
        return HttpClient.get(url, param,.....);
    }

此时需要关心远程服务的地址,还需要组装请求等等,而如果采用 RPC 调用那就是:

   public String getSth(String str) {
        // 看起来和之前调用没差?哈哈没唬你,
        // 具体的实现已经搬到另一个服务上了,这里只有接口。
        // 看完下面就知道了。
         return yesService.get(str);  
    }

所以说  RPC 其实就是用来屏蔽远程调用网络相关的细节,使得远程调用和本地调用使用一致,让开发的效率更高。

在了解了 RPC 的作用之后,我们来看看 RPC 调用需要经历哪些步骤。

RPC 调用基本流程

按上面的例子来说,yesService 服务实现被移到了远程服务上,本地没有具体的实现只有一个接口。

那这时候我们需要调用 yesService.get(str) ,该怎么办呢?

我们所要做的就是把传入的参数和调用的接口全限定名通过网络通信告知到远程服务那里。

然后远程服务接收到参数和接口全限定名就能选中具体的实现并进行调用。

业务处理完之后再通过网络返回结果,这就搞定了!

上面的操作这些就是由yesService.get(str) 触发的。

不过我们知道 yesService 就是一个接口,没有实现的,所以这些操作是怎么来的?

是通过动态代理来的。

RPC 会给接口生成一个代理类,所以我们调用这个接口实际调用的是动态生成的代理类,由代理类来触发远程调用,这样我们调用远程接口就无感知了。

动态代理想必大家都比较熟悉,最常见的就是 Spring 的 AOP 了,涉及的有 JDK 动态代理和 cglib。

在 Dubbo 中用的是 Javassist,至于为什么用这个其实梁飞大佬已经写了博客说明了。

他当时对比了 JDK 自带的、ASM、CGLIB(基于ASM包装)、Javassist。

经过测试最终选用了 Javassist。

梁飞:最终决定使用JAVAASSIST的字节码生成代理方式。虽然ASM稍快,但并没有快一个数量级,而JAVAASSIST的字节码生成方式比ASM方便,JAVAASSIST只需用字符串拼接出Java源码,便可生成相应字节码,而ASM需要手工写字节码。

可以看到选择一个框架的时候性能是一方面,易用性也很关键。

说回 RPC 。

现在我们知道动态代理屏蔽了 RPC 调用的细节,使得用户无感知的调用远程服务,那调用的细节有哪些呢?

序列化

像我们的请求参数都是对象,有时候是定义的  DTO ,有时候是 Map ,这些对象是无法直接在网络中传输的。

你可以理解为对象是“立体”的,而网络传输的数据是“扁平”的,最终需要转化成“扁平”的二进制数据在网络中传输。

你想想,各对象分配在内存不同位置,各种引用,这看起来是不是有种立体的感觉?

最终都是要变成一段01组成的数字传输给对方,这种就01组成的数字看起来是不是很“扁平”?

把对象转化成二进制数据的过程称为序列化,把二进制数据转化成对象的过程称为反序列化。

当然如何选择序列化格式也很重要。

比如采用二进制的序列化格式数据更加紧凑,采用 JSON 等文本型序列化格式可读性更佳,排查问题比较方便。

还有很多序列化选择,一般需要综合考虑通用性、性能、可读性和兼容性。

具体本文就不分析了,之后再专门写一篇分析各种序列化协议的。

RPC 协议

刚才也提到了只有二进制数据才能在网络中传输,那一堆二进制在底层看来是连起来的,它可不会管你哪些数据是哪个请求的。

但接收方得知道呀,不然就不能顺利的把二进制数据还原成对应的一个个请求了。

于是就需要定义一个协议,来约定一些规范,制定一些边界使得二进制数据可以被还原。

比如下面一串数字按照不同位数来识别得到的结果是不同的。

所以协议其实就定义了到底如何构造和解析这些二进制数据。

我们的参数肯定比上面的复杂,因为参数值长度是不定的,而且协议常常伴随着升级而扩展,毕竟有时候需要加一些新特性,那么协议就得变了。

一般 RPC 协议都是采用协议头+协议体的方式。

协议头放一些元数据,包括:魔法位、协议的版本、消息的类型、序列化方式、整体长度、头长度、扩展位等。

协议体就是放请求的数据了。

通过魔法位可以得知这是不是咱们约定的协议,比如魔法位固定叫 233 ,一看我们就知道这是 233 协议。

然后协议的版本是为了之后协议的升级。

从整体长度和头长度我们就能知道这个请求到底有多少位,前面多少位是头,剩下的都是协议体,这样就能识别出来,扩展位就是留着日后扩展备用。

贴一下 Dubbo 协议:

可以看到有 Magic 位,请求  ID, 数据长度等等。

网络传输

组装好数据就等着发送了,这时候就涉及网络传输了。

网络通信那就离不开网络 IO 模型了。

网络 IO 分为这四种模型,具体以后单独写文章分析,这篇就不展开了。

一般而言我们用的都是 IO 多路复用,因为大部分 RPC 调用场景都是高并发调用,IO 复用可以利用较少的线程 hold 住很多请求。

一般 RPC 框架会使用已经造好的轮子来作为底层通信框架。

例如 Java 语言的都会用 Netty ,人家已经封装的很好了,也做了很多优化,拿来即用,便捷高效。

小结

RPC 通信的基础流程已经讲完了,看下图:

响应返回就没画了,反正就是倒着来。

我再用一段话来总结一下:

服务调用方,面向接口编程,利用动态代理屏蔽底层调用细节将请求参数、接口等数据组合起来并通过序列化转化为二进制数据,再通过 RPC 协议的封装利用网络传输到服务提供方。

服务提供方根据约定的协议解析出请求数据,然后反序列化得到参数,找到具体调用的接口,然后执行具体实现,再返回结果。

这里面还有很多细节。

比如请求都是异步的,所以每个请求会有唯一 ID,返回结果会带上对应的 ID, 这样调用方就能通过 ID 找到对应的请求塞入相应的结果。

有人会问为什么要异步,那是为了提高吞吐。

当然还有很多细节,会在之后剖析 Dubbo 的时候提到,结合实际中间件体会才会更深。

真正工业级别的 RPC

以上提到的只是 RPC 的基础流程,这对于工业级别的使用是远远不够的。

生产环境中的服务提供者都是集群部署的,所以有多个提供者,而且还会随着大促等流量情况动态增减机器。

因此需要注册中心,作为服务的发现。

调用者可以通过注册中心得知服务提供者们的 IP 地址等元信息,进行调用。

调用者也能通过注册中心得知服务提供者下线。

还需要有路由分组策略,调用者根据下发的路由信息选择对应的服务提供者,能实现分组调用、灰度发布、流量隔离等功能。

还需要有负载均衡策略,一般经过路由过滤之后还是有多个服务提供者可以选择,通过负载均衡策略来达到流量均衡。

当然还需要有异常重试,毕竟网络是不稳定的,而且有时候某个服务提供者也可能出点问题,所以一次调用出错进行重试,较少业务的损耗。

还需要限流熔断,限流是因为服务提供者不知道会接入多少调用者,也不清楚每个调用者的调用量,所以需要衡量一下自身服务的承受值来进行限流,防止服务崩溃。

而熔断是为了防止下游服务故障导致自身服务调用超时阻塞堆积而崩溃,特别是调用链很长的那种,影响很大。

比如A=>B=>C=>D=>E,然后 E 出了故障,你看ABCD四个服务就傻等着,慢慢的资源就占满了就崩了,全崩。

大致就是以上提到的几点,不过还能细化,比如负载均衡的各种策略、限流到底是限制总流量还是根据每个调用者指定限流量,还是上自适应限流等等。

这个在之后分析 Dubbo 的时候都会提到,等着哈。

最后

我之前面过一个同学,两年经验,简历写着熟悉 Spring Cloud Alibaba 然后了解 Dubbo 。

我问他 RPC 的调用原理,他问我什么是 RPC,没听过这个名词。

这就太浮在表面了。

理解原理还是很重要的,像我上面提到的动态代理也不是一定是要的,像 C++ 就没有动态代理, gRPC 框架用的是代码生成。

反正最终只要能屏蔽调用细节,不需要使用者关心即可,至于用什么方式达到这个目的,影响不大。

还有上面提到面向接口,其实有时候就是没接口,例如一些服务网关,暴露出 HTTP 调用的方式给调用者来调用后端 RPC 服务。

网关是要接入很多后端服务的,所以不可能依赖后端的接口,不然就不灵活了。

这里就有个泛化调用的概念。

其实只要你理解了请求方无非就是告知服务提供方我要调哪个方法,参数都是哪些,你就能很容易的理解什么叫泛化调用。

也就能理解其实不需要接口我们也能进行 RPC 调用。

具体泛化调用是什么之后写  Dubbo 会提到。

所以听起来好像很高级的玩意,如果你理解了本质,其实也就这么点东西。

万变不离其宗。

欢迎关注我的公众号【yes的练级攻略】,更多硬核文章等你来读。

更多文章可看我的文章汇总:https://github.com/yessimida/yes 欢迎 star !


我是 yes,从一点点到亿点点,欢迎在看、转发、留言,我们下篇见。

RPC 核心,万变不离其宗的更多相关文章

  1. 万变不离其宗之UART要点总结

    [导读] 单片机开发串口是应用最为广泛的通信接口,也是最为简单的通信接口之一,但是其中的一些要点你是否明了呢?来看看本人对串口的一些总结,当然这个总结并不能面面俱到,只是将个人认为具有共性以及相对比较 ...

  2. 从基层容器类看万变不离其宗的JAVA继承体系

    以容器类为例子,可以观一叶而知秋,看看以前的前辈们是如何处理各种面向对象思想下的继承体系的.读的源代码越多,就越要总结这个继承关系否则读的多也忘得快. 首先摆上一张图片: 看到这张图很多人就慌了,难道 ...

  3. 《吃透MQ系列》核心基础全在这里了

    这是<吃透XXX>技术系列的开篇,这个系列的思路是:先找到每个技术栈最本质的东西,然后以此为出发点,逐渐延伸出其他核心知识.所以,整个系列侧重于思考力的训练,不仅仅是讲清楚 What,而是 ...

  4. spring技术核心概念纪要

    一.背景 springframework 从最初的2.5版本发展至今,期间已经发生了非常多的修正及优化.许多新特性及模块的出现,使得整个框架体系显得越趋庞大,同时也带来了学习及理解上的困难. 本文阐述 ...

  5. RPC协议

    什么是 RPC? 初步印象 RPC的语义是远程过程调用,在一般的印象中,就是将一个服务调用封装在一个本地方法中,让调用者像使用本地方法一样调用服务.而具体的实现是通过调用方和服务方各自的stub基于T ...

  6. 拥抱.NET Core,跨平台的轻量级RPC:Rabbit.Rpc

    不久前发布了一篇博文".NET轻量级RPC框架:Rabbit.Rpc",当初只实现了非常简单的功能,也罗列了之后的计划,经过几天的不断努力又为Rabbit.Rpc增加了一大波新特性 ...

  7. NET Core,跨平台的轻量级RPC

    NET Core,跨平台的轻量级RPC:Rabbit.Rpc 特性一览 Apache License 2.0协议开源 支持客户端负载均衡(提供了轮询.随机算法的实现) 支持ZooKeeper和文件共享 ...

  8. 集群RPC通信

    RPC即远程过程调用,它的提出旨在消除通信细节.屏蔽繁杂且易错的底层网络通信操作,像调用本地服务一般地调用远程服务,让业务开发者更多关注业务开发而不必考虑网络.硬件.系统的异构复杂环境. 先看看集群中 ...

  9. NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成

    本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过. 快一个月没有写博文了,最近忙着两件事;    一:阅读刘墉先生的<说话的魅力>,以一种微妙的,你我大家都会经常遇见 ...

随机推荐

  1. linux(centos7.x)安装jdk

    一.下载与安装 下载地址:链接:https://pan.baidu.com/s/1g7MF1xqlOxWnLGf2shl3NA   提取码:epae  下载完成后将安装包上传到linxu环境中,并将其 ...

  2. Session 与 sql 会话,mysql 权限设置,mybatis 逆向工程

    Session 与 Sql 会话注意点: 通过 sqlSessionFactoty 工厂建立的与sql的会话,在进行相应的插入操作后,需要进行 commit 操作才会让数据库执行插入更新操作.如何主键 ...

  3. 聊聊kafka-client的源码

    一,感想 kafka 客户端代码很早以前 我就想研究借鉴一下,我前前后后至少阅读过三遍源码,我发现我看不下去,不知道为啥这么写,在次期间,我也参考了很多的网上的源码分析,我发现自己依然一知半解的, 慢 ...

  4. 6. Git工作流

    6.1 git工作流理解 在项目开发过程中使用 Git 的方式 6.2 git工作流分类 集中式工作流 像 SVN 一样,集中式工作流以中央仓库作为项目所有修改的单点实体.所有修改都提交到 Maste ...

  5. Java 关于策略模式+简单工厂模式下的思考

    导读 最近在做公司一个消息网关的服务,包括:短信.微信.邮件等,所有请求通过一个入口,方便接口的管理(记录日志.接口限流白名单啥的).如何写这个接口呢,还有为了以后扩展,对接过短信.微信.公众号的童鞋 ...

  6. 在Mac下配置JDK

    1.下载JDK,例JDK8U144 下载地址https://pan.baidu.com/s/1upjecBzTIMnyz0JmdkYprQ 2.安装 安装后配置JDK环境变量 cd ~ open .b ...

  7. 从docker介绍及其简介

    一.引言 1.我本地代码运行没问题啊,但是别人机器运行不了,从而导致环境不一致的问题 2.那个兄弟又写死循环了,怎么这么卡,在多用户的操作系统下,会相互影响. 天猫双十一的情况下,用户量暴涨,从而导致 ...

  8. 基本的C#面试问题(15例)

    1.给定一个int数组,编写方法以统计所有偶数的值. 有很多方法可以做到这一点,但是最直接的两种方法是: static long TotalAllEvenNumbers(int[] intArray) ...

  9. CSS聚光灯文字(无图片)

    Welcome to my admin site! h1 { font-size: 70px; color: rgba(255, 255, 255, 1); padding: 0; margin: 0 ...

  10. 第15.25节 PyQt(Python+Qt)入门学习:Model/View开发实战--使用QTableView展示Excel文件内容

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 在前面的订阅专栏<第十九章.Model/View开发:QTableView的功能及属 ...