分布式通信
分布式系统可以总结为是处于不同物理位置的多个进程组成的整体,为了确保这个整体有效并且高效的对外提供服务,每个节点之间都有可能需要进行通信来交换信息,而这个交换信息的过程多数使用的是 tcp 协议。
一、RPC
RPC 框架屏蔽了底层的网络传输,达到调用远程方法就像调用本地方法一样的效果
1.1. 组件
一个 RPC 框架总结下来需要包括 5 块核心内容:客户端请求程序、客户端 Stub、通信模块、服务端Stud、服务端响应程序等。
1.1.1. 客户端
请求程序
客户端请求程序会像调用本地方法一样调用客户端 Stub 程序,然后接收 Stub 程序返回的响应信息。
客户端存根(Client Stub)
服务器和客户端都包括 Stub 程序。在客户端,Stub 程序表现得就像本地程序一样,底层会将调用请求和参数序列化并通过通信模块发送给服务
器。之后 Stub 程序会等待服务器的响应信息,将响应信息反序列化并返回给请求程序。Client Stub 就是将客户端请求的参数、服务名称、服务地址进行打包,统一发送给 server 方。
服务调用过程中,真正的方法逻辑存在于服务端中,那么客户端保存就是服务端真实方法的一个存根(也可以认为是服务端的代理,存放服务端的地址等信息); 即当客户端需要远程访问服务端方法的时候, 就可以凭借服务端在客户端中的存根来组装发起远程调用所需要的信息。
序列化模块
序列化反序列化可以自己实现,也可以使用开源 Protobuf 之类序列化协议实现~
协议模块
协议模块主要就是规定了客户端传输的数据内容、服务端响应的内容、网络端点的信息,客户端传输的数据内容主要包括请求的信息(一个实体类,其中包括服务名、调用的具体函数名、参数类型、返回类型、版本号)、调用参数值。服务端响应的内容主要包括相应编码 (成功或失败)、服务端信息以及调用的服务端函数返回的数据
通信模块 Client
1.1.2. 服务端
响应程序
服务端会接收来自 Stub 程序的调用请求,执行对应的逻辑并返回执行结果。
服务端存根 (Server Stub)
服务端接收到Client发送的数据之后进行消息解包,调用本地方法。
通信模块 Server
1.1.3. 通信模块
客户端和服务端通过网络连接。所有的数据都需要通过网络传输,网络传输层需要把序列化后的字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行~
尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2~
基于 TCP 协议实现的 RPC
TCP 协议处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发数。
基于 HTTP 协议实现的 RPC
可以使用 JSON 和 XML 格式的请戏或响应数据
占用的字节数会比使用TCP协议传输所占用的HTTP协议是上层协议,发送包含字节数更高。
1.2. 实现
一次 RPC 调用流程概述~,业务代码发起 RPC 调用:
客户端
- 客户端需要调用某个远程方法的时候,首先获取对应服务的本地代理
- 客户端代理将请求的服务名、方法名、入参等构建成一个请求对象,同时加入一些通信必须的信息(如消息标识,协议版本等)封装成请求通信对象
- 序列化模块将封装的请求通信对象进行序列化,转化成可以在网络上传输的二进制数据
- 通过连接池组件拿到一个可用的连接 connection
- 通过连接 connection 将述序列化后的请求数据发送到远程服务器
服务端
- 服务端收到请求数据后,进行反序列化还原为通信请求对象
- 从请求通信对象中获取请求对象(服务名、方法名、方法参数),根据服务名获取服务实例,根据方法名和方法参数利用反射等机制获取服务实例的服务方法,将请求参数反射调用对应的方法,获取方法执行后的结果。
- 将执行结果封装成请求响应对象,进一步封装成通信响应对象
- 将通信响应对象进行序列化为二进制数据,通过网络返回给客户端
- 客户端将收到的响应数据进行反序列化为通信响应对象,进一步剥离出响应对象,将响应对象向上返回给客户端调用方
1.2.1. 基本实现
代理层
从使用角度来说,希望这款框架在使用的期间,对于远程方法调用,最好能够将其内部的细节进行封装屏蔽,使得使用起来就像本地方法调用一样方便好用,面对客户端的请求,可以设计一个代理层,统一将内部的细节都屏蔽起来,让调用者使用起来无感知~
路由层
客户端的请求会经过一个叫做路由层的部分,通过路由层内部的规则去匹配对应的 provider 服务~
协议层
client 端在使用 RPC 框架进行远程调用的时候,需要对数据信息进行统一的包装和组织,最终才能将其发送到目标机器并且被目标机器接收解析,因此对于数据的各种序列化、反序列化,协议的组装统一可以封装在协议层中进行实现。router 模块会负责计算好最终需要调用的服务提供者具体信息,然后将对应的地址信息、请求参数传输给到protocol层,最终由protocol层对数据封装为对应的协议体,然后进行序列化处理,最终通过网络发送给到目标机器。
1.2.2. 进阶
可插拔式组件设计与开发
从本地请求,到 protocol 层发送数据,整个链路中可能还需要考虑后续的一些二次扩展设计,例如某些自定义条件的过滤,服务分组等等,所以在设计的时候可以考虑在 proxy 和 router 之间加入一些链路模块。
容错层
- 超时重试
- 快速失败
- 无限重试
- …
…
1.3. 开源实现
1.3.1. Spark 通信架构实现
1.3.2. Flink 通信架构实现
1.3.3. Hadoop 通信架构实现
二、消息队列
发布订阅与消息队列最重要的区别是消息的传递的推拉方式不同,前者是消息中心主动推送消息到消费者,后者是只把消息放到消息队列中心里就结束了,由消费者自己去拉取消息。
对于消息队列模式,消息队列中心无需提前获取消费者信息,因此对消费者比较灵活,适合消费者为临时用户的场景;而发布订阅模式,需要消费者提前向消息中心订阅消息,也就是说消息中心需要提前获取消费者信息,比较适合消费者为长驻进程或服务的场景。
三、 WebService/Restful
如 SOA SOAP WSDL 等,一般使用 http 方式将请求数据和响应数据以 xml 格式进行传送,如下某天气预报 webservice 请求响应数据格式
- Restful 大部分是对外提供公共服务,更加容易理解,方便对外提供服务。
- RPC 更多的用于内部服务之间的调用。