RPC
Posted 焦叔育人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RPC相关的知识,希望对你有一定的参考价值。
RPC 框架作为微服务体系的底层支撑,也成了日常开发的必备工具。
RPC 框架已经不仅是进行远程调用的基础工具,还需要提供路由、服务发现、负载均衡、容错等能力。
基础能力
RPC(Remote Procedure Call)远程过程调用,顾名思义最基本的能力当然是远程调用一个过程。放到今天的面向对象的语言中,其实就是调用一个远程的方法。在远程我们必须先定义这个方法,然后才可以通过 RPC 框架调用该方法,远程调用不仅可以传参数、获取到返回值,还可以捕捉调用过程中的异常。RPC 让远程调用就像本地调用一样。
远程调用的开销较大,调用远程方法有网络开销
多路复用的优化
RPC 提供的是远程方法的调用,但本质上是数据的传递,传递数据有一个最基本的问题要处理,就是提升吞吐量(单位时间传递的数据量)。如果为每个远程调用(请求)建立一个连接,就会造成资源的浪费,因此通常我们会考虑多个请求复用一个连接,叫作多路复用。
单个 TCP 连接的极限传输速度受到窗口大小、缓冲区等因素的制约,不一定可以用满网络资源。如果传输量特别大的时候,有可能需要考虑提供多个连接,每个连接再去考虑多路复用的情况。
调用约定和命名
远程调用一个函数,命名空间+类名+方法名是一个比较好的选择,简而言之,每个可以远程调用的方法就是一个字符串。
在进行远程调用的时候,给远程方法命名是调用约定的一部分。我们通过调用命名空间下完整的名称调用远程方法。在面向对象的语言中,还有一种常见的做法是先不具体指定调用的方法,而是先创造一个远程对象的实例。比如上面例子中我们先通过 RPC 框架构造一个 PayService 对象的实例。这里会用到一些特别的编程技巧,比如代理设计模式、动态接口生成等。
不过归根结底,我们调用的本质就是字符串名称。而实现这个调用,你需要知道两件事情:
IP 是多少,也就是方法在哪台机器上调用;
端口是多少,也就是哪个服务提供这个调用。
注册和发现
调用的时候,我们需要根据字符串(命名)去获取 IP 和端口(机器和服务)。
用 Redis 的hash对象存储这个对应关系就很不错。
写这个hash对象的过程,也就是服务被记录的过程称作注册。我们远程调用一个 RPC 服务的时候,调用端提供的是 RPC 服务的名称(例如:命名空间+对象+方法),根据名称查找到提供服务的 IP + 端口清单并指定某个 IP + 端口(提供服务)的过程称作发现。
注册就是写一个共享的哈希表,发现就是查哈希表再决定服务的响应者。在实际的设计中,要考虑的因素会更多。
设计注册和发现两个功能的最大的价值是让客户端不再需要关注服务的部署细节,这样方便在全局动态调整服务的部署策略。
负载均衡的设计
负载均衡器的设计往往需要和 RPC 框架一起考虑。因为 RPC 框架提供了注册、发现的能力,提供发现能力的模块本身就是一个负载均衡器。因此负载均衡可以看作发现模块的一个子组件。请求到达 RPC 的网关(或某个路由程序)后,发现组件会提供服务对应的所有实例(IP + 端口),然后负载均衡算法会指定其中一个响应这个请求。
可用性和容灾
当一个服务实例崩溃的时候(不可用),因为有发现模块的存在,可以及时从注册表中删除这个服务实例。只要服务本身有足够多的实例,比如多个容器而且部署在不同的机器上,那么完全不可能用的风险会大大降低。
如果遇到临时访问量剧增,需要扩容的场景。这个时候只需要上线更多的容器,并且去注册即可。当然这要求部署模块和注册模块之间有较高的协同,这块可以用自动化脚本衔接。
设计一个 RPC 框架最基础的能力就是实现远程方法的调用。这里需要一个调用约定,比如怎么描述一个远程的方法,发送端怎么传递参数,接收方如何解析参数?如果发生异常应该如何处理?
实现了基本调用能力的基础上,接下来就是提供服务的注册、发现能力。有了这两个能力,就可以向客户端完全屏蔽服务的部署细节,并衍生出容灾、负载均衡的设计。
还需要思考底层具体网络的传输问题。如果用 TCP 要思考多路复用以及连接数量的问题;如果是 UDP,需要增加对于可靠性保证的思考。如果使用了消息队列,还需要考虑服务的幂等性设计等。
以上是关于RPC的主要内容,如果未能解决你的问题,请参考以下文章