[Java微服务架构]3_服务通信

28 阅读7分钟

引言

在前文➡️[Java微服务架构]2_服务通信之服务发现与服务注册理论了解了服务发现的基本情况后。

允许我继续来讲解服务通信的最后过程 —— 通信本身的简单概述。

通信方式

服务之间的通信存在同步、异步、事件等多种方式。 本篇先论述其中的同步通信方式,同步通信方式常见的有两种:REST与RPC。

同步通信——REST

REST与HTTP强绑定,其通过网络通信的应用层协议HTTP进行请求,标准化、通用性强。核心价值在于标准化、易用。 理想的REST风格的系统应该满足有6项原则:

  • 1.客户端与服务端分离(Client-Server) 将客户端与服务器端的职责严格分离,客户端负责用户交互,服务器负责数据存储和业务逻辑处理。 分离可以提升系统的可扩展性与可移植性,但是和微服务的划分一样,实际过程的执行参考“康威定律”。
  • 2.无状态(Stateless) 每个请求必须包含所有必要的信息,服务器不保存客户端的会话状态。 如:每次请求携带Token这样的设计。 无状态减少了服务器的工作,更适应微服务架构。
  • 3.可缓存(Cacheable) 响应中明确标识是否可缓存,客户端或中间代理可缓存响应以减少重复请求。 一般采用请求头形式。
  • 4.统一接口(Uniform Interface) 通过标准化接口约束资源的操作方式,确保客户端与服务器的交互简洁且无歧义。 如:GET请求获取资源,POST请求创建资源,请求响应包含状态与内容等元数据(status\date). 统一接口约束如同一个都遵循的第三方协议一样,标准化了服务通信。
  • 5.分层系统(Layered System) 客户端无法知道是否直接连接到终端服务器,系统可由多个层级组成(如负载均衡层、缓存层、认证层)。 解耦了互相通信的服务。
  • 6.按需编码(Code-on-Demand) 服务器可向客户端传输可执行代码(如JavaScript),动态扩展客户端功能。

微服务常用组件如RestTemplate、Feign本质都是使用的HTTP,且对外提供服务也一般遵循标准的RESTful API格式。

RESTful API典型请求响应格式如下:


成功
{
  "status": 200,
  "data": {
    "id": 123,
    "name": "Product A",
    "price": 99.99
  }
}

失败
{
  "status": 400,
  "error": "Invalid request",
  "message": "Missing required parameter 'email'"
}

不过我们也能看到不少服务是非RESTful格式的,毕竟有些时候功能优先于架构与标准。

同步通信——RPC

RPC,Remote Procedure Call 远程过程调用。 是指位于互不重合的内存地址空间中的两个程序,在语言层面上,以同步的方式使用带宽有限的信道来传输程序控制信息。

RPC三个基本问题

Q:跨服务调用带来的数据表示问题

服务在进行通信时,不同服务的数据表示格式不一致,RPC通过约定所有通信的服务将数据转换成约定的数据类型来表示、使用,从而解决数据的表示问题。 例如gRPC的Protocol Buffers序列化框架,通信时进行序列化与反序列化。

Q:如何传递数据

解决了需要传递的数据的表示问题后,如何传递数据呢? RPC任然是基于TCP、UCP等标准的传输层协议来完成的。

Q:表示方法签名

RPC通过语言无关的接口描述语言来找到不同服务需要调用的方法。 最开始最简单的做法就是给每个方法生成一个在任何机器上都绝不重复的编号,譬如UUID。

流行的Dubbo框架主要采用服务之间进行通信的方式便是RPC,如图所示,其并不强行绑定某一种通信协议。 ![[Pasted image 20250321142456.png]]

RPC与HTTP

这里需要注意的是,与REST强行绑定的HTTP协议不同,PRC虽然也被称为是协议,但是层级不同。 HTTP在OSI模型中是应用层,而PRC可以工作在传输层或更高层(具体取决于实现)。 RPC与其说是协议,更多的是一种模式,核心是围绕“三个基本问题”的追求高效、低延迟的通信模式。 即,HTTP是应用层协议,而RPC是远程调用的抽象模式。

RPC与REST

RPC发展方向有“朝着面向对象”、“朝着性能”、“朝着简化”这3个基本方向,目前网上Java微服务使用RPC常见的都是为了“朝着性能发展”。 RPC朝着性能发展的代表为gRPC,决定RPC性能的主要因素有两个:序列化效率和信息密度。 序列化输出结果的容量越小,速度越快,效率自然越高。信息密度则取决于协议中有效负载(Payload)所占总传输数据的比例大小,使用传输协议的层次越高,信息密度就越低。 那么以gPRC为例,在官网上可以看到官网的自述为:

为什么 gRPC 比 REST 更好/更差?
gRPC 在很大程度上遵循基于 HTTP/2 的 HTTP 语义,但我们明确允许全双工流。我们偏离了典型的 REST 约定,因为我们在调用调度期间使用静态路径来提高性能,因为从路径、查询参数和有效负载主体解析调用参数会增加延迟和复杂性。我们还正式化了一组我们认为比 HTTP 状态代码更直接适用于 API 用例的错误。

…… 解释一下就是基于HTTP/2,支持多路复用(Multiplexing)、头部压缩(HPACK)、二进制传输,显著降低延迟并提高吞吐量。 采用二进制序列化(Protocol Buffers),体积更小、相比 JSON/XML其序列化/反序列化更快。 流式通信,支持客户端/服务端/双向流(Streaming),适用于实时数据推送或大规模数据传输(如日志流、实时监控)。 劣势就是更复杂。

经典RPC协议-gRPC

gRPC官网文档 ⬆️官网有gRPC的各概念。

包括gRPC在内的众多RPC,都遵循“定义服务思想”,这是一种契约优先、接口驱动的设计思想,核心如下:

  • 前置定义:通过 IDL 明确服务接口、方法、参数和返回类型。 gRPC 可以使用 Protocol Buffers 作为其接口定义语言 (IDL) 和其底层消息交换格式。 明确IDL可以帮助解决“三个核心问题”中的“表示方法签名”,一般IDL包括服务对外暴露的方法、参数类型、返回类型以及可能的异常。通过gRPC调用服务的客户端会自动存储服务的存根Stub。 使用 Protocol Buffers也可以解决“三个核心问题”中的数据表示问题。
  • 代码生成:基于定义自动生成客户端和服务端通信代码,简化开发。
  • 强一致性:确保客户端与服务端的数据格式和通信协议严格一致。
  • 灵活性扩展:支持多种通信模式(单向、流式)和跨语言协作。

![[Pasted image 20250321145617.png]]

gRPC为什么快?

这个快是相较于使用REST强行绑定的HTTP而言的,作为追求性能的RPC,gPRC也在序列化效率和信息密度做了设计。采用二进制序列化(Protocol Buffers),基于HTTP/2协议,与采用流式模型和强类型结构优化端到端处理。 协议层:HTTP/2 的多路复用和头部压缩减少网络开销。 数据层:Protobuf 的高效二进制编码降低序列化成本和传输体积。 架构层:流式模型和强类型接口优化端到端处理效率。

下面为Dubbo内置的Triple通信协议。 官网文档

拓展阅读

通过网络进行分布式运算的八宗罪(8 Fallacies of Distributed Computing)。

1)The network is reliable.——网络是可靠的。

2)Latency is zero.——延迟是不存在的。

3)Bandwidth is infinite.——带宽是无限的。

4)The network is secure.——网络是安全的。

5)Topology doesn’t change.——拓扑结构是一成不变的。

6)There is one administrator.——总会有一个管理员。

7)Transport cost is zero.——不必考虑传输成本。

8)The network is homogeneous.——网络都是同质化的。

资料引用

1.《凤凰架构:构建可靠的大型分布式系统》