Dubbo3单端口多协议源码分析

前言

Dubbo3 发布了基于 HTTP1、HTTP2、HTTP3 的 triple 通信协议,相较于之前的 dubbo 私有化协议,triple 协议在跨语言、云原生、服务治理方面有显著优势。

为了方便用户平滑迁移到 triple 协议,Dubbo3 新增了一项能力,支持在同一个端口上同时发布 dubbo 协议和 triple 协议的服务,做到协议迁移几乎没有额外负担。

实战

同时发布 dubbo 和 triple 协议的服务,只需增加配置:

yaml 复制代码
dubbo:
 protocol:
   name: dubbo
   ext-protocol: tri

或者设置 ProtocolConfig#extProtocol 属性

java 复制代码
ProtocolConfig protocolConfig = new ProtocolConfig("dubbo", 50000);
// 额外发布triple协议的服务
protocolConfig.setExtProtocol("tri");

下面是一个示例,在 50000 端口发布 EchoService 服务,同时支持 dubbo 和 triple 通信协议。

java 复制代码
public class Provider {
    public static void main(String[] args) {
        ServiceConfig<EchoService> serviceConfig = new ServiceConfig<>();
        serviceConfig.setInterface(EchoService.class);
        serviceConfig.setRef(new EchoServiceImpl());

        ApplicationConfig applicationConfig = new ApplicationConfig("app-provider");
        applicationConfig.setQosEnable(false);
        ProtocolConfig protocolConfig = new ProtocolConfig("dubbo", 50000);
        // 额外发布triple协议的服务
        protocolConfig.setExtProtocol("tri");
        DubboBootstrap.getInstance()
                .application(applicationConfig)
                .protocol(protocolConfig)
                .service(serviceConfig)
                .registry(new RegistryConfig("N/A"))
                .start().await();
    }
}

EchoService 接口

java 复制代码
@Mapping(path = "/")
public interface EchoService {

    @Mapping(path = "/echo", method = HttpMethods.GET)
    String echo(@Param("name") String name, @Param("age") int age);
}

public class EchoServiceImpl implements EchoService {

    @Override
    public String echo(String name, int age) {
        return "echo: {name: %s , age: %d}".formatted(name, age);
    }
}

启动消费者,直连dubbo://127.0.0.1:50000通过 dubbo 协议去调用服务是成功的。

同样,直接在浏览器访问http://127.0.0.1:50000/echo?name=lisa&age=18也是可以的。

因为基于 http 协议,所以 triple 协议的服务非常好调试,通用性也更好,现在你完全可以直接用 dubbo 来处理南北向流量,在这之前,你必须通过加入一个网关层,做 http 协议到 dubbo 协议的转换,非常麻烦。

源码分析

秉承着"知其然,知其所以然"的态度,通过源码了解 dubbo3 单端口多协议的实现原理。

按照以往的开发经验,一般都是一个端口只处理一种通信协议。以 nacos 为例,端口 8848 提供 http 协议的服务,端口 9848 提供 gRPC 协议的服务,客户端按需选择端口建立连接。单端口多协议的支持,确实比较少见。

熟悉 dubbo 的同学都知道,dubbo 会把服务封装成 ServiceConfig 对象,并通过其 export 方法导出。笔者顺着服务导出的链路,最终整理出以下流程图。

triple 协议对应的协议实现类是 TripleProtocol,dubbo 默认使用 netty 来处理网络数据传输。既然是服务导出,那么必然会创建 Netty 的 ServerBootstrap,根据调用链查找,果然在导出的过程中会创建 NettyPortUnificationServer,在它的构造函数里会调用其 doOpen 方法,最终创建 ServerBootstrap 绑定本地端口监听 RPC 请求。

ServerBootstrap 的 ChannelPipeline 是数据处理的管道,dubbo 怎么配置它是关键。

通过源码可以发现,这个叫negotiation-protocol的 Handler 就是通信协议协商的处理器。

NettyPortUnificationServerHandler 实现了 ByteToMessageDecoder 接口,所以它是一个解码器,当有数据可读时,会触发其 decode 方法,最终触发 detectProtocol 方法进行协议检测。

detectProtocol 流程:

  1. 遍历支持检测的协议名称,挨个检测
  2. 为了和 Protocol 区分,dubbo 新增了 WireProtocol 接口,它支持协议检测和配置协议处理器
  3. 标记读指针
  4. 调用 ProtocolDetector 组件进行协议检测,返回检测结果
  5. 重置读指针,因为这里只是检测协议,不能触发实际的数据读取,否则后面的 Handler 读到的数据不完整
  6. 如果协议检测通过,那么就调用 configServerProtocolHandler 方法配置Handler,同时移除自己,因为自己只是负责协议协商,协议一旦确认,自己这个 Handler 就没用了

协议协商是一部分,WireProtocol#configServerProtocolHandler 同样很关键。协议一旦确认,那么双方就要基于这个协议去通信了,WireProtocol 会根据检测的结果,根据协议来配置协议处理器 Handler。

配置协议处理器,本质上是配置两个东西:

  • 消息编解码器:字节流和请求响应对象的转换,比如把请求字节流转换成 HttpRequest
  • RPC 请求处理器:根据请求对象去查找服务 Invoker,调用服务并返回结果

以 TripleHttp2Protocol 为例,根据 Http 版本的不同,会分别针对 http1 和 http2 版本进行配置。又以 http1 为例,dubbo 会配置服务端编解码器 HttpServerCodec,和RPC请求处理器 NettyHttp1ConnectionHandler。

至此,我们知道。dubbo 服务导出会创建 ServerBootstrap 监听本地端口来处理请求,然后配置一个 NettyPortUnificationServerHandler 的解码器来进行协议协商,协议一旦确定,dubbo 就会针对这个连接针对性地配置协议的编解码器和RPC请求处理器,以此来实现单端口多协议。

但是,现在还有最后一个问题,协议是怎么检测的?

答案是:通过魔数(Magic Number)

在计算机领域,魔数是一个有特殊含义或用途的固定值。

比如通过魔数来区分文件格式,魔数通常位于文件的头几个字节:

  • PNG 文件:前8字节为 \x89PNG\r\n\x1a\n(十六进制)
  • JPEG 文件:前2字节为 0xFFD8
  • ELF 可执行文件:前4字节为 0x7F454C46(ASCII 对应 \x7FELF

同理,通信协议一般也有魔数。幸运的是,dubbo 协议和 triple 协议都有魔数,自然也就能直观地识别协议了。

dubbo 私有化协议,每个请求报文的前2个字节就是魔数,值是十六进制的0xdabb

所以,DubboDetector 识别 dubbo 协议很简单,就是比较前2个字节

triple 是基于 http 的,所以 triple 协议的识别,本质是 http 协议的识别。

http1 没有明确的魔数,但是有规律。请求报文是以 HttpMethod 开头的,而 HttpMethod 只有八个,最长的是OPTIONS7个字节。所以,http1 协议检测也很简单,读取前7个字节,判断是否属于 HttpMethod 中的任意一个即可。

http2 就更简单了,http 协议规定,为了避免对端不支持 http2,所以客户端在使用 http2 前,必须先发一个 **连接前言(Connection Preface),**如果对端支持该协议,客户端就继续用 http2 通信,否则回退到 http1。这个连接前言就相当于是 http2 底层的一个握手数据,也可以把它看作魔数。

连接前言是一个固定值,如下:

sql 复制代码
PRI * HTTP/2.0
[空行]
SM

所以,http2 的协议检测,就是判断报文是否以连接前言开头

尾巴

dubbo3 为了方便用户平滑升级到 triple 协议,支持在一个端口上同时发布 dubbo 协议和 triple 协议的服务。实现原理是通过配置一个特殊的解码器来进行协议协商,通过识别请求报文的魔数来检测协议,协议一旦确认,再针对性地配置协议处理器。协议处理器,主要包含 消息编解码器和RPC请求处理器两部分。

相关推荐
装不满的克莱因瓶4 天前
【Java架构师】各个微服务之间有哪些调用方式?
java·开发语言·微服务·架构·dubbo·restful·springcloud
他们叫我技术总监6 天前
从开发者视角深度评测:ModelEngine 与 AI 开发平台的技术博弈
java·人工智能·dubbo·智能体·modelengine
CodeLongBear6 天前
Day02计算机网络网络层学习总结:从协议到路由全解析
学习·计算机网络·dubbo
编啊编程啊程10 天前
【018】Dubbo3从0到1系列之时间轮流程图解
rpc·dubbo
编啊编程啊程10 天前
【020】Dubbo3从0到1系列之服务发现
rpc·dubbo
静止了所有花开11 天前
虚拟机ping不通百度的解决方法
dubbo
helloworld_工程师11 天前
Dubbo应用开发之FST序列化的使用
后端·dubbo
百度智能云技术站11 天前
百度亮相 SREcon25:搜索稳定背后的秘密,微服务雪崩故障防范
微服务·架构·dubbo
问道飞鱼14 天前
【微服务组件】Springboot结合Dubbo实现RPC调用
spring boot·微服务·rpc·dubbo
helloworld工程师17 天前
Dubbo应用开发之RPC直连开发
网络协议·rpc·dubbo