Dubbo协议设计参考了现有TCP/IP协议。一次RPC调用包括协议头和协议体两个部分。16字节长的报文头部主要携带了魔法数(0xdabb),以及当前请求报文是否是Request、REsponse、心跳和事件的信息,请求时也会携带当前报文体内序列化协议编号。除此之外还携带了请求状态,以及请求唯一标识和报文体长度。
Dubbo协议字段解析:
偏移比特位字段描述作用0~7魔数高位存储的是魔法数高位(0xda00)8~15魔数低位存储的是魔法数低位(0xbb)16数据包类型是否为双向RPC调用(比如方法调用有返回值),0为Response,1为Request17调用方式仅在第16位被这设为1的情况下有效0为单向调用,1位双向调用比如在优雅停机时服务端发送readoly不需要双向调用,这里标志位就不会设定18事件标识0为当前数据包是请求或响应包1为当前数据包是心跳包,比如框架为了保活TCP连接,每次客户端和服务端相互发送心跳包时这个标志位被设定设置了心跳报文不会透传到业务方法调用,仅用于框架内部保活机制19~23序列化器编号2为HessianSerializatiion3为JavaSerialization4为CompactedJavaSerialization6为FastJsonSerialization7为NativJavaSerialization8为KryoSerialization9为FstSerialization24~31状态20为OK30为CLIENT_TIMEOUT31为SERVER_TIMEOUT40为BAD_REQUEST50为BAD_RESPONSE…32~95请求编号这8个字节存储RPC请求的唯一id,用来将请求和响应做关联96~127消息体长度占用的4个字节存储消息体长度。在一次RPC请求过程中,消息体中一次会存储7部分内容在消息体中,客户端严格按照序列化顺序写入消息,服务端也会遵循相同的顺序读取消息,客户端发起请求的消息体一次保存下列内容:Dubbo版本号、服务接口名、服务接口版本、方法名、参数类型、方法参数值和请求额外参数(attachment)。
完整状态响应码和作用:
状态值状态符号作用20OK正确返回30CLIENT_TIMEOUT客户端超时31SERVER_TIMEOUT服务端超时40BAD_REQUEST请求报文格式错误50BAD_RESPONSE响应报文格式错误60SERVICE_NOT_FOUND未找到匹配的服务70SERVICE_ERROR服务调用错误80SERVER_ERROR服务端内部错误90CLIENT_ERROR客户端错误100SERVER_THREADPOOL_EXHAUSTED_ERROR服务端线程池满拒绝执行Dubbo响应标记:
状态值状态符号作用5RESPONSE_NULL_VALUE_WITH_ATTACHEMENTS响应空值包含隐藏参数4RESPONSE_VALUE_WITH_ATTACHEMENTS响应结果包含隐藏参数3RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS异常返回包含隐藏参数2RESPONSE_NULL_VALUE响应空值1RESPONSE_VALUE响应结果0RESPONSE_WITH_EXCEPTION异常返回在返回消息体中,会先把返回值状态标记写入输入流,根据标记状态判断RPC是否正常,比如一次正常RPC调用成功,则先往消息体中写一个标记1,紧接着再写方法返回值。
在网络通信中(基于TCP)需要解决网络粘包/解包的问题,一些常用解决办法比如用回车、换行、固定长度和特殊分隔符等进行处理,通过对前面协议的理解,很容易发现Dubbo其实就是用特殊符号0xdabb魔数来分隔处理粘包问题的。
客户端会使用多线程并发调用服务,Dubbo是如何做到正确响应调用线程的呢?
当客户端多个线程并发请求时,框架内部会调用Defaultfutre对象的get方法进行等待。在请求发起时,框架内部会创建Request对象,这个时候会被分配一个唯一的id,DefaultFuture可以从Request对象中获取id,并将关联关系存储到静态HashMap中,就是上图中的Future集合。当客户端收到响应时,会根据Response对象中的id,从Futures集合中查找对应的DefaultFuture对象,最终会唤醒对应的线程并通知结果。客户端也会启动一个定时扫描线程去探测超时没有返回的请求。