RPC (Remote Procedur call ) 远程服务调用
什么是rpc?
举个小河上的桥例子.
rpc的作用?
- 屏蔽远程调用和本地的区别,让我们感觉像本地调用
- 隐藏底层网络通信的复杂性
联想网络通信本质是啥?
netty 网络通信的框架
一个完整的rpc会涉及到哪些过程?
数据传输,保证其可靠性通常都是TCP来传输,常用的http协议就是在其基础之上
网络数据肯定是二进制数据,那么就会涉及到序列化和反序列化. 这里也会产生数据通信之间的协议.
数据格式的约定内容叫协议.协议包括请求头和请求体.
请求头: 一般用于身份识别、协议标识、数据大小、请求类型、序列化类型
消息体 : 请求业务参数信息和扩展信息
协议
http 协议和rpc 协议都属于应用层协议.
Rpc 数据请求通过网络,二进制格式传输,写入本地socket 通过网卡发到另外的设备上.
数据也不是一次把数据全部传递过去,中间可能需要拆包,合并请求等(合并的前提是同一个tcp连接上的数据)
这些取决于tcp的窗口大小,系统参数配置.
协议 也是给请求一个参考边界. 例如数据大小,结尾格式等.可以联想到redis 的resp协议使用#key#value 这样的分割方式.
为啥还要rpc自己的协议? http协议不能满足要求吗? 他们有什么样的区别?
http协议属于无状态协议,而且协议本身有很多无用的内容,比如换行符、回车符、请求头导致数据多.
无法实现请求跟响应关联,每次都需要重新建立连接,响应完成关闭连接.
rpc 是如果和实现请求和响应关联的呢?
dubbo 是消费者发出请求,Atomiclong 产生消息id,,之后底层IO是异步发送消息,dubbo发送请求后,需要阻塞等待消费者返回消息.消费者id存储在map结构里,服务提供者返回附带请求消息id,之后dubbo 通过消息id就能对应上请求和响应啦.
怎么完成自定义的协议呢?
协议的边界
给一个固定大小的长度表示数据长度,之后在根据数据的大小进行读取数据.
数据的序列化方式
如果不指定序列化方式的话,那么也解析不出来数据的格式啊?
得出结论,数据大小,序列化的方式这些 固定下来的数据空间,我们可以放到协议头上,具体的请求内容放置在协议体.
bit offset | 0-15 | 16-47 | 48-63 | 64-71 | 72-79 | 80-87 |
---|---|---|---|---|---|---|
0 | 魔术位 | 数据长度 | 消息id 请求和响应关联 | 协议版本 | 消息类型 | 序列化方式 |
协议体 | 请求接口 | 请求参数 | 可变长度 |
可扩展的协议?
RPC序列化 方式
protobuf —》protostuff 不支持null ,不支持单纯的Map List 对象集合需要放在对象里.
kryo、hessian
序列化考虑参考图:
序列化注意事项:
- 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚;
- 入参对象与返回值对象体积不要太大,更不要传太大的集合;
- 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类;
- 对象不要有复杂的继承关系,最好不要有父子类的情况。
零拷贝传输数据
通常是程序发起写–〉应用缓存区(cpu拷贝)–〉内核缓冲区– (依靠DMA) –〉网卡–〉其他设备
每一次都需要把数据写到用户空间缓冲区 之后到系统内核的缓冲区 ,这样每次都需要2次的写.
所谓的零拷贝 就是直接写入内核,之后从内核直接读取数据…就是把数据用户空间和内核空间都将数据写入到同一个地方. 虚拟内存的方式?
二种解决方式:
- mmap+write
- senfile
还要深入了解下以后.netty的零copy-zero 是怎么做的呢?
todo 后续要进行进一步了解
netty是完全站在了用户空间上,也就是jvm上.偏向数据操作的优化 - netty 提供了compositeByteBuf 类 ,可以将多个byteBuf 合并为逻辑上的byteBuf,避免byteBuf间copy.
- byteBuf 支持slice 操作,可以分解为多个共享同一存储区域的byteBuf.
- 通过wrap 操作,可以将 byte[] 数组,byteBuf,byteBuffer 等包装成netty 自己的 byteBuf对象
netty 解决 Tcp 沾包,拆包 都是靠compositeByteBuf 类 wrap 和 slice 去解决的.
Netty 可以采用Direct Buffers 堆外内存的方式解决,与虚拟内存一样的效果.
还提供了FileRegion 中包装NIO 的 FileChannel.trasnsferTo() 方法实现了零拷贝,这和linux 中的sendfile 原理一样.
扩展阅读 c10k 问题
https://www.jianshu.com/p/ba7fa25d3590
https://blog.csdn.net/lsgqjh/article/details/86622532
- selector 模式
使用fd_set 结构告诉内核监听哪些文件句柄,之后轮询逐个排查状态,句柄是有上限的,逐个检查吞吐量低, 每次调用都重复初始化fd_set . - poll 模式
主要解决了selector 模式2缺点,一个句柄上线的问题(链表方式存储)以及重复初始化的问题. (不同字段标注关注事件和发生事件) - epoll 模式
event-callback 方式. 仅对发生变化的文件句柄感兴趣. 通过epoll_ctl 注册文件描述符fd , 一旦fd 就绪,内核就会采用callback激活fd ,epoll_wait 收到通知,通知应用程序.而且epoll一个文件描述符管理多个描述符,将用户进程的文件描述符事件存放在内核一个事件表里. 这样数据从内核一次到用户进程地址空间.epoll 依赖 linux 系统,需要开启epoll .本文欢迎转载,但是希望注明出处并给出原文链接。
如果你有任何疑问,欢迎在下方评论区留言,我会尽快答复。
如果你喜欢或者不喜欢这篇文章,欢迎你发邮件到 alonecong@126.com 告诉我你的想法,你的建议对我非常重要。本文作者: 不利索的阿瓜
联系方式: alonecong@126.com
版权声明: 除特别声明外,所有文章均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!