协议概述
Dubbo3 提供了 Triple (Dubbo3) 和 Dubbo2 协议,它们是 Dubbo 框架的原生协议。此外,Dubbo3 还集成了许多第三方协议,并将它们纳入 Dubbo 的编程和服务治理体系中。包括 gRPC、Thrift、JsonRPC、Hessian2、REST 等。以下重点介绍 Triple 和 Dubbo2 协议。
协议描述
Triple 协议是 Dubbo3 推出的主协议。Triple 意味着第三代。经过 Dubbo1.0/Dubbo2.0 两代协议的演进以及云原生带来的技术标准化浪潮,全新的 Dubbo3 协议 Triple 应运而生。
选择 RPC 协议
协议是 RPC 的核心,它规定了网络中数据传输的内容和格式。除了必要的请求和响应数据外,它通常还包含额外的控制数据,例如单个请求的序列化方法、超时时间、压缩方法和身份验证信息。
协议的内容由三部分组成
- 数据交换格式:定义 RPC 请求和响应对象在网络传输中的字节流内容,也称为序列化模式
- 协议结构:定义字段列表、每个字段的语义以及不同字段的排列方式
- 协议通过定义规则、格式和语义来定义数据如何在网络中传输。成功的 RPC 要求通信双方都能够读取和写入网络字节流,并根据协议转换对象。如果双方无法就使用的协议达成一致,就会出现鸡同鸭讲的情况,无法满足远程通信的需求。
RPC 协议的设计需要考虑以下因素
- 通用性:统一的二进制格式,跨语言、跨平台、多传输层协议支持
- 可扩展性:协议添加字段、升级、支持用户扩展和额外的业务元数据
- 性能:越快越好
- 穿透性:可以被各种终端设备识别和转发:网关、代理服务器等。通常,通用性和高性能不能同时实现,协议设计者需要做出一定的权衡。
HTTP/1.1 协议
与直接构建在 TCP 传输层上的私有 RPC 协议相比,构建在 HTTP 上的远程调用解决方案将具有更好的通用性,例如 WebServices 或 REST 架构,使用 HTTP + JSON 可以说是事实上的标准解决方案。
选择构建在 HTTP 之上有两个最大的优势
- HTTP 的语义和可扩展性很好地满足了 RPC 调用的需求。
- 通用性,HTTP 协议几乎受到网络上所有设备的支持,并且具有良好的协议穿透性。
但也存在明显的问题
- 在典型的请求-响应模型中,一个链接上一次只能有一个等待的请求。会产生 HOL。
- 人类可读的标头,使用更通用、人类可读的标头传输格式,但性能要差得多
- 不支持直接服务器推送,需要使用轮询长轮询等变通方法
gRPC 协议
上面提到了在 HTTP 和 TCP 协议之上构建 RPC 协议的优缺点。与构建在 TCP 传输层上的 Dubbo 相比,Google 选择直接在 HTTP/2 协议之上定义 gRPC。gRPC 的优势继承自 HTTP2 和 Protobuf。
- 基于 HTTP2 的协议足够简单,用户学习成本低,并且天生具有服务器推送/多路复用/流控功能
- 基于 Protobuf 的多语言跨平台二进制兼容性,提供强大的统一跨语言能力
- 基于协议本身的生态相对丰富,k8s/etcd 等组件的天然支持协议,云原生的实际协议标准
但也存在一些问题
- 对服务治理的支持比较基础,更倾向于基本的 RPC 功能。协议层缺乏必要的统一定义,用户直接使用并不容易。
- 强绑定 protobuf 的序列化方法需要较高的学习成本和改造成本。对于现有的单语言用户来说,迁移成本不容忽视
最终选择 Triple 协议
最终,我们选择兼容 gRPC,并使用 HTTP2 作为传输层来构建一个新的协议,这就是 Triple。
容器化应用程序和微服务的兴起导致了针对工作负载内容优化的技术的发展。客户端中使用的传统通信协议(RESTFUL 或其他基于 HTTP 的自定义协议)难以满足应用程序在性能、可维护性、可扩展性和安全性方面的便利性要求。跨语言、模块化的协议将逐渐成为新的应用开发协议标准。自 2017 年 gRPC 协议成为 CNCF 项目以来,包括 k8s 和 etcd 在内的越来越多的基础设施和企业开始使用 gRPC 生态系统。作为云原生微服务框架,Dubbo 的新协议也与 gRPC 完全兼容。此外,Triple 还将增强和补充 gRPC 协议中一些不完善的部分。
那么,Triple 协议是否解决了我们上面提到的一系列问题呢?
- 在性能方面:Triple 协议采用元数据与有效负载分离的策略,这样网关等中间设备就可以避免对有效负载进行解析和反序列化,从而减少响应时间。
- 在路由支持方面,由于元数据支持用户添加自定义标头,用户可以更方便地根据标头进行集群划分或路由,从而在发布时,在灰度或灾难恢复切换方面拥有更大的灵活性。
- 在安全性方面,它支持双向 TLS 认证 (mTLS) 等加密传输功能。
- 在易用性方面,除了支持原生 gRPC 推荐的 Protobuf 序列化之外,Triple 还以通用的方式支持 Hessian/JSON 等其他序列化方式,使用户可以更方便地升级到 Triple 协议。对于原有的 Dubbo 服务,修改或添加 Triple 协议只需要在声明服务的代码块中添加一行协议配置,改造成本几乎为零。
Triple 协议
现状
- 与 grpc 完全兼容,客户端/服务器可以与原生 grpc 客户端连接
- 目前已通过大规模生产实践验证,达到生产水平
特性与优势
- 能够进行跨语言通信。传统的多种语言多 SDK 模式和 Mesh 跨语言模式都需要一种更通用、更可扩展的数据传输格式。
- 提供更完整的请求模型。除了请求/响应模型外,还应支持流式传输和双向传输。
- 易于扩展,渗透率高,包括但不限于对跟踪/监控等的支持,也应该被各级设备所识别,网关设施等可以识别数据包,对服务网格部署友好,降低用户理解难度。
- 多种序列化方法支持和平滑升级
- 支持 Java 用户无感知升级,无需定义繁琐的 IDL 文件,只需简单修改协议名称即可轻松升级到 Triple 协议
Triple 协议内容介绍
在 grpc 协议基础上的进一步扩展
- 服务版本 → “tri-service-version” {Dubbo 服务版本}
- 服务组 → “tri-service-group” {Dubbo 服务组}
- 跟踪 ID → “tri-trace-traceid” {跟踪 ID}
- 跟踪 RPC ID → “tri-trace-rpcid” {_span ID _}
- 集群信息 → “tri-unit-info” {集群信息}
其中,Service-Version 和 Service-Group 分别标识 Dubbo 服务的版本和分组信息,因为 grpc 的路径声明了服务名和方法名,相比 Dubbo 协议缺少版本和分组信息;Tracing-ID、Tracing-RPC-ID 用于全链路跟踪功能,分别代表跟踪 ID 和 span ID 信息;Cluster-Info 代表集群信息,可以用于构建一些与路由相关的灵活服务治理能力,例如集群划分。
Triple 流式传输
与传统的单向方法相比,Triple 协议目前提供了更多的流式 RPC 功能
- 流式传输用于什么场景?
在一些应用场景中,例如大文件传输和直播,消费者或提供者需要与对等方传输大量数据。由于这些情况下数据量非常大,无法在一个 RPC 包中传输,因此对于这些数据包,我们需要将数据包分片,并通过多次 RPC 调用进行传输。如果我们并行传输这些拆分的 RPC 数据包,那么到达对等端后的相关数据包是无序的,接收到的数据需要排序和拼接,相关的逻辑会非常复杂。但如果我们串行传输拆分的 RPC 包,相应的网络传输 RTT 和数据处理延迟会非常大。
为了解决上述问题,并以流水线的方式在消费者和提供者之间传输大量数据,流式 RPC 模型应运而生。
通过 Triple 协议的流式 RPC 方法,消费者和提供者之间将建立多个用户模式长连接(Stream)。同一个 TCP 连接上可以同时存在多个 Stream,每个 Stream 由一个 StreamId 标识,一个 Stream 上的数据包将按顺序读写。
总结
在 API 的世界里,最重要的趋势是标准化技术的兴起。Triple 协议是 Dubbo3 推出的主要协议,采用分层设计,其数据交换格式基于 Protobuf(Protocol Buffers)协议开发,具有出色的序列化/反序列化效率。当然,它也支持多种序列化方法和多种开发语言。在传输层协议方面,Triple 选择了 HTTP/2,与 HTTP/1.1 相比,其传输效率有了很大提高。此外,作为一种成熟的开放标准,HTTP/2 拥有丰富的安全和流量控制功能,并具有良好的互操作性。Triple 不仅可以用于服务器端服务调用,还支持浏览器、移动应用和物联网设备与后端服务之间的交互。同时,Triple 协议无缝支持 Dubbo3 的所有服务治理能力。
在云原生趋势下,跨平台、跨厂商、跨环境系统之间的互操作性需求必然催生基于开放标准的 RPC 技术,而 gRPC 顺应了历史潮流,得到了越来越广泛的应用。在微服务领域,Triple 协议的提出和实现是 Dubbo3 向云原生微服务迈进的一大步。
##Dubbo2
协议规范
魔数 - 魔数高位 & 魔数低位(16 位)
标识 Dubbo 协议,值为:0xdabb
请求/响应(1 位)
标识这是一个请求还是响应。请求 - 1;响应 - 0。
双向(1 位)
仅在请求/响应为 1(请求)时有用,期望从服务器返回一个值或不返回值。如果需要从服务器返回一个值,则设置为 1。
事件(1 位)
标识是否为事件消息,例如心跳事件。如果这是一个事件,则设置为 1。
序列化 ID(5 位)
标识序列化类型:fastjson 的值为 6。
状态(8 位)
仅在请求/响应为 0(响应)时有用,标识响应的状态
- 20 - 成功
- 30 - 客户端超时
- 31 - 服务器超时
- 40 - 错误请求
- 50 - 错误响应
- 60 - 服务未找到
- 70 - 服务错误
- 80 - 服务器错误
- 90 - 客户端错误
- 100 - 服务器线程池耗尽错误
请求 ID(64 位)
标识一个唯一的请求。数字(长整型)。
数据长度(32 位)
序列化后内容(可变部分)的长度,以字节为单位。数字(整数)。
可变部分
每个部分都是使用特定序列化类型序列化后的字节数组,由序列化 ID 标识。
每个部分都是使用特定序列化类型序列化后的字节数组,由序列化 ID 标识
如果内容是请求(请求/响应 = 1),则每个部分依次包含以下内容:
- Dubbo 版本
- 服务名称
- 服务版本 - 方法名称 - 方法参数类型 - 方法参数 - 附件
如果内容是响应(请求/响应 = 0),则每个部分依次包含以下内容:
- 返回值类型,标识从服务器端返回的值的类型:RESPONSE_NULL_VALUE - 2,RESPONSE_VALUE - 1,RESPONSE_WITH_EXCEPTION - 0。
- 返回值,从服务器返回的实际值。
对于(可变部分)可变长度部分,当当前版本的 Dubbo 框架使用 JSON 序列化时,会在内容的每个部分之间添加一个额外的换行符作为分隔符。请在可变部分的每个部分之后添加一个额外的换行符,例如
Dubbo version bytes (line break)
Service name bytes (newline)