如果我们自己写rpc框架需要考虑什么/怎么实现 (一)

852 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

目标 想获得一次 创作先锋奖 希望同志们 支持下

先说目标

用两篇文档 和大家一起 思考/整理 如果我们自己写rpc框架需要考虑什么/怎么实现

会讲解的内容

  1. 协议 必须 可扩展且向后兼容 怎么实现呢?

  2. 调用过程中超时了怎么处理业务?

  3. 什么场景下最适合使用 RPC?

  4. 什么时候才需要考虑开启压缩?

  5. 接口是契约,需要接口设计者做向下兼容

  6. 流控,错误处理

  7. 最难的问题:分布式事务处理

  8. RPC 里面,我们是怎么实现请求跟响应关联的呢?

  9. 既然基于TCP优于HTTP,gRPC为什么选择基于HTTP2?
    grpc基于http2,更容易跨语言支持。

RPC 使用过程中需要注意哪些问题?

  1. 下游服务的服务能力,避免因为你的调用把别人给调挂了,要事前协商好qps等,做好限流
  2. 调用服务异常时,要考虑降级、重试等措施
  3. 核心的服务不能强依赖非核心的服务,避免核心服务因为非核心服务异常而不可用

为什么写这个系列呢?

今天早上刚起床看到一个公众号 推送的 《RPC 实战与核心原理》 , 突然感觉 "哎 我好像还真没整理过rpc的知识,搞,我先看看需要多长时间,不能影响我睡觉啊。。。。嗯 一天时间够了 搞起"

名词解释

  1. RPC(Remote Procedure Call,远程过程调用)
  2. HTTP(HyperText Transfer Protocol 超文本传输协议)

RPC 主要是基于 TCP/IP 协议的,而 HTTP 服务主要是基于 HTTP 协议的,我们都知道 HTTP 协议是在传输层协议 TCP 之上的。

RPC 服务

RPC 架构里的四个核心的组件:

  1. 客户端(Client),服务的调用方。
  2. 服务端(Server),真正的服务提供者。
  3. 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  4. 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

支持同步调用异步调用

HTTP 服务

即:RESTful 风格的服务接口。优点就是简单、直接、开发方便。利用现成的 http 协议进行传输。

接口可能返回一个 JSON 字符串或者是 XML 文档。然后客户端再去处理这个返回的信息,从而可以比较快速地进行开发。

自己思考下 rpc 需要实现什么功能

简单想 不就是把服务A 的body 序列化 通过协议 传输给 服务B ,服务B处理完之后 ,再进行返回给服务A

好,那我们就从这个最简单的需求 实现

比较httprpc
传输协议基于HTTP协议可以基于TCP协议,也可以基于HTTP协议
传输效率请求中会包含很多无用的内容减少报文体积,提高传输效率
性能消耗大部分是通过json实现的,字节大小和序列化耗时都比thrift要更消耗性能可以基于thrift实现高效的二进制传输
性能消耗需要配置Nginx,HAProxy实现 feign也可以自带了负载均衡策略

RPC主要用于公司内部服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器调用,APP接口调用,第三方接口调用等等。

RPC和HTTP都可以用于实现远程过程调用,如何选择 从速度上看,RPC比HTTP更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩 从难度上看,RPC实现较为复杂,http相对简单 从灵活性上看,HTTP更胜一筹,因为它不关心实现细节,跨平台,跨语言

RPC 的作用就是体现在这样两个方面

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

你是不是以为到这就结束了?

并没有 上面这个 百度就可以搜索到,但是 http 和rpc 到底性能差多少,多余的body 能有多大,都是什么呢? 对不起 这个写在后面的文章中 哈哈 写作中

RPC的通信流程

image.png

  1. Bit Offset——标识协议的其实位置
  2. 魔术位——标识是什么协议
  3. 整体长度——标识整个协议有多长,减去协议头长度就是协议体长度
  4. 头长度——标识协议头的长度,因为头是可扩展的,所以具体长度不固定,需要标识一下
  5. 协议版本——标识当前协议的版本,用于协议兼容性控制
  6. 消息类型——标识消息的类型,对于文本的需要,这里也需要嘛?协议类型可能是对象?可能是XML文件?可能是JSON码?正常来讲应该都是对象才对,让用于反序列化,猜测是为了扩展预留的
  7. 序列化方式——用于消息的序列化和反序列化
  8. 消息ID——用于表示请求和响应的关系
  9. 协议头扩展字段——用于扩展协议头,是协议具有扩展性,更加的灵活可控
  10. 协议体——协议的内容,一堆堆的二进制数据,双方沟通的东西
  11. 协议头——规定信息转换的规则
  12. 协议体——信息真正的内容,由于在传输层对人不友好对应用程序也不友好需要转换一下

RPC 框架中如何去选择序列化协议,我们有这样几个很重要的参考因素,优先级从高到低

  1. 安全性
  2. 通用性
  3. 兼容性
  4. 性能
  5. 效率
  6. 空间开销

序列化

序列化 选型

标题JDK 原生序列化JSONHessianProtobuf
问题JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserializer 类修复;Locale 类,可以通过扩展 ContextSerializerFactory 类修复;Byte/Short 反序列化的时候变成 Integer对于具有反射和动态能力的语言来说,用起来很费劲
优点方便理解更加高效,生成的字节数更小,有非常好的兼容性和稳定性序列化后体积相比 JSON、Hessian 小很多;IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;序列化反序列化速度很快,不需要通过反射获取类型;消息格式升级和兼容性不错,可以做到向后兼容。
应用场景数据量较小可以使用大部分情况使用这种

JDk 原生序列化

image.png

下节更新内容

  1. 网络IO模型
  2. 动态代理
  3. rpc demo (之后的东西都在这个demo 基础上添加,代码放到码云上)
  4. 单机性能怎么 得到最大的提升

之后还愿的 (新开小节 说这个)

  1. http rpc 性能 等参数的 数据比较