写点什么

聊聊 dubbo 协议

用户头像
捉虫大师
关注
发布于: 2021 年 05 月 21 日

本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎 star。

协议

协议通俗易懂地解释就是通信双方需要遵循的约定。


我们了解的常见的网络传输协议有 tcp、udp、http 等。再到我们常用的基础组件,一般来说 client 端与 server 端也有相应的协议,如 redis、mysql、zookeeper 等都是各自约定的私有协议,同样今天标题中的 dubbo 协议也是一种私有协议,他们都是应用层协议,基于 tcp 或 udp 设计。


通常应用层协议都是基于 tcp 和 udp,可靠传输通常使用 tcp,如大多数的基础组件,如 redis、mysql。只有能容忍丢失且需要很高的性能时使用 udp 协议,比如 metric 上报等场景。


这里介绍几种基于 tcp 的应用协议。

redis 协议

redis 协议足够简单,所以先介绍一下。redis 协议基于 tcp 设计,客户端和服务器发送的命令一律使用\r\n(CRLF)结尾。他的格式如下


*<参数数量> CRLF$<参数1 字节数量> CRLF<参数1的数据> CRLF...$<参数n 字节数量> CRLF<参数n的数据> CRLF
复制代码


举个例子,client 向 server 端发送命令 set mykey myvalue


*3 CRLF$3 CRLFSET CRLF$5 CRLFmykey CRLF$7 CRLFmyvalue CRLF
复制代码


也就是 *3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n


关于 redis 协议更详细信息可以看这个链接:


http://redisdoc.com/topic/protocol.html

http 协议

http 协议是我们最常见的协议,它的请求报文格式是由三部分组成:


  • 请求行:包括 method、url、version,由空格分隔,\r\n 结尾

  • 请求头:多行,每行是 key:value 的格式,以\r\n 结尾

  • 请求体:请求头与请求体直接由一个空白行分隔,请求体的长度在请求头中由content-length给出


redis 和 http 协议的处理方式截然不同。他们都是基于 tcp,而 tcp 协议传输的数据是流式的,通俗地说就是它就像水流,不断地发送字节,tcp 保证不重复,不丢包。而接收端要拿到想要的数据必须得从流式的数据中“判断出数据包的边界”,这就是 tcp 的粘包问题,解决它通常有三种方法:


  1. 发送固定长度的消息

  2. 使用特殊标记区分消息间隔

  3. 将消息的尺寸和消息一起发送


redis 协议使用了第 2 种,http 和接下来要介绍的 dubbo 协议使用了第 3 种,固定长度的消息比较理想,在实际中很少遇到。

dubbo 协议

由于 dubbo 支持的协议很多,本文提到的dubbo协议特指 dubbo 框架的默认协议,也就是 dubbo 的私有协议。它的格式如下:


  • 0-15: 魔数,判断是否是 dubbo 协议

  • 16: 判断是请求还是返回

  • 17: 判断是否期望返回

  • 18: 判断是否为事件消息,如心跳事件

  • 19-23: 序列化标志

  • 24-31: 标志响应状态(类似 http status)

  • 32-63: 请求 id

  • 64-95: 内容长度(字节)

  • 96-?: 序列化后的内容(换行符分隔)


常用的 attachments 在 dubbo 协议的哪里?

dubbo 的 attachments,我们通常将他类比为 http 协议的 header,可以携带一些隐式的参数信息(不用编码到请求对象中),如压测标志等。从他的类型


private Map<String, String> attachments;
复制代码


基本可以推断出 attachments 存在于 dubbo 协议的 96 字节之后的内容中,因为前面头的根本放不下这个 map。从 dubbo 的实现中可以看出,dubbo 的一个请求被封装为一个DecodeableRpcInvocation对象,里面包含了methodNameparameterTypesargumentsattachments等,将该对象序列化后填入 dubbo 协议的 96 字节后的内容中发送出去。


使用时,consumer 端:


RpcContext.getContext().setAttachment("hello", "from_consumer");
复制代码


provider 端:


RpcContext.getContext().getAttachment("hello");
复制代码


这里能看出 dubbo 协议相比较 http 协议来说设计的还是有所欠缺的,想要拿到一些隐式参数,或者想要知道请求发往哪里,必须得把请求体解析出来才可以,这也是 dubbo 协议往 mesh 方向发展的一个绊脚石。

dubbo 协议支持在返回值中带回 attachments 吗?

consumer 端向 provider 端发送请求可以在头部携带隐式参数,那么返回时也可以从 provider 端带回到 consumer 端吗?


比如 provider 回传给 consumer 它自身的处理耗时,consumer 计算出请求的响应时间,两者相减即可得到网络耗时,此时 provider 端最好是将耗时放在 attachments 中隐式地传回。


dubbo 的协议是请求和回复都是相同格式,理论上 consumer 可以带隐式参数到 provider 端,则 provider 端肯定也可以回传。


从 dubbo 的返回对象DecodeableRpcResult中可以看到是存在attachments的,但从实际的测试来看,2.7.x 版本是不支持的,但 2.6.x(>=2.6.3)版本是支持的。provider 端设置:


RpcContext.getServerContext().setAttachment("hello", "from_provider");
复制代码


consumer 端获取:


RpcContext.getServerContext().getAttachment("hello")
复制代码


github 上相关的 issue 链接如下:


https://github.com/apache/dubbo/pull/1843

协议和序列化有什么区别?

我们可能会经常听到这样的说法,dubbo 除了 dubbo 协议外还支持 rest、thrift、grpc 等协议,也支持 hessian、json 序列化。协议与序列化是什么关系?


通过刚刚介绍的 dubbo 协议格式或许就能明白,dubbo 协议是如上的格式包含了头和内容,其中 96 字节之后的内容是序列化后的内容,默认使用 hessian2 序列化,也可以修改为 fastjson、gson、jdk 等。只需要配置即可修改协议


<dubbo:protocol name="dubbo" serialization="fastjson"/>
复制代码


如果非要做个类比的话,就是你不仅可以通过 http 协议传输 json 格式的数据,也可以传输 xml 格式的数据。http 就是协议,json 和 xml 就是序列化。

最后

dubbo 协议的设计虽然有所欠缺,但依然不能阻止它成为 dubbo 使用最广泛的协议。




关于作者:专注后端的中间件开发,公众号"捉虫大师"作者,关注我,给你最纯粹的技术干货



发布于: 2021 年 05 月 21 日阅读数: 827
用户头像

捉虫大师

关注

还未添加个人签名 2018.09.19 加入

欢迎关注我的公众号“捉虫大师”

评论

发布
暂无评论
聊聊dubbo协议