微服务系列之远程通信

背景

后端开发三年有余,陆续接触使用了 springcloud,dubbo,thrift 三种RPC框架。

日常工作中,经常用来不同业务组间进行接口互调。然而只会用,不理解其中的实现原理,导致出了问题或者使用上想自定义扩展,不知从哪着手。所以这一次花时间学习一下RPC框架。  

何为RPC

RPC(remote Process Call)即远程服务调用,与本地方法调用比较,是通过跨进程通信来达到调用某个方法。进程间通信,需要通过网络请求。那如何网络传输,以及传输数据序列化,是首要研究的问题。

rpc为什么要用到序列化呢,因为网络传输过程中一直是二进制字节形式,而有状态的对象要在进程间进行传输,则必须进行序列化。  

例如以下业务场景:

public class A {
    //根据所用的rpc框架提供的引用服务注解
    // 例如 dubbo的@Reference
    private B b;
    public Entity test(Entity e){
        return b.doSth(e);
    }
}
复制代码

A 接口依赖另一个远程服务接口B,调用 test()方法时,实际会发送网络请求,等待返回结果。

image.png

  其中 A 发送请求时,带有path(B接口目录路径),B接口serviceName(接口名称信息),method(实际调用的方法名),参数等,组装成一个网络请求发送给远程服务。

远程服务接收到请求,执行相应逻辑,返回数据。看上面的代码,A 接口与B接口交互都是传递了一个类型为 Entity的实体类。

两个问题:

1 A -> B 服务接口通信选择什么网络通信方式

2 Entity 实体对象如何序列化,进行传输

网络协议传输

网络分层,按照 tcp/ip 模型分,通常分成五层

有应用层,传输层,网络层,链路层,和物理层

image.png

各层级的功能,以及之间如何协助,如下图所示

image.png

从图中可以看出,http是基于tcp实现的协议,单纯的tcp通信是没有http协议头的。 所以在网络上传输的请求,可以有下层没上层,网络传输请求不一定都是带有http头。

rpc框架设计时,有的框架是基于应用层http协议,直接进行网络通信。 例如springCloud,它的远程服务调用可以理解成发送了一个 http请求,它的参数序列化一般也是json格式的。SpringCloud 日常远程接口调试非常方便。

有的是基于传输层,例如tcp协议,自己实现通信。

简单RPC框架,基于 Tcp 传输伪代码:

//provider 服务

ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
//读取方法名
String methodName = input.read(); 
//参数类型
Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); 
 //参数
Object[] arguments = (Object[]) input.readObject();
Method method = service.getClass().getMethod(methodName, parameterTypes);  //找到方法
//方法调用
Object result = method.invoke(service, arguments); 
// 返回结果
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
  output.writeObject(result);


------------------------------------------------------------------------

//consumer 服务

//指定 调用的 provider 的 ip 和端口
Socket socket = new Socket(host, port);  
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
//传方法名
out.write(method.getName());  
//传参数类型
out.writeObject(method.getParameterTypes());  
//传参数值
out.writeObject(arguments);  
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());

//获取返回值
Object result = input.readObject(); 
return result;
复制代码

在传输层网络传输时,通常使用传统的 Socket 进行 通信。Socket 存在 I/O阻塞,线程模型缺陷以及内存拷贝等问题,有的rpc框架想要通信快且次数较多时,在这层通信协议上优化。如比较有名的 netty,对 Socket 通信编程做了很多方面的优化。dubbo 和 thrift 基于 tcp传输层,就用netty优化Socket通信来做RPC框架通信使用。

Netty 对 Socket 通信编程做了很多方面的优化,具体可以参考下这篇文章,time.geekbang.org/column/arti…

对象序列化

首先,什么是序列化

序列化:对象转换为字节序列的过程

反序列化:字节序列恢复为对象的过程

影响序列化性能的关键因素:序列化后的码流大小(网络宽带的占用),序列化的性能(CPU资源占用),是否支持跨语言。

序列化方式:

1 传统的 JDK 序列化

ObjectOutputStream 对象输出流,它的 writeObject()方法可以对对象进行序列化

ObjectInputStream 对象输入流,它的 readObject() 方法可以反序列化字节流,恢复一个对象。

通常使用时,传输对象实现 Serializable接口,传输过程中,把元信息和对象属性信息,序列化成字节流进行传输。

个人理解,二进制字节流传输,属于对象的裸传输,需要使用java语言中的对应的方法,才能解析,恢复成一个原类型且有原属性状态对象。

2 Json/Xml 序列化协议

{
"name":lily,
"age":"22"
}
复制代码

为什么会有这两种格式呢,我觉得首先,他们可读性很好,使用简单,不管什么语言都认识这。序列化过程通常是,一个对象先转换成 Json/Xml格式进行描述,然后序列化成字节流,再进行传输。

数据到达另一进程,可以直接使用,或者可以对它进行相应处理生成相应的对象。

但是XML 不能区分字符串和数字,JSON 不能区分整数和浮点数,如果要转成对象,需要后续程序逻辑来处理。

  个人理解,把对象的属性信息展开来,变成一种可读性高的文件格式,在网络上进行传输。

3 Hessian

二进制字节流传输,和传统的JDK序列化相似,只是更加优化对象二进制字节裸传输。

Hessian 专门为面向对象传输设计,它在序列化时,会有自描述注解等信息。相比传统的JDK序列化,序列化之后的数据字节数更小,还包含引用已经序列化的概念,即如果一个对象之前出现过,会直接插入一个R index这样的块来表示一个引用位置。

hessian 还是一个跨语言的序列化方式,这意味着序列化后的数据可以被其他语言使用,例如,java可以和python交互。

与JDK 序列化比较,优化实现,可参考文章 zhuanlan.zhihu.com/p/395262380

个人理解,优化传统的JDK序列化模式,网路传输直接传对象二进制字节流,hessian序列化支持多个语言,多个服务方接收到,可用hessian反序列获得对象。

4 Thrift 

thrift 支持丰富的数据类型,还支持定义类信息,用 struct表示,统一使用流转。

类定义:

struct User {
1: i32 id,
2: string name
}
复制代码

通过自身的IDL(接口描述语言) 中间语言进行编译,序列化传输

它们的使用还需要用到配套的编译工具,大家使用一个统一的数据传输格式进行传输。

一般要执行编译命令

thrift -r -gen java *.thrift
复制代码

个人理解,定义一个统一的类定义格式,大家统一使用这个类对象。

协议名词解释

之前看dubbo相关技术文档时,有些名词之前一直不理解,现在想想原来是为了方便,是把网络传输协议和序列化协议结合起来,定义的专业名词。  

1 dubbo 协议

dubbo框架默认走的协议,序列化用的是 hessian二进制序列化协议,网络协议基于tcp协议传输,用netty单一长链接,进行NIO异步传输。

场景:传输数据量小,消费者多于提供者,并发量大。长连接,建立连接后可以持续发送请求,无须再建立连接。

常规远程服务方法调用。

2 rmi协议

java提供的jDK标准序列化方式,网络传输阻塞式短链接,

3 hessian协议

hessian 服务本身就可以网络数据传输,使用的是http协议。基于hessian二进制序列化协议,用http协议网络传输,连接方式为短连接。适用场景,页面传输,文件传输,或与原生hessian服务互操作

4 http协议

传统页面url点击,数据传输,采用http协议网络传输,短连接,json格式序列化。

5 thrift 协议

thrift 服务本身就可以进行网路传输,thrift 服务里提供 netty,mina等通过tcp协议与各服务建立连接

总结

工作中,公司使用的rpc框架是架构组设计的,名为ditto。阅读设计文档,发现也围绕网络传输和对象序列化进行过技术选型。后续在日常功能需求迭代较快,单个传输协议不能满足的场景限制下,扩展为可支持自己配置协议,同一服务上也支持多种协议使用。

如今的分布式系统,服务间的通信尤为频繁且重要。想要深入了解RPC框架实现原理,服务间的通信原理和通信协议优化,是必定要知道的。

猜你喜欢

转载自juejin.im/post/7053017924092559374