Dubbo源码阅读-服务调用过程

本文深入探讨了Dubbo服务调用的过程,从消费者请求的发送,包括代理调用、过滤器处理、序列化,到提供者的接收。详细解析了协议构造、序列化操作,并对数据发送的各个环节进行了拆解。

1.概述

Dubbo 服务调用过程比较复杂,包含很多步骤,比如:发送请求、编解码/报文协议、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。本篇文章将会重点分析请求的发送与接收、编解码、线程派发以及响应的发送与接收等过程,至于服务降级、过滤器链和序列化后续在其他文章进行分析,暂时忽略。

2.源码

2.1 服务调用方式

2.2 消费者请求数据发送过程

proxy0#sayHello(String)
  —> InvokerInvocationHandler#invoke(Object, Method, Object[])
    —> MockClusterInvoker#invoke(Invocation)
      —> AbstractClusterInvoker#invoke(Invocation)
        —> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker<T>>, LoadBalance)
          —> Filter#invoke(Invoker, Invocation)  // 包含多个 Filter 调用
            —> ListenerInvokerWrapper#invoke(Invocation) 
              —> AbstractInvoker#invoke(Invocation) 
                —> DubboInvoker#doInvoke(Invocation)
                  —> ReferenceCountExchangeClient#request(Object, int)
                    —> HeaderExchangeClient#request(Object, int)
                      —> HeaderExchangeChannel#request(Object, int)
                        —> AbstractPeer#send(Object)
                          —> AbstractClient#send(Object, boolean)
                            —> NettyChannel#send(Object, boolean)
                              —> NioClientSocketChannel#write(Object)

2.2.1 协议

 Dubbo 数据包结构

偏移量(Bit)字段取值
0 ~ 7魔数高位0xda00
8 ~ 15魔数低位0xbb
16数据包类型0 - Response, 1 - Request
17调用方式仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用
18事件标识0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包
19 ~ 23序列化器编号2 - Hessian2Serialization
3 - JavaSerialization
4 - CompactedJavaSerialization
6 - FastJsonSerialization
7 - NativeJavaSerialization
8 - KryoSerialization
9 - FstSerialization
24 ~ 31状态20 - OK
30 - CLIENT_TIMEOUT
31 - SERVER_TIMEOUT
40 - BAD_REQUEST
50 - BAD_RESPONSE
……
32 ~ 95请求编号共8字节,运行时生成
96 ~ 127消息体长度运行时计算

请求过程首先会通过位运算将消息头写入到 header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。


/**
 * ExchangeCodec.
 * 第1字节:
 *  0 ~ 7	魔数高位	0xda00
 * 第2字节:
 *  8 ~ 15	魔数低位	0xbb
 * 第3字节:
 *  16	数据包类型	0 - Response, 1 - Request
 *  17	调用方式	仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用
 *  18	事件标识	0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包
 *  19 ~ 23	序列化器编号
 *      2 - Hessian2Serialization
 *      3 - JavaSerialization
 *      4 - CompactedJavaSerialization
 *      6 - FastJsonSerialization
 *      7 - NativeJavaSerialization
 *      8 - KryoSerialization
 *      9 - FstSerialization
 * 第4字节:
 *  24 ~ 31	状态
 *      20 - OK
 *      30 - CLIENT_TIMEOUT
 *      31 - SERVER_TIMEOUT
 *      40 - BAD_REQUEST
 *      50 - BAD_RESPONSE
 * 第5-12字节:
 *  32 ~ 95	请求编号	共8字节,运行时生成
 * 第13-16字节:
 *  96 ~ 127	消息体长度	运行时计算
 */
public class ExchangeCodec extends TelnetCodec {

    protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
        Serialization serialization = getSerialization(channel);
        // header.
        byte[] header = new byte[HEADER_LENGTH];
        // set magic number.
        Bytes.short2bytes(MAGIC, header);

        // set request and serialization flag.
        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());

        if (req.isTwoWay()) {
            header[2] |= FLAG_TWOWAY;
        }
        if (req.isEvent()) {
            header[2] |= FLAG_EVENT;
        }

        // set request id.
        Bytes.long2bytes(req.getId(), header, 4);

        // encode request data.
        int savedWriteIndex = buffer.writerIndex();
        // 更新 writerIndex,先预留头部的16个字节空间
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        if (req.isEvent()) {
            encodeEventData(channel, out, req.getData());
        } else {
            // 对请求数据进行序列化操作
            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);
        // 将消息体长度写入到消息头中
        Bytes.int2bytes(len, header, 12);

        // write
        buffer.writerIndex(savedWriteIndex);
        // 写入头信息
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    }
}

2.2.2 序列化

我们再来看一下 Request 对象的 data 字段序列化过程,也就是 encodeRequestData 方法的逻辑,如下:

public class DubboCodec extends ExchangeCodec implements Codec2 {
    protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
        RpcInvocation inv = (RpcInvocation) data;
        // 依次序列化 dubbo version、path、version
        out.writeUTF(version);
        out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
        out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
        // 序列化调用方法名
        out.writeUTF(inv.getMethodName());
        // 将参数类型转换为字符串,并进行序列化
        out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
        Object[] args = inv.getArguments();
        if (args != null)
            for (int i = 0; i < args.length; i++) {
                // 对运行时参数进行序列化
                out.writeObject(encodeInvocationArgument(channel, inv, i));
            }
        // 序列化 attachments
        out.writeObject(inv.getAttachments());
    }
}

2.3 提供者数据接受过程

3.总结

TODO

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值