通俗地解释一下RPC框架

 

什么是 RPC ?

RPC (Remote Procedure Call)即远程过程调用,是分布式系统常见的一种通讯方法,已经有 40 多年历史。当两个物理分离的子系统须要创建逻辑上的关联时,RPC 是牵线搭桥的常见技术手段之一。除 RPC 以外,常见的多系统数据交互方案还有分布式消息队列、HTTP 请求调用、数据库和分布式缓存等。nginx

 

 

其中 RPC 和 HTTP 调用是没有通过中间件的,它们是端到端系统的直接数据交互。HTTP 调用其实也能够当作是一种特殊的 RPC,只不过传统意义上的 RPC 是指长链接数据交互,而 HTTP 通常是指即用即走的短连接。git

RPC 在咱们熟知的各类中间件中都有它的身影。Nginx/Redis/MySQL/Dubbo/Hadoop/Spark/Tensorflow 等重量级开源产品都是在 RPC 技术的基础上构建出来的,咱们这里说的 RPC 指的是广义的 RPC,也就是分布式系统的通讯技术。RPC 在技术中的地位比如咱们身边的空气,它无处不在,可是又有不少人根本不知道它的存在。github

 

本地过程调用数据库

RPC就是要像调用本地的函数同样去调远程函数。在研究RPC前,咱们先看看本地调用是怎么调的。假设咱们要调用函数Multiply来计算lvalue * rvalue的结果:缓存

1 int Multiply(int l, int r) {
2    int y = l * r;
3    return y;
4 }
5 
6 int lvalue = 10;
7 int rvalue = 20;
8 int l_times_r = Multiply(lvalue, rvalue);

那么在第8行时,咱们实际上执行了如下操做:服务器

  1. 将 lvalue 和 rvalue 的值压栈
  2. 进入Multiply函数,取出栈中的值10 和 20,将其赋予 l 和 r
  3. 执行第2行代码,计算 l * r ,并将结果存在 y
  4. 将 y 的值压栈,而后从Multiply返回
  5. 第8行,从栈中取出返回值 200 ,并赋值给 l_times_r

以上5步就是执行本地调用的过程。(20190116注:以上步骤只是为了说明原理。事实上编译器常常会作优化,对于参数和返回值少的状况会直接将其存放在寄存器,而不须要压栈弹栈的过程,甚至都不须要调用call,而直接作inline操做。仅就原理来讲,这5步是没有问题的。)网络

 

远程过程调用带来的新问题框架

在远程调用时,咱们须要执行的函数体是在远程的机器上的,也就是说,Multiply是在另外一个进程中执行的。这就带来了几个新问题:socket

  1. Call ID映射。咱们怎么告诉远程机器咱们要调用Multiply,而不是Add或者FooBar呢?在本地调用中,函数体是直接经过函数指针来指定的,咱们调用Multiply,编译器就自动帮咱们调用它相应的函数指针。可是在远程调用中,函数指针是不行的,由于两个进程的地址空间是彻底不同的。因此,在RPC中,全部的函数都必须有本身的一个ID。这个ID在全部进程中都是惟一肯定的。客户端在作远程过程调用时,必须附上这个ID。而后咱们还须要在客户端和服务端分别维护一个 {函数 <--> Call ID} 的对应表。二者的表不必定须要彻底相同,但相同的函数对应的Call ID必须相同。当客户端须要进行远程调用时,它就查一下这个表,找出相应的Call ID,而后把它传给服务端,服务端也经过查表,来肯定客户端须要调用的函数,而后执行相应函数的代码。
  2. 序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,咱们只须要把参数压到栈里,而后让函数本身去栈里读就行。可是在远程过程调用时,客户端跟服务端是不一样的进程,不能经过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(好比服务端用C++,客户端用Java或者Python)。这时候就须要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成本身能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也须要序列化反序列化的过程。
  3. 网络传输。远程调用每每用在网络上,客户端和服务端是经过网络链接的。全部的数据都须要经过网络传输,所以就须要有一个网络传输层。网络传输层须要把Call ID和序列化后的参数字节流传给服务端,而后再把序列化后的调用结果传回客户端。只要能完成这二者的,均可以做为传输层使用。所以,它所使用的协议实际上是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也能够,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。

因此,要实现一个RPC框架,其实只须要把以上三点实现了就基本完成了。分布式

Call ID映射能够直接使用函数字符串,也可使用整数ID。映射表通常就是一个哈希表。

序列化反序列化能够本身写,也可使用Protobuf或者FlatBuffers之类的。

网络传输库能够本身写socket,或者用asio,ZeroMQ,Netty之类。

 

最后,有兴趣的能够看咱们本身写的一个小而精的RPC库 tinyrpc(hjk41/tinyrpc),对于理解RPC如何工做颇有好处。

 

---------------------------------------------------------------------------------------------------------------

SOA的发展
 

咱们在作一个访问量不大的项目的时候,一台服务器部署上一个应用+数据库也就够了.

那么访问量稍微大一点以后呢,为了解决用户反馈的卡,反应慢的状况,咱们就上集群.架设nginx,部署多个服务,由nginx负责把请求转发到其余服务上,这样就解决了用户说的卡慢问题.

过了一段时间以后呢,咱们发现数据库已经扛不住了,应用服务无缺,数据库有时候宕机. 那这个时候呢,咱们就上数据库读写分离,再架设几台数据库服务器,作主从,作分库分表. 而后数据库也不宕机了,服务又恢复了流畅.

又过了一段时间,公司事业增增日上,服务访问量愈来愈高,且大部分都是查询, 吸收以前宕机且为了办证数据库的健壮性,咱们这个时候又加上了缓存, 把用户高频次访问的数据放到缓存里.

后来,项目功能愈来愈多,整个项目也愈发庞大,修改一个类就须要全盘上传,切换nginx重启,这样的发布流程愈来愈长,愈来愈繁杂.而后咱们开始把模块拆分,用户信息分个项目,订单系统分一个项目.这样就达到了,用户模块代码修改的时候,只须要更新用户信息服务就行了.可是仍是须要切换顶层的nginx.把要重启的服务的流量切到可用服务上. 这个时候咱们就想到了RPC

那么RPC解决了什么呢? 全部的服务在启动的时候注册到一个注册机里面,而后顶层处理在接收到nginx的请求时,去注册机找一个可用的服务,并调用接口. 这样子呢,在不加新功能的时候,顶层处理服务咱们就不须要动了? 那修改了用户信息项目的时候,咱们只须要一个个更新用户信息项目的服务群就行了?

这样的话,不管是扩展仍是服务的健壮性都妥妥的了

相关文章
相关标签/搜索