计网 - 怎样实现 RPC 框架

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 计网 - 怎样实现 RPC 框架

20210702225354984.jpg


Pre


随着微服务架构的盛行,远程调用成了开发微服务必不可少的能力,RPC 框架作为微服务体系的底层支撑,也成了日常开发的必备工具。当下,RPC 框架已经不仅是进行远程调用的基础工具,还需要提供路由、服务发现、负载均衡、容错等能力。那么今天,我们就以“怎样实现 RPC 框架”为引,从设计者角度看看如何设计一个 RPC 框架。


基础能力设计


RPC(Remote Procedure Call)远程过程调用,顾名思义最基本的能力当然是远程调用一个过程。放到今天的面向对象的语言中,其实就是调用一个远程的方法。在远程我们必须先定义这个方法,然后才可以通过 RPC 框架调用该方法,远程调用不仅可以传参数、获取到返回值,还可以捕捉调用过程中的异常。RPC 让远程调用就像本地调用一样。


假设我们实现了一个rpc对象,其中的invoke方法可以实现远程调用。下面这段伪代码在调用远程的greetings方法(RPC 调用),并向远程方法传递参数arg1``arg2,然后再接收到远程的返回值。

var result = rpc.invoke("greetings", arg1, arg2, ...)

这段程序将本地看作 一个 RPC 的客户端,将远程看作一个 RPC 的服务端。如下图所示:


20210712001619480.png

服务 A 发起远程方法调用,RPC 客户端通过某种协议将请求发送给服务 B,服务 B 解析请求,进行本地方法的调用,将结果返回到服务 B 的 RPC 服务端,最终返回到服务 A。


对服务 A 来说,调用的是一个函数,从接口到返回值的设计,和调用本地函数并没有太大的差别。


当然,我们不能完全忽略这是一次远程方法调用,因为远程调用的开销较大。如果程序员没有意识到调用远程方法有网络开销,就可能会写出下面这段程序:

for(int i = 0; i < 1000000; i++) {
  rpc.invoke(...)
}

之所以写出上面的程序,是因为 没有意识到 rpc.invoke 是一次远程调用。在实际的操作过程中,rpc.invoke可能被封装到了某个业务方法中,程序员调用的时候便容易忽视这是一次远程操作。所以 RPC 调用时就要求我们对性能有清晰的认识。


多路复用的优化


RPC 提供的是远程方法的调用,但本质上是数据的传递,传递数据有一个最基本的问题要处理,就是提升吞吐量(单位时间传递的数据量)


如果为每个远程调用(请求)建立一个连接,就会造成资源的浪费,因此通常我们会考虑多个请求复用一个连接,叫作多路复用

在具体实现多路复用的时候,也会有不同的策略。假设我们要发送数据 A、B、C、D,那么一种方式是建立一个连接,依次将 A、B、C、D 发过去,就像下图这样:


20210712001949911.png


在这种结构中,利用一个连接顺序发送 A、B、C、D 将多个请求放入一个连接的方式,节省了多次握手、挥手的时间,但是由于 ABCD 不是真的并行发送,而是顺序发送,当其中某个请求的体积较大时,容易阻塞其他请求。比如下图这种情况:


20210712002005569.png


在 A 较大的时候,B,C,D 就只能等 A 完全传送完成才能发生传送。这样的模型对于 RPC 请求/响应大小不平均的网络不太友好,体积小的请求/响应可能会因为一些大体积的请求/响应而延迟。

因此还有另一种常见的多路复用方案,就是将 A、B、C、D 切片一起传输,如下图所示:

20210712002035700.png


上图中,我们用不同颜色代表不同的传输任务。采用顺序传输方案将 A、B、C、D 用一个连接传输节省了握手、挥手的成本。切片传输的方案在这之上,将数据切片可以保证大、小任务并行,不会因为大任务阻塞小任务。


另外还有一个需要考虑的点,是单个 TCP 连接的极限传输速度受到窗口大小、缓冲区等因素的制约,不一定可以用满网络资源。如果传输量特别大的时候,有可能需要考虑提供多个连接,每个连接再去考虑多路复用的情况。


调用约定和命名


接下来,我们一起思考下服务的命名。远程调用一个函数,命名空间+类名+方法名是一个比较好的选择,简而言之,每个可以远程调用的方法就是一个字符串。


比如远程调用一个支付服务对象 PayService 的 pay 方法,命名空间可能是 trade.payment,对象名称是 PayService,方法名称是 pay。组合起来可以是一个完整的字符串,例如用 # 分割


trade.payment#PayService#pay。


在进行远程调用的时候,给远程方法命名是调用约定的一部分。我们通过调用命名空间下完整的名称调用远程方法。在面向对象的语言中,还有一种常见的做法是先不具体指定调用的方法,而是先创造一个远程对象的实例。比如上面例子中我们先通过 RPC 框架构造一个 PayService 对象的实例。这里会用到一些特别的编程技巧,比如代理设计模式、动态接口生成等。


不过归根结底,我们调用的本质就是字符串名称。而实现这个调用,你需要知道两件事情:


  • IP 是多少,也就是方法在哪台机器上调用;
  • 端口是多少,也就是哪个服务提供这个调用。

注册和发现


调用的时候,我们需要根据字符串(命名)去获取 IP 和端口(机器和服务)。


机器可以是虚拟机、容器、实体机,也可以是某个拥有虚拟网卡的代理。在网络的世界中,需要的只是网络接口和 IP 地址。而操作系统区分应用需要的是端口。所以,在调用过程中,我们需要的是一个注册表,存储了字符串和 IP + 端口的对应关系。


聪明的同学可能马上会想到,用 Redis 的hash对象存储这个对应关系就很不错。当我们上线一个服务的时候,就在 Redis 的某个hash对象中存储它和它对应的 IP 地址 + 端口列表。为什么是存一个列表?因为一个服务可能由多个机器提供。


通常我们将写这个hash对象的过程,也就是服务被记录的过程称作注册。我们远程调用一个 RPC 服务的时候,调用端提供的是 RPC 服务的名称(例如:命名空间+对象+方法),根据名称查找到提供服务的 IP + 端口清单并指定某个 IP + 端口(提供服务)的过程称作发现。


当然,我们不能就这样简单理解成:注册就是写一个共享的哈希表,发现就是查哈希表再决定服务的响应者。在实际的设计中,要考虑的因素会更多。


比如基于 Redis 的实现,如果所有 RPC 调用都需要去 Redis 查询,会造成负责发现的中间件压力较大。实际的操作过程中,往往会增加缓存。也就是 RPC 调用者会缓存上一次调用的 IP + 端口。但是这样设计,缓存又可能会和注册表之间产生数据不一致的问题。这个时候,可以考虑由分布式共识服务比如 ZooKeeper 提供订阅,让 RPC 调用者订阅到服务地址的变更,及时更新自己的缓存。


设计注册和发现两个功能的最大的价值是让客户端不再需要关注服务的部署细节,这样方便在全局动态调整服务的部署策略。


负载均衡的设计


在设计 RPC 框架的时候,负载均衡器的设计往往需要和 RPC 框架一起考虑。因为 RPC 框架提供了注册、发现的能力,提供发现能力的模块本身就是一个负载均衡器。因此负载均衡可以看作发现模块的一个子组件。请求到达 RPC 的网关(或某个路由程序)后,发现组件会提供服务对应的所有实例(IP + 端口),然后负载均衡算法会指定其中一个响应这个请求。


可用性和容灾


当一个服务实例崩溃的时候(不可用),因为有发现模块的存在,可以及时从注册表中删除这个服务实例。只要服务本身有足够多的实例,比如多个容器而且部署在不同的机器上,那么完全不可能用的风险会大大降低。当然,可用性是不可能 100% 实现的。


另外,注册表和 RPC 调用者之间必然存在不一致现象,而且注册表的更新本身也可能滞后。比如确认一个服务有没有崩溃,可能需要一个心跳程序持续请求这个服务,因此 RPC 的调用者如果调用到一个不存在的服务,或者调用到一个发生崩溃的服务,需要自己重新去发现组件申请新的服务实例(地址 + 端口)。


如果遇到临时访问量剧增,需要扩容的场景。这个时候只需要上线更多的容器,并且去注册即可。当然这要求部署模块和注册模块之间有较高的协同,这块可以用自动化脚本衔接。


小结


总结下,设计一个 RPC 框架最基础的能力就是实现远程方法的调用。这里需要一个调用约定,比如怎么描述一个远程的方法,发送端怎么传递参数,接收方如何解析参数?如果发生异常应该如何处理?具体来说,这些事情都不难实现,只是比较烦琐。其实不仅仅在 RPC 调用时有调用约定,编译器在实现函数调用的时候,也会有调用约定。另外,还有一些在 RPC 基础上建立起来的更复杂、更体系化的约定,比如说面向服务架构(SOA)。


在实现了基本调用能力的基础上,接下来就是提供服务的注册、发现能力。有了这两个能力,就可以向客户端完全屏蔽服务的部署细节,并衍生出容灾、负载均衡的设计。


当然,程序员还需要思考底层具体网络的传输问题。如果用 TCP 要思考多路复用以及连接数量的问题;如果是 UDP,需要增加对于可靠性保证的思考。如果使用了消息队列,还需要考虑服务的幂等性设计等。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
负载均衡 Dubbo Java
Dubbo 3.x:探索阿里巴巴的开源RPC框架新技术
随着微服务架构的兴起,远程过程调用(RPC)框架成为了关键组件。Dubbo,作为阿里巴巴的开源RPC框架,已经演进到了3.x版本,带来了许多新特性和技术改进。本文将探讨Dubbo 3.x中的一些最新技术,包括服务注册与发现、负载均衡、服务治理等,并通过代码示例展示其使用方式。
359 9
|
6月前
|
JSON 负载均衡 网络协议
Rpc编程系列文章第二篇:RPC框架设计目标
Rpc编程系列文章第二篇:RPC框架设计目标
|
6月前
|
设计模式 负载均衡 网络协议
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
267 0
|
17天前
|
自然语言处理 负载均衡 API
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
gRPC 是一种现代开源高性能远程过程调用(RPC)框架,支持多种编程语言,可在任何环境中运行。它通过高效的连接方式,支持负载平衡、跟踪、健康检查和身份验证,适用于微服务架构、移动设备和浏览器客户端连接后端服务等场景。gRPC 使用 Protocol Buffers 作为接口定义语言,支持四种服务方法:一元 RPC、服务器流式处理、客户端流式处理和双向流式处理。
|
6月前
|
Dubbo Java 应用服务中间件
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
|
3月前
|
Dubbo 网络协议 Java
RPC框架:一文带你搞懂RPC
这篇文章全面介绍了RPC(远程过程调用)的概念、原理和应用场景,解释了RPC如何工作以及为什么在分布式系统中广泛使用,并探讨了几种常用的RPC框架如Thrift、gRPC、Dubbo和Spring Cloud,同时详细阐述了RPC调用流程和实现透明化远程服务调用的关键技术,包括动态代理和消息的编码解码过程。
RPC框架:一文带你搞懂RPC
|
2月前
|
XML 负载均衡 监控
分布式-dubbo-简易版的RPC框架
分布式-dubbo-简易版的RPC框架
|
3月前
|
XML 存储 JSON
(十二)探索高性能通信与RPC框架基石:Json、ProtoBuf、Hessian序列化详解
如今这个分布式风靡的时代,网络通信技术,是每位技术人员必须掌握的技能,因为无论是哪种分布式技术,都离不开心跳、选举、节点感知、数据同步……等机制,而究其根本,这些技术的本质都是网络间的数据交互。正因如此,想要构建一个高性能的分布式组件/系统,不得不思考一个问题:怎么才能让数据传输的速度更快?
|
5月前
|
存储 缓存 Linux
【实战指南】嵌入式RPC框架设计实践:六大核心类构建高效RPC框架
在先前的文章基础上,本文讨论如何通过分层封装提升一个针对嵌入式Linux的RPC框架的易用性。设计包括自动服务注册、高性能通信、泛型序列化和简洁API。框架分为6个关键类:BindingHub、SharedRingBuffer、Parcel、Binder、IBinder和BindInterface。BindingHub负责服务注册,SharedRingBuffer实现高效数据传输,Parcel处理序列化,而Binder和IBinder分别用于服务端和客户端交互。BindInterface提供简单的初始化接口,简化应用集成。测试案例展示了客户端和服务端的交互,验证了RPC功能的有效性。
402 7
|
4月前
|
分布式计算 负载均衡 数据安全/隐私保护
什么是RPC?有哪些RPC框架?
RPC(Remote Procedure Call,远程过程调用)是一种允许运行在一台计算机上的程序调用另一台计算机上子程序的技术。这种技术屏蔽了底层的网络通信细节,使得程序间的远程通信如同本地调用一样简单。RPC机制使得开发者能够构建分布式计算系统,其中不同的组件可以分布在不同的计算机上,但它们之间可以像在同一台机器上一样相互调用。
152 8