手写RPC-对RPC简单的理解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写RPC-对RPC简单的理解相关的知识,希望对你有一定的参考价值。
RPC简单的理解
其实RPC也是一种协议或者思想,在网络环境中,他需要基于某种“网络协议”,这种“网络协议”如果在OSI中,需要有支持 “传输层” 的功能,比如经典的TCP/IP或者是Dubbo新出的Triple协议等这些在网络中具有传输功能的协议做支撑;然后具有可以自己处理 编码,序列化或者说是按指定格式交换数据的功能,也可以扩展新字段传输,这是区别传统的传输层多出来的功能!!
RPC调用过程
Feign调用过程
设计一个高可用的RPC框架
- 注册中心:服务多了,我们需要有个东西去保存服务的元数据信息,比如redis、zookeeper、nacos等,都可以完成这个工作;
- 序列化和反序列话:这个就是去解决远程调用时传输数据的问题;这里的双向转换也是一个会存在性能相关的问题,可以选择protobuf之类的;
- 负载均衡问题:类似于ribbon这种负载均衡框架,我们也可以用一些算法,写自己的负载均衡策略;
- 传输框架:一般我们考虑的是基于OSI里面的传输层协议做选择,比如TCP/IP;当然也可以http,但http方式传输效率肯定没有基于TCP好,且http多了很多无用的报文数据;当然这里也有很多公司自研的一些传输层协议框架Triple等;
Tips:这里的话有个面试经常问的问题,就是你自己的协议怎么实现的?这里一般指的是“传输框架 + 加上自己定义的编解码”,也就是叫做“通信协议”;这里就比如基于http的,http也有自己的消息协议,什么header、cookie之类的; - 缓存:引入一个缓存也是有必要的,当服务已经注册。那我们其实可以将这些元数据信息缓存起来,这样找到对应的函数调用会更快;
- 异步调用:也就是请求过来不需要一直等待,而可以很快的去处理下一个请求,这里异步调用我们可以选择使用基于netty的NIO去实现;
- 健康检查:我们可以通过心跳得方式去做健康检查;
- SPI机制,可以引入序列化、压缩等自己的实现;
关于TCP粘包/拆包的问题
TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架、Netty等。如果你的简历中写了类似的技术或者你所面试的公司使用了相关的技术,被问到该面试的几率会非常高。
今天这篇文章就带大家详细了解一下TCP的粘包和拆包以及解决方案。
什么是粘包?
在学习粘包之前,先纠正一下读音,很多视频教程中将“粘”读作“nián”。经过调研,个人更倾向于读“zhān bāo”。
如果在百度百科上搜索“粘包”,对应的读音便是“zhān bāo”,语义解释为:网络技术术语。指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
TCP是面向字节流的协议,就是没有界限的一串数据,本没有“包”的概念,“粘包”和“拆包”一说是为了有助于形象地理解这两种现象。
为什么UDP没有粘包?
粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。
粘包拆包发生场景
因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。
如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。
如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。
关于粘包和拆包可以参考下图的几种情况:
上图中演示了以下几种情况:
- 正常的理想情况,两个包恰好满足TCP缓冲区的大小或达到TCP等待时长,分别发送两个包;
- 粘包:两个包较小,间隔时间短,发生粘包,合并成一个包发送;
- 拆包:一个包过大,超过缓存区大小,拆分成两个或多个包发送;
- 拆包和粘包:Packet1过大,进行了拆包处理,而拆出去的一部分又与Packet2进行粘包处理。
常见的解决方案
对于粘包和拆包问题,常见的解决方案有四种:
- 发送端将每个包都封装成固定的长度,比如100字节大小。如果不足100字节可通过补0或空等进行填充到指定长度;
- 发送端在每个包的末尾使用固定的分隔符,例如\\r\\n。如果发生拆包需等待多个包发送过来之后再找到其中的\\r\\n进行合并;例如,FTP协议;
- 将消息分为头部和消息体,头部中保存整个消息的长度,只有读取到足够长度的消息之后才算是读到了一个完整的消息;
- 通过自定义协议进行粘包和拆包的处理。
Netty对粘包和拆包问题的处理
Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器(Decoder)来解决粘包和拆包的问题。如:
- LineBasedFrameDecoder:以行为单位进行数据包的解码;
- DelimiterBasedFrameDecoder:以特殊的符号作为分隔来进行数据包的解码;
- FixedLengthFrameDecoder:以固定长度进行数据包的解码;
- LenghtFieldBasedFrameDecode:适用于消息头包含消息长度的协议(最常用);
基于Netty进行网络读写的程序,可以直接使用这些Decoder来完成数据包的解码。对于高并发、大流量的系统来说,每个数据包都不应该传输多余的数据(所以补齐的方式不可取),LenghtFieldBasedFrameDecode更适合这样的场景。
可以看看具体的代码:
public class MyChannelInitializer extends ChannelInitializer<SocketChannel>
@Override
protected void initChannel(SocketChannel channel)
/* 解码器 */
// 基于换行符号
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 基于指定字符串【换行符,这样功能等同于LineBasedFrameDecoder】
// e.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, false, Delimiters.lineDelimiter()));
// 基于最大长度
// e.pipeline().addLast(new FixedLengthFrameDecoder(4));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
//在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new MyServerHandler());
谢谢大家阅读!!!
公众号: 搜索关注,爱搞技术的吴同学 ,公众号上会经常写实用性的文章,谢谢关注!!回复:“加好友”,可获取我的微信二维码,欢迎加好友,一起学习!!!
大量源码: 欢迎star,可能会分享微服务实战,分页插件等;gitee
以上是关于手写RPC-对RPC简单的理解的主要内容,如果未能解决你的问题,请参考以下文章