Dubbo-聊聊Dubbo协议

article/2025/11/7 19:23:22

前言

Dubbo源码阅读分享系列文章,欢迎大家关注点赞

SPI实现部分

  1. Dubbo-SPI机制

  2. Dubbo-Adaptive实现原理

  3. Dubbo-Activate实现原理

  4. Dubbo SPI-Wrapper

注册中心

  1. Dubbo-聊聊注册中心的设计

  2. Dubbo-时间轮设计

通信

  1. Dubbo-聊聊通信模块设计

什么是协议

在网络交互中是以字节流的形式传递的,对于字节流都是二进制格式,这样我们就面临一个问题就是如何转化为我们可以识别的字符,协议就是来解决这个问题的,协议用通俗易懂地解释就是通信双方需要遵循的约定。 在日常开发中,我们常见的网络传输协议有TCP、UDP、HTTP。常用的中间件也会定义对应的协议,如Redis、Mysql、Zookeeper等都有自己约定的协议,同样Dubbo的通信也采用一种协议,这些都是应用层协议,都是基于TCP或者UDP设计的。

如何定义协议

应用层协议一般的形式有三种:定长协议、特殊结束符和变长协议,聊到这里就可以抛出来一个常见的面试题,如何解决网络通信粘包和拆包的问题?该问题的解决方案也就是通过约定协议,下面我们就来聊聊这三种模式优缺点以及使用场景。

定长协议

定长的协议是指协议内容的长度是固定的,比如协议byte长度是50,当从网络上读取50个byte后,就进行decode解码操作。

优点

定长协议在读取或者写入时,效率比较高,因为数据大小都是确定的。

缺点

定长协议的缺点在于适应性不足,网络传输中传输的内容的大小不可能都是相同的,因此对于一些长度不够的消息,明显过于的浪费带宽。

特殊结束符

特殊结束符就是在每次传输结束的时候使用一个特殊的结束符,在Redis中的协议采用了特殊结束符,客户端和服务器发送的命令一律使用\r\n(CRLF)结尾。

优点

与定长协议一样读取或者写入时,效率比较高,同时解决定长协议的尴尬。

缺点

特殊结束符方式的问题是必须要有一个完整的消息体才能进行传输,除此之外必须要防止用户传输的数据不能同结束符相同,否则就会出现紊乱。

变长协议

变长协议由定长以及不定长两部分组成,定长部分一般是协议头,此部分会包含变长部分的描述,变长协议我们经常使用的HTTP协议采用变长协议,HTTP请求报文格式是由三部分组成:

  1. 请求行:包括Url、Version等,由空格分隔,\r\n结尾;

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

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

优点

灵活性比较高,解决了定长协议以及特殊结束符的所有缺点。

缺点

复杂性比较高,需要自定义一套标准,所有消息都需要按照该格式发送以及解析。

Dubbo协议

Dubbo框架支持很多协议,默认采用Dubbo协议,Dubbo协议采用的是变长协议的设计,整体的格式如下:

  1. 0~7位和8~15位分别是Magic High和Magic Low,是固定魔数值(0xdabb),我们可以通过这两个Byte,判断是否为Dubbo协议;

  2. 16位是Req/Res标识,用于标识当前消息是请求还是响应;

  3. 17位是2Way标识,用于标识当前消息是单向还是双向,如果需要来自服务器的返回值,则设置为1;

  4. 18位是Event标识,用于标识当前消息是否为事件消息;

  5. 19~23位是序列化类型的标志,用于标识当前消息使用哪一种序列化算法;

  6. 24~31位是Status状态,用于记录响应的状态,当Req/Res为0时才有用;

  7. 32~95位是Request ID,用于记录请求的唯一标识;

  8. 96~127位是序列化后的内容长度,该值是按字节计算;

  9. 128位之后是可变的数据,被特定的序列化类型序列化后,每个部分都是一个 byte [] 或者byte,如果是请求包,则每个部分依次为:Dubbo version、Service name、Service version、Method name、Method parameter types、Method arguments 和 Attachments。如果是响应包,则每个部分依次为:返回值类型、返回值;

image.png

优点

Dubbo协议整体设计比较简洁,能采用1个bit表示的,不会用一个byte来表示;此外请求头和响应头一致,整体采用一套解析标准就可以,代码实现起来相对简单。

缺点

由于整体的设计相对简洁,导致扩展性不够;

Dubbo协议是如何解析的

在通信篇中我们讲过Codec2该接口,该接口提供了encode和decode个方法来实现消息与字节流之间的相互转换,关于该接口的实现我们没有讲解,这里我们来看看此部分和Dubbo协议有什么关系。

AbstractCodec抽象类没有实现Codec2中定义的接口方法,而是提供了几个给子类用的基础方法。

  1. getSerialization方法:通过SPI获取当前使用的序列化方式;

  2. checkPayload方法:检查编解码数据的长度,如果数据超长,会抛出异常;

  3. isClientSide、isServerSide方法:判断当前是Client端还是Server端;

接下来我们就来聊聊子类如何被解析的,我们可以看到四个子类的继承关系,重点介绍的是ExchangeCodec和DubboCodec,其他就是做一下简单介绍。 TransportCodec该类已经被标注为弃用,该类内部也就是根据getSerialization方法选择的序列化方法,对传入消息或ChannelBuffer进行序列化或反序列化。 TelnetCodec继承了TransportCodec的能力,该类主要是提供了对Telnet命令处理的能力,该功能主要是对服务进行治理的功能,这里后续我们画一点时间来进行介绍。

ExchangeCodec

ExchangeCodec继承了TelnetCodec,在该类基础上增加Dubbo协议头的处理能力,接下来我们首先来看下其核心字段,

//协议头长度
protected static final int HEADER_LENGTH = 16;
//魔数 判断是否是Dubbo协议
protected static final short MAGIC = (short) 0xdabb;
protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
//设置请求响应标志位
protected static final byte FLAG_REQUEST = (byte) 0x80;
//单向还是双向标志位
protected static final byte FLAG_TWOWAY = (byte) 0x40;
//是否事件消息标志位
protected static final byte FLAG_EVENT = (byte) 0x20;
//序列化协议标志位
protected static final int SERIALIZATION_MASK = 0x1f;

通过核心字段我们可以发现其实和我们介绍的Dubbo的协议是一致的,因此接下来的encode和decode就是对Dubbo协议头的解密和编码,我们来下看encode方法,在encode方法中会根据需要编码的消息类型进行分类, 分为三类:Request、Response、telenet,encodeRequest方法专门对Request对象进行编码,encodeResponse方法对Response对象进行编码。

@Overridepublic void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {//Requestif (msg instanceof Request) {encodeRequest(channel, buffer, (Request) msg);//Response} else if (msg instanceof Response) {encodeResponse(channel, buffer, (Response) msg);} else {//telenetsuper.encode(channel, buffer, msg);}}
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {Serialization serialization = getSerialization(channel, req);//存储协议头byte[] header = new byte[HEADER_LENGTH];// set magic number.Bytes.short2bytes(MAGIC, header);//设置协议头标志位header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());if (req.isTwoWay()) {header[2] |= FLAG_TWOWAY;}if (req.isEvent()) {header[2] |= FLAG_EVENT;}//记录请求IDBytes.long2bytes(req.getId(), header, 4);//序列化请求 并统计序列化以后字节数int savedWriteIndex = buffer.writerIndex();//将写入位置后移16位buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);//请求序列化ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);//是否心跳检查 为空就是心跳检查if (req.isHeartbeat()) {// heartbeat request data is always nullbos.write(CodecSupport.getNullBytesOf(serialization));} else {ObjectOutput out = serialization.serialize(channel.getUrl(), bos);//事件序列化if (req.isEvent()) {//事件序列化encodeEventData(channel, out, req.getData());} else {//Dubbo请求序列化encodeRequestData(channel, out, req.getData(), req.getVersion());}out.flushBuffer();if (out instanceof Cleanable) {((Cleanable) out).cleanup();}}bos.flush();bos.close();//获取字节数int len = bos.writtenBytes();//检查字节长度checkPayload(channel, len);//将字节数写入header数组中Bytes.int2bytes(len, header, 12);//重置写入位置buffer.writerIndex(savedWriteIndex);//写入消息头buffer.writeBytes(header);//buffer写出去的位置从writeIndex开始 加上header长度 数据长度buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {int savedWriteIndex = buffer.writerIndex();try {//序列化Serialization serialization = getSerialization(channel, res);//协议头  长度为16字节byte[] header = new byte[HEADER_LENGTH];//魔数Bytes.short2bytes(MAGIC, header);//序列化方式header[2] = serialization.getContentTypeId();//心跳还是正常消息if (res.isHeartbeat()) {header[2] |= FLAG_EVENT;}//响应状态byte status = res.getStatus();header[3] = status;//设置请求IDBytes.long2bytes(res.getId(), header, 4);//写入时候真需要加上协议头长度buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);//对响应信息进行编码if (status == Response.OK) {if(res.isHeartbeat()){//心跳bos.write(CodecSupport.getNullBytesOf(serialization));}else {//正常响应ObjectOutput out = serialization.serialize(channel.getUrl(), bos);if (res.isEvent()) {encodeEventData(channel, out, res.getResult());} else {encodeResponseData(channel, out, res.getResult(), res.getVersion());}out.flushBuffer();if (out instanceof Cleanable) {((Cleanable) out).cleanup();}}} else {//错误消息ObjectOutput out = serialization.serialize(channel.getUrl(), bos);out.writeUTF(res.getErrorMessage());out.flushBuffer();if (out instanceof Cleanable) {((Cleanable) out).cleanup();}}bos.flush();bos.close();//写入的长度int len = bos.writtenBytes();//检查消息长度checkPayload(channel, len);Bytes.int2bytes(len, header, 12);//重置写入位置buffer.writerIndex(savedWriteIndex);//写入消息头buffer.writeBytes(header);//buffer写出去的位置从writeIndex开始 加上header长度 数据长度buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);} catch (Throwable t) {// clear bufferbuffer.writerIndex(savedWriteIndex);// send error message to Consumer, otherwise, Consumer will wait till timeout.if (!res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {Response r = new Response(res.getId(), res.getVersion());r.setStatus(Response.BAD_RESPONSE);if (t instanceof ExceedPayloadLimitException) {logger.warn(t.getMessage(), t);try {r.setErrorMessage(t.getMessage());channel.send(r);return;} catch (RemotingException e) {logger.warn("Failed to send bad_response info back: " + t.getMessage() + ", cause: " + e.getMessage(), e);}} else {// FIXME log error message in Codec and handle in caught() of IoHanndler?logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);try {r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));channel.send(r);return;} catch (RemotingException e) {logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);}}}// Rethrow exceptionif (t instanceof IOException) {throw (IOException) t;} else if (t instanceof RuntimeException) {throw (RuntimeException) t;} else if (t instanceof Error) {throw (Error) t;} else {throw new RuntimeException(t.getMessage(), t);}}
}

ExchangeCodec的decode方法是encode方法的逆过程,会先检查魔数,然后读取协议头和后续消息的长度,最后根据协议头中的各个标志位构造相应的对象,以及反序列化数据。

DubboCodec

在ExchangeCodecencode的encode方法中,不论是encodeRequest还是encodeResponse都调用encodeRequestData方法,该方法会对Boby内容进行编码,该方法实现是在DubboCodec中,因此DubboCodec是对消息体的编解码,接下来我们来看下encodeRequestData和encodeResponseData方法的实现,

protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {RpcInvocation inv = (RpcInvocation) data;//dubbo服务版本out.writeUTF(version);// https://github.com/apache/dubbo/issues/6138String serviceName = inv.getAttachment(INTERFACE_KEY);if (serviceName == null) {//服务pathserviceName = inv.getAttachment(PATH_KEY);}//服务名out.writeUTF(serviceName);//版本号out.writeUTF(inv.getAttachment(VERSION_KEY));//方法名out.writeUTF(inv.getMethodName());//方法类型描述out.writeUTF(inv.getParameterTypesDesc());Object[] args = inv.getArguments();if (args != null) {for (int i = 0; i < args.length; i++) {//参数值out.writeObject(encodeInvocationArgument(channel, inv, i));}}//附加属性out.writeAttachments(inv.getObjectAttachments());
}
@Overrideprotected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {Result result = (Result) data;//检验版本boolean attach = Version.isSupportResponseAttachment(version);Throwable th = result.getException();if (th == null) {Object ret = result.getValue();if (ret == null) {//空结果out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);} else {//正常写入out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);out.writeObject(ret);}} else {//异常out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);out.writeThrowable(th);}if (attach) {//Dubbo版本号result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());out.writeAttachments(result.getObjectAttachments());}
}

结束

欢迎大家点点关注,点点赞!


http://chatgpt.dhexx.cn/article/I43288ZO.shtml

相关文章

dubbo客户端的实现

业界微服务大行其道。服务与服务之间的同学主要有有以下两大类。 阿里RPC框架&#xff1a;dubboRestFull风格的Http调用 我们知道Http接口我们找到PostMan这种Http客户端。 但是dubbo似乎并没有想关的客户端&#xff0c;我们调试的时常常需要同时打开两个以上的服务。 dubbo是…

dubbo组成原理-http服务消费端如何调用

dubbo协议已经用的很多了&#xff0c;这里来稍微介绍一下http协议&#xff0c;官方对http协议的说明简直少的让人发指。哈哈 百度大部分都只是讲了http服务端的配置 那就先从服务端的配置说起 dubbo需要的jar包这里就不说明了&#xff0c;网上找些maven的pom就可以 web.xml…

Dubbo基本原理机制

分布式服务框架&#xff1a; –高性能和透明化的RPC远程服务调用方案–SOA服务治理方案 -Apache MINA 框架基于Reactor模型通信框架&#xff0c;基于tcp长连接 Dubbo缺省协议采用单一长连接和NIO异步通讯&#xff0c;适合于小数据量大并发的服务调用&#xff0c;以及服务消费…

Dubbo基本原理与机制

1、什么是Dubbo Dubbo 是一款高性能、轻量级的开源 RPC 框架&#xff0c;提供服务自动注册、自动发现等高效服务治理方案&#xff0c; 可以和 Spring 框架无缝集成。 2、Dubbo依赖关系 1、服务消费者&#xff08;Consumer&#xff09;: 调用远程服务的服务消费方&#xff0c…

dubbo原理和机制

Dubbo 框架是用来处理分布式系统中&#xff0c;服务发现与注册以及调用问题的&#xff0c;并且管理调用过程。 一&#xff0c;工作流程&#xff1a; 服务提供者在启动的时候&#xff0c;会通过读取一些配置将服务实例化。Proxy 封装服务调用接口&#xff0c;方便调用者调用。…

Dubbo的原理与机制

​ Dubbo 前言 在介绍Dubbo之前先了解一下基本概念&#xff1a; Dubbo是一个RPC框架&#xff0c;RPC&#xff0c;即Remote Procedure Call&#xff08;远程过程调用&#xff09;&#xff0c;相对的就是本地过程调用&#xff0c;在分布式架构之前的单体应用架构和垂直应用架…

Dubbo基础及原理机制

1. 什么是Dubbo&#xff1f; Dubbo是 阿里巴巴公司开源的一个高性能RPC 分布式服务框架&#xff0c;使得应用可通过高性能的 RPC 实现服务的输出和输入功能&#xff0c;可以和 Spring框架无缝集成&#xff0c;现已成为 Apache 基金会孵化项目。 2. 为什么要用Dubbo&#xff1…

Dubbo原理和机制详解(非常全面)

Dubbo是一款Java RPC框架&#xff0c;致力于提供高性能的RPC远程服务调用方案。Dubbo 作为主流的微服务框架之一&#xff0c;为开发人员带来了非常多的便利。 本文我们重点详解 Dubbo 的原理机制 mikechen 目录 Dubbo核心功能Dubbo核心组件Dubbo的架构设计Dubbo调用流程 1️…

大地测量学基础(复习)第一部分

写在前面 这篇博文是用来复习课程“大地测量学基础”的&#xff0c;里面仅列出本人觉得这门课程比较重要的部分&#xff0c;希望能够帮助到有需要的朋友。 课本采用《大地测量学基础》孔祥元&#xff0c;武汉大学出版社。 前面已经有一篇“大地测量学基础&#xff08;复习&…

深入理解ArcGIS的地理坐标系、大地坐标系

1、引言 地理坐标&#xff1a;为球面坐标。 参考平面地是 椭球面,坐标单位:经纬度 大地坐标&#xff1a;为平面坐标。参考平面地是 水平面,坐标单位&#xff1a;米、千米等 地理坐标转换到大地坐标的过程可理解为投影。 &#xff08;投影&#xff1a;将不规则的地球曲面转…

GNSS定位中的不同高度概念及计算

文章目录 高度相关的几个基本概念RTKLIB中高度设置与计算参考文献 由于在GNSS定位中由多种高度表示&#xff0c;不同的高度概念很容易混淆&#xff0c;中英文对应有时候也容易搞混。因此整理了一下常用的两种高度——椭球高、正高的概念与计算&#xff0c;并且标注了对应的英文…

概念讲解:大地水准面 | 地球椭球体 | 参考椭球体 | 大地基准面 | 地图投影

文章目录 大地水准面地球椭球体参考椭球体大地基准面地图投影几个概念之间的关系相关文章 大地水准面 指平均海平面通过大陆延伸勾画出的一个连续的封闭曲面。大地水准面包围的球体称为大地球体。从大地水准面起算的陆地高度&#xff0c;称为绝对高度或海拔。 大地水准面是对地…

地图学的基础知识_天文坐标系_大地坐标系_地心坐标系及其相关概念

学习地图学&#xff0c;由于地理知识欠缺&#xff0c;学习相关投影知识还为时过早&#xff0c;需要复习一些基本概念。 阅读对象:测绘类 地球自然球体: 由地球自然表面所包围的的形体称为地球自然体。 地球自然球体形状:地球不是一个正球体&#xff0c;而是一个极半径略短&a…

大地测量学

大地测量学简答题复习 问&#xff1a;建立国家平面控制网的方法&#xff1f; 答&#xff1a; 常规大地测量&#xff1a;三角测量&#xff08;已知一个点的坐标&#xff0c;又精密测量了1&#xff0c;2点的边长和坐标方位角&#xff0c;依据三角形的正弦定理推测出其他边的长…

[复试——大地测量学]第一章节——2022/12/30

PART 1 大地测量学的定义 是在一定的时间和空间的参考系中&#xff0c;测量和描绘地球形状及其重力场并监测其变化&#xff0c;为人类活动提供地球空间信息的一门学科。 大地测量学的作用 1.建立地面控制网 2.精确测定大地控制点的三维位置以及其重力场参数 大地测量学的…

1985高程基准与全球大地水准面(EGM2008)的关系综述

​按照惯例&#xff0c;先给结论&#xff1a; &#xff08;1&#xff09;1985国家高程基准与EGM2008的差异有严密的理论公式计算&#xff1b; &#xff08;2&#xff09;1985国家高程基准与EGM20008的垂直偏差与参考系、水准模型、选取的水准点有关系 &#xff08;3&#xf…

似大地水准面精化

转载自&#xff1a;http://blog.sina.com.cn/s/blog_64886846010153qi.html 大地高是指以参考椭球面作为高程基准面的高程系统&#xff0c;是地面点沿法线到参考椭球面的距离。 正高是地面点沿重力线到大地水准面的距离。正常高是指从一地面点沿过此点的**正常重力线**到似…

正常高、大地高、海拔高的测绘概念

一、海拔高、大地高 高程是地理学和测量学中对地物高度的一种表达。英文的表达是elevation。与高程相关的两个概念——大地高与海拔高,在实践上有差异,但很容易混淆。 王慧麟等编著的《测量与地图学》(南大出版社,2004年)中对这两个概念有明确表述: 点位沿椭球面的法线至…

大地高、正高和正常高及高程异常

测量学习过程中&#xff0c;这几个高程或许曾困扰过许多人&#xff08;其中也包括我&#xff09;&#xff0c;希望我能在这里把这些说清楚。 可以简单地理解&#xff0c;高程其实就是某点到某个基准面的距离。 这么算下来&#xff0c;对于这几个高程的理解就是怎么理解相对应…

大地高、海拔高 地心纬度、大地纬度/地理纬度

大地测量学模型所表示的地球表面&#xff0c;所有点的当地重力矢量都垂直于该表面&#xff0c;即一个“等位面”&#xff1b;这种形状称为大地水准面&#xff01; 但是由于表面及其不规则&#xff0c;见如下图&#xff1a; 真实的地球据说如下&#xff1a; 地球真实不是一个正球…