RPC实战与核心原理笔记<二>
1、通信协议
提到协议,可能最熟悉不过的就是HTTP,HTTPS协议,TCP,UDP协议等等。那么什么是协议呢?简单而言协议就是一种规定,就像咱们的语言一样,要有一个统一的标准,不然是无法通信的,不能互相理解。为了避免provider与consumer之间鸡同鸭讲,因此需要设立一个标准,二者都遵守的标准,这样才可以进行通信。
1.1、协议的作用
只有二进制数据才能在网络中进行传播,应用程序将数据写到socket缓冲区后,通过网卡发送到网络中,由于缓冲区和滑动窗口的存在,可能将一个包分成好几个小包进行发送,也有可能将几个小包合成一个大包进行发送,至于如何合并或者拆分,这个是程序员无法干预的。
而接收方读到一批字节6流,就如我们拿到一篇没有标点符号的文章一样,无法判断有效数据从哪里开始,从哪里结束。因此为了准确的“断句“,引入了协议。协议就是将字节流按照一定的格式组织在一起。
1.2、如何设计良好的协议
协议一般比较简单,但是如果需要设计出一个扩展性强,字段紧凑,冗余字段少的协议还是比较困难的。
简单而言一般有几个原则:
-
协议一般由消息头和消息体组成
-
能用一个byte表示就不用用一个short
-
不用反序列化body就能做的东西就不要放到body中,例如判断是否超时,超时时间放在消息头中,如果超时了直接丢弃这个请求,返回一个timeout的response,压根不用反序列化。鉴权,限流的思想也是一样的,但是对于限流或者鉴权这样的粒度可能有点粗。
这样便设计出一个比较好的协议。扩展性强,格式紧凑。在扩展字段中,可以设置请求的超时时间,请求携带的token,各种字符串,或者byte,int,类型的信息。
2、序列化技术
序列化是将对象变成字节流的一个过程,将对象的类型,属性值,属性类型按照固定的格式写到写成一个二进制的字节流,而且这个过程必须是可逆的。序列化是一次RPC调用中除了业务处理应该是最耗时的步骤了。因此一个好的快速的,兼容性强,可以跨语言的序列化技术是非常重要的。
其中兼容性应该是非常重要的,因为总有业务方反馈,因为服务方进行了jar包升级,添加了字段什么的,导致序列化的时候报错等等一些问题。当然速度和生成的码流的大小也是很重要的。
另外在设计接口参数的时候,尽量简单,对象不要有复杂的继承关系,最好提供无参构造方法,对象之间的组合关系不要过于复杂,避免集合之间的嵌套。
3、网络IO模型
网络通信是整个RPC过程中的基础,这里主要使用Netty的react模式,其中比较重要的就是千万不要占用netty的work线程,向序列化和反序列化这样的逻辑不要让netty的work线程去做,另外为了实现服务隔离,可以定制化重要的服务有一个单独的业务线程池,这样就实现了“要挂你先挂”,即使其它接口的请求出现了问题,核心接口依然可以得到处理。
4、RPC自身的可扩展性
提起RPC自然离不开服务治理,RPC仅是通信的那块,一个完善的RPC框架必然要有很强的扩展性。
- 扩展路由策略
- 扩展负载均衡算法
- 扩展序列化方式
- 扩展服务端线程池类型
- …
只有具备很好的扩展性,才是一个完备的RPC框架。
这种东西一般通过SPI或者责任链模式,让用户可以认为的添加一些自己额为的逻辑,来实现一定的扩展机制。