如何手撸一个较为完整的RPC框架?

发表于 2年以前  | 总阅读数:405 次

缘起

最近在公司分享了手撸RPC,因此做一个总结。

概念篇

RPC 是什么?

RPC 称远程过程调用(Remote Procedure Call),用于解决分布式系统中服务之间的调用问题。通俗地讲,就是开发者能够像调用本地方法一样调用远程的服务。所以,RPC的作用主要体现在这两个方面:

  • 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法;
  • 隐藏底层网络通信的复杂性,让我们更专注于业务逻辑。

RPC 框架基本架构

下面我们通过一幅图来说说 RPC 框架的基本架构

RPC 框架包含三个最重要的组件,分别是客户端、服务端和注册中心。在一次 RPC 调用流程中,这三个组件是这样交互的:

  • 服务端在启动后,会将它提供的服务列表发布到注册中心,客户端向注册中心订阅服务地址;
  • 客户端会通过本地代理模块 Proxy 调用服务端,Proxy 模块收到负责将方法、参数等数据转化成网络字节流;
  • 客户端从服务列表中选取其中一个的服务地址,并将数据通过网络发送给服务端;
  • 服务端接收到数据后进行解码,得到请求信息;
  • 服务端根据解码后的请求信息调用对应的服务,然后将调用结果返回给客户端。

RPC 框架通信流程以及涉及到的角色

从上面这张图中,可以看见 RPC 框架一般有这些组件:服务治理(注册发现)、负载均衡、容错、序列化/反序列化、编解码、网络传输、线程池、动态代理等角色,当然有的RPC框架还会有连接池、日志、安全等角色。

具体调用过程

  1. 服务消费方(client)以本地调用方式调用服务
  2. client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
  3. client stub 将消息进行编码并发送到服务端
  4. server stub 收到消息后进行解码
  5. server stub 根据解码结果调用本地的服务
  6. 本地服务执行并将结果返回给 server stub
  7. server stub 将返回导入结果进行编码并发送至消费方
  8. client stub 接收到消息并进行解码
  9. 服务消费方(client)得到结果

RPC 消息协议

RPC调用过程中需要将参数编组为消息进行发送,接收方需要解组消息为参数,过程处理结果同样需要经编组、解组。消息由哪些部分构成及消息的表示形式就构成了消息协议。

RPC调用过程中采用的消息协议称为RPC消息协议。

实战篇

从上面的概念我们知道一个RPC框架大概有哪些部分组成,所以在设计一个RPC框架也需要从这些组成部分考虑。从RPC的定义中可以知道,RPC框架需要屏蔽底层细节,让用户感觉调用远程服务像调用本地方法一样简单,所以需要考虑这些问题:

  • 用户使用我们的RPC框架时如何尽量少的配置
  • 如何将服务注册到ZK(这里注册中心选择ZK)上并且让用户无感知
  • 如何调用透明(尽量用户无感知)的调用服务提供者
  • 启用多个服务提供者如何做到动态负载均衡
  • 框架如何做到能让用户自定义扩展组件(比如扩展自定义负载均衡策略)
  • 如何定义消息协议,以及编解码
  • ...等等

上面这些问题在设计这个RPC框架中都会给予解决。

技术选型

  • 注册中心 目前成熟的注册中心有Zookeeper,Nacos,Consul,Eureka,这里使用ZK作为注册中心,没有提供切换以及用户自定义注册中心的功能。
  • IO通信框架 本实现采用 Netty 作为底层通信框架,因为Netty 是一个高性能事件驱动型的非阻塞的IO(NIO)框架,没有提供别的实现,也不支持用户自定义通信框架
  • 消息协议 本实现使用自定义消息协议,后面会具体说明

项目总体结构

从这个结构中可以知道,以rpc命名开头的是rpc框架的模块,也是本项目RPC框架的内容,而consumer是服务消费者,provider是服务提供者,provider-api是暴露的服务API。

整体依赖情况

项目实现介绍

要做到用户使用我们的RPC框架时尽量少的配置,所以把rpc框架设计成一个starter,用户只要依赖这个starter,基本那就可以了。

为什么要设计成两个 starter (client-starter/server-starter) ?

这个是为了更好的体现出客户端和服务端的概念,消费者依赖客户端,服务提供者依赖服务端,还有就是最小化依赖。

为什么要设计成 starter ?

基于spring boot自动装配机制,会加载starter中的 spring.factories 文件,在文件中配置以下代码,这里我们starter的配置类就生效了,在配置类里面配置一些需要的bean。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.rrtv.rpc.client.config.RpcClientAutoConfiguration

发布服务和消费服务

  • 对于发布服务

服务提供者需要在暴露的服务上增加注解 @RpcService,这个自定义注解是基于 @service 的,是一个复合注解,具备@service注解的功能,在@RpcService注解中指明服务接口和服务版本,发布服务到ZK上,会根据这个两个元数据注册

  • 发布服务原理:

服务提供者启动之后,根据spring boot自动装配机制,server-starter的配置类就生效了,在一个 bean 的后置处理器(RpcServerProvider)中获取被注解 @RpcService 修饰的bean,将注解的元数据注册到ZK上。

  • 对于消费服务

消费服务需要使用自定义的 @RpcAutowired 注解标识,是一个复合注解,基于 @Autowired

  • 消费服务原理

要让客户端无感知的调用服务提供者,就需要使用动态代理,如上面所示, HelloWordService 没有实现类,需要给它赋值代理类,在代理类中发起请求调用。

基于spring boot自动装配,服务消费者启动,bean 后置处理器 RpcClientProcessor 开始工作,它主要是遍历所有的bean,判断每个bean中的属性是否有被 @RpcAutowired 注解修饰,有的话把该属性动态赋值代理类,这个再调用时会调用代理类的 invoke 方法。

代理类 invoke 方法通过服务发现获取服务端元数据,封装请求,通过netty发起调用。

注册中心

本项目注册中心使用ZK,由于注册中心被服务消费者和服务提供者都使用。所以把ZK放在rpc-core模块。

rpc-core 这个模块如上图所示,核心功能都在这个模块。服务注册在 register 包下。

服务注册接口,具体实现使用ZK实现。

负载均衡策略

负载均衡定义在rpc-core中,目前支持轮询(FullRoundBalance)和随机(RandomBalance),默认使用随机策略。由rpc-client-spring-boot-starter指定。

通过ZK服务发现时会找到多个实例,然后通过负载均衡策略获取其中一个实例

可以在消费者中配置 rpc.client.balance=fullRoundBalance 替换,也可以自定义负载均衡策略,通过实现接口 LoadBalance,并将创建的类加入[IOC容器] 即可。由于我们配置 @ConditionalOnMissingBean,所以会优先加载用户自定义的 bean。

自定义消息协议、编解码

所谓协议,就是通信双方事先商量好规则,服务端知道发送过来的数据将如何解析。

  • 自定义消息协议

  • 魔数:魔数是通信双方协商的一个暗号,通常采用固定的几个字节表示。魔数的作用是防止任何人随便向服务器的端口上发送数据。例如 java Class 文件开头就存储了魔数 0xCAFEBABE,在加载 Class 文件时首先会验证魔数的正确性
  • 协议版本号:随着业务需求的变化,协议可能需要对结构或字段进行改动,不同版本的协议对应的解析方法也是不同的。
  • 序列化算法:序列化算法字段表示数据发送方应该采用何种方法将请求的对象转化为二进制,以及如何再将二进制转化为对象,如 JSON、Hessian、Java 自带序列化等。
  • 报文类型:在不同的业务场景中,报文可能存在不同的类型。RPC 框架中有请求、响应、心跳等类型的报文。
  • 状态:状态字段用于标识请求是否正常(SUCCESS、FAIL)。
  • 消息ID:请求唯一ID,通过这个请求ID将响应关联起来,也可以通过请求ID做链路追踪。
  • 数据长度:标明数据的长度,用于判断是否是一个完整的数据包
  • 数据内容:请求体内容

编解码

编解码实现在 rpc-core 模块,在包 com.rrtv.rpc.core.codec下。

自定义编码器通过继承 netty 的 MessageToByteEncoder<MessageProtocol<T>>类实现消息编码。

自定义解码器通过继承 netty 的 ByteToMessageDecoder类实现消息解码。

解码时需要注意TCP粘包、拆包问题

什么是TCP粘包、拆包

[TCP 传输协议] 是面向流的,没有数据包界限,也就是说消息无边界。客户端向服务端发送数据时,可能将一个完整的报文拆分成多个小报文进行发送,也可能将多个报文合并成一个大的报文进行发送。因此就有了拆包和粘包。

在网络通信的过程中,每次可以发送的数据包大小是受多种因素限制的,如 MTU 传输单元大小、滑动窗口等。

所以如果一次传输的网络包数据大小超过传输单元大小,那么我们的数据可能会拆分为多个数据包发送出去。如果每次请求的网络包数据都很小,比如一共请求了 10000 次,TCP 并不会分别发送 10000 次。TCP采用的 Nagle(批量发送,主要用于解决频繁发送小数据包而带来的网络拥塞问题) 算法对此作出了优化。

所以,网络传输会出现这样:

tcp_package.png

  1. 服务端恰巧读到了两个完整的数据包 A 和 B,没有出现拆包/粘包问题;
  2. 服务端接收到 A 和 B 粘在一起的数据包,服务端需要解析出 A 和 B;
  3. 服务端收到完整的 A 和 B 的一部分数据包 B-1,服务端需要解析出完整的 A,并等待读取完整的 B 数据包;
  4. 服务端接收到 A 的一部分数据包 A-1,此时需要等待接收到完整的 A 数据包;
  5. 数据包 A 较大,服务端需要多次才可以接收完数据包 A。

如何解决TCP粘包、拆包问题

解决问题的根本手段:找出消息的边界:

  • 消息长度固定

每个数据报文都需要一个固定的长度。当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。当发送方的数据小于固定长度时,则需要空位补齐。

消息定长法使用非常简单,但是缺点也非常明显,无法很好设定固定长度的值,如果长度太大会造成字节浪费,长度太小又会影响消息传输,所以在一般情况下消息定长法不会被采用。

  • 特定分隔符

在每次发送报文的尾部加上特定分隔符,接收方就可以根据特殊分隔符进行消息拆分。分隔符的选择一定要避免和消息体中字符相同,以免冲突。否则可能出现错误的消息拆分。比较推荐的做法是将消息进行编码,例如 base64 编码,然后可以选择 64 个编码字符之外的字符作为特定分隔符

  • 消息长度 + 消息内容

消息长度 + 消息内容是项目开发中最常用的一种协议,接收方根据消息长度来读取消息内容。

本项目就是利用 “消息长度 + 消息内容” 方式解决TCP粘包、拆包问题的。所以在解码时要判断数据是否够长度读取,没有不够说明数据没有准备好,继续读取数据并解码,这里这种方式可以获取一个个完整的数据包。

序列化和反序列化

序列化和反序列化在 rpc-core 模块 com.rrtv.rpc.core.serialization 包下,提供了 HessianSerializationJsonSerialization 序列化。

默认使用 HessianSerialization 序列化。用户不可以自定义。

序列化性能:

  • 空间上

serialization_space.png

  • 时间上

serialization_time.png

网络传输,使用netty

netty 代码固定的,值得注意的是 handler 的顺序不能弄错,以服务端为例,编码是出站操作(可以放在入站后面),解码和收到响应都是入站操作,解码要在前面。

image.png

客户端 RPC 调用方式

成熟的 RPC 框架一般会提供四种调用方式,分别为同步 Sync、异步 Future、回调 Callback和单向 Oneway。

  • Sync 同步调用

客户端线程发起 RPC 调用后,当前线程会一直阻塞,直至服务端返回结果或者处理超时异常。

sync.png

  • Future 异步调用

客户端发起调用后不会再阻塞等待,而是拿到 RPC 框架返回的 Future 对象,调用结果会被服务端缓存,客户端自行决定后续何时获取返回结果。当客户端主动获取结果时,该过程是阻塞等待的

future.png

  • Callback 回调调用

客户端发起调用时,将 Callback 对象传递给 RPC 框架,无须同步等待返回结果,直接返回。当获取到服务端响应结果或者超时异常后,再执行用户注册的 Callback 回调

callback.png

  • Oneway 单向调用

客户端发起请求之后直接返回,忽略返回结果

oneway.png

这里使用的是第一种:客户端同步调用,其他的没有实现。逻辑在 RpcFuture 中,使用 [CountDownLatch] 实现阻塞等待(超时等待)

整体架构和流程

流程分为三块:服务提供者启动流程、服务消费者启动、调用过程

服务提供者启动
  1. 服务提供者 provider 会依赖 rpc-server-spring-boot-starter
  2. ProviderApplication 启动,根据springboot 自动装配机制,RpcServerAutoConfiguration 自动配置生效
  3. RpcServerProvider 是一个bean后置处理器,会发布服务,将服务元数据注册到ZK上
  4. RpcServerProvider.run 方法会开启一个 netty 服务
服务消费者启动
  1. 服务消费者 consumer 会依赖 rpc-client-spring-boot-starter
  2. ConsumerApplication 启动,根据springboot 自动装配机制,RpcClientAutoConfiguration 自动配置生效
  3. 将服务发现、[负载均衡] 、代理等bean加入IOC容器
  4. 后置处理器 RpcClientProcessor 会扫描 bean ,将被@RpcAutowired 修饰的属性动态赋值为代理对象
调用过程
  1. 服务消费者 发起请求http://localhost:9090/hello/world?name=hello
  2. 服务消费者 调用 helloWordService.sayHello()方法,会被代理到执行 ClientStubInvocationHandler.invoke()方法
  3. 服务消费者 通过ZK服务发现获取服务元数据,找不到报错404
  4. 服务消费者 自定义协议,封装请求头和请求体
  5. 服务消费者 通过自定义编码器 RpcEncoder 将消息编码
  6. 服务消费者 通过 服务发现获取到服务提供者的ip和端口, 通过Netty网络传输层发起调用
  7. 服务消费者 通过 RpcFuture 进入返回结果(超时)等待
  8. 服务提供者 收到消费者请求
  9. 服务提供者 将消息通过自定义解码器 RpcDecoder 解码
  10. 服务提供者 解码之后的数据发送到 RpcRequestHandler 中进行处理,通过反射调用执行服务端本地方法并获取结果
  11. 服务提供者 将执行的结果通过 编码器 RpcEncoder 将消息编码。(由于请求和响应的协议是一样,所以编码器和解码器可以用一套)
  12. 服务消费者 将消息通过自定义解码器 RpcDecoder 解码
  13. 服务消费者 通过RpcResponseHandler将消息写入 请求和响应 池中,并设置 RpcFuture 的响应结果
  14. 服务消费者 获取到结果

以上流程具体可以结合代码分析,代码后面会给出

环境搭建

  • 操作系统:Windows
  • 集成开发工具:[IntelliJ IDEA]
  • 项目技术栈:SpringBoot 2.5.2 + JDK 1.8 + Netty 4.1.42.Final
  • 项目依赖管理工具:Maven 4.0.0
  • 注册中心:Zookeeeper 3.7.0

项目测试

  • 启动 Zookeeper 服务器:bin/zkServer.cmd
  • 启动 provider 模块 ProviderApplication
  • 启动 consumer 模块 ConsumerApplication
  • 测试:浏览器输入 http://localhost:9090/hello/world?name=hello,成功返回 您好:hello, rpc 调用成功

项目代码地址

https://gitee.com/listen_w/rpc.git

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/wqs7QjdzikH96Gl1TK6knA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237271次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录