Dubbo调用介绍
首先客户端启动时会从注册中心拉取和订阅对应的服务列表,Cluster会把拉取到的服务列表聚合成一个Invoker,每次RPC调用前会通过Directory#list获取provider地址,获取这些服务列表给后续路由和负载均衡使用
应图中①中主要是将多个服务提供者做聚合.在框架内部另外一个实现Directory接口RegistryDirectory类,它和接口名是一一对应的关系,主要负责拉取和订阅服务提供者,动态配置和路由项
在Dubbo发起服务调用时,所有路由和负载均衡都是在客户端实现的.客户端服务调用首先会触发路由操作,然后将路由结果得到的服务列表作为负载均衡参数,经过负载均衡后会选出一台机器进行RPC调用,这三个步骤对应②,③,④.客户端经过路由和负载均衡后,会将请求交给底层I/O线程池(比如Netty)处理.I/O线程池主要处理读写,序列化和反序列化等逻辑,因此这里一定不能用阻塞操作.Dubbo也提供了控制参数控制(decode.in.io)参数,在处理反序列化对象时会在业务线程池中处理.在⑤中包含两种类似的线程池,一种是I/O线程池,另一种是Dubbo业务线程池
下面说的Telnet的东西我都不知道是啥
目前Dubbo将服务调用和Telent调用做了端口复用,在编码层面也做了适配.在Telnet调用时,会建立起一个TCP链接,传递接口,方法和JSON格式的参数进行服务调用,在编译码层面读取六中的字符串,最终交个Telnet对应的Handler去解析方法调用.如果是非Telnet调用,则服务提供方会根据传递过来的接口,分组和版本信息查找Invoker对应的实例进行反射调用.在⑦中进行了端口复用,如果是Telnet调用,则先找到对应的Invoker进行方法调用.Telnet和正常的RPC调用不一样的地方是序列化和反序列化使用的不是Hession方式,而是直接使用fastjson进行处理
Dubbo协议
Dubbo协议涉及参考了现有的TCP/IP协议,一次RPC调用包括协议头和协议提两部分,16字节长的报文头部主要携带了模式以及当前请求报文是否是Request,Response,心跳和事件的信息,请求时也会带上当前报文体内序列化协议编号.除此之外,请求报文还携带了请求状态,以及请求唯一表示和报文体长度
Dubbo协议字段解析:
偏移比特位 | 字段描述 | 作用 |
---|---|---|
0~7 | 魔数高位 | |
8~15 | 魔数低位 | |
16 | 数据包类型 | 是否为双向的RPC调用,0位Response,1为Request |
17 | 调用方式 | 仅在第16位设置为1的情况下有效 0为单向调用,1位双向调用 比如在优雅停机时服务端发送readonly不需要双向调用,这里标志位就不会设定 |
18 | 事件标识 | 0为当前数据包时请求或响应包 1为当前数据包时心跳包,设置了心跳包的报文不会透传到业务方法调用,仅用于框架内部保活机制 |
19~23 | 序列化器编号 | 2 为Hessian2Serialization 3 为JavaSerialization 4 为CompactedSerialization 6 为FastJsonSerialization 7 为NativeJavaSerialization 8 为KryoSerialization 9 为FstSerialization |
24~31 | 状态 | 20 为OK 30 为CLIENT_TIMEOUT 31 为SERVER_TIMEOUT 40 为BAD_REQUEST 50 为BAD_RESPONSE 60 为SERVICE_NOT_FOUND 70 为SERVICE_ERROR 80 为SERVER_ERROR 90 为CLIENT_ERROR 100 为SERVERTHREADPOOL_EXHAUSTEDERROR(服务端线程池满拒绝执行) |
32~95 | 请求编号 | 8个字节存储RPC请求的唯一id,用来将请求和响应做关联 |
96~127 | 消息体长度 | 占用4个字节存储消息体长度.再一次RPC请求过程中,消息体中一次会存储7部分内容 |
在消息体中,客户端严格按照序列化顺序写入信息,服务端也会遵循相同的顺序读取消息.客户端发起请求的消息体一次保存下列内容:Dubbo版本号,服务接口名,服务接口版本,方法名,参数类型,方法参数值和请求额外参数
响应标记:
状态值 | 状态符号 | 作用 |
---|---|---|
5 | RESPONSE_NULL_VALUE_WITH_ATTACHMENTS | 响应空值包含隐藏参数 |
4 | RESPONSE_VALUE_WITH_ATTACHMENTS | 响应结果包含隐藏参数 |
3 | RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS | 异常返回包含隐藏参数 |
2 | RESPONSE_NULL_VALUE | 响应空值 |
1 | RESPONSE_VALUE | 响应结果 |
0 | RESPONSE_WITH_EXCEPTION | 异常返回 |
在返回消息体中,会先把返回值状态标记写入输出流,紧接着再写返回方法值
Dubbo使用特殊符号0xdabb魔数来分割处理粘包问题
客户端使用多线程并发调用服务时,Dubbo通过使用协议头的全局请求id标识正确响应调用的线程
客户端多个线程并发请求时,框架内部会调用DefaultFuture对象get方法进行等待,在请求发起时,框架内部会创建Request对象,这个时候会被分配一个唯一id,DefaultFuture可以从Request对象中获取id,并将关联关系存储到静态HashMap中(即上图的Futures).当客户端收到响应时,会根据Response对象中的id,从Futures集合中查找对应的DefaultFuture对象,最终会唤醒对应的线程并通知结果.客户端也会启动一个定时扫描去探测超时没有返回的请求
编解码器
TODO
Telnet调用原理
编码器把Telnet当做明文字符串处理,按照Dubbo的调用规范,解析成掉用命令格式,然后查找对应的Invoker,发起方法调用
Telnet指令解析原理
Telnet指令解析被设置成了扩展点TelnetHandler,每个Telnet指令都会实现这个扩展点.
通过这个扩展点定义,能够解决扩展更多命令的诉求.message包含处理命令之外的所有字符串参数,具体如何使用这些参数以及这些参数的定义全部交给命令实现者决定.
完成Telnet指令转发的核心实现类是TelnetHandlerAdapter,它的实现非常简单,首先将用户输入的指令识别成command,然后将剩余的内容解析成message.message会交给命令实现者去处理.
TODO
Telnet实现健康监测
Telnet提供了健康检查的指令,可以在Telnet连接成功后执行status -l查看线程池,内存和注册中心等状态信息.
TODO