dubbo 远程服务调用流程

来源:互联网 发布:女神联盟2进阶15数据 编辑:程序博客网 时间:2024/05/17 03:27
  1. 消费端触发请求。
  2. 消费端请求编码。
  3. 提供端请求解码。
  4. 提供端处理请求。
  5. 提供端响应结果编码。
  6. 消费端响应结果解码。

消费端触发请求

在消费者初始化的时候,会生成一个消费者代理注册到容器中,该代理回调中持有一个MockClusterInvoker实例,消费调用服务接口时它的invoke会被调用,此时会构建一个RpcInvocation对象,把服务接口的method对象和参数放到RpcInvocation对象中,作为MockClusterInvoker.invoke方法的参数,在这个invoke方法中,判断请求是需要mock,是否配置了mock属性,是强制mock还是失败后mock,关于mock这里先不详细展开,这里只看下核心流程。
MockClusterInvoker.invoke会调用FailfastClusterInvoker.invoke,为了服务高可用性,同一个服务一般会有多个应用服务者,在服务消费者初始化时,接口名和提供者Invoker对应关系保存在RegistryDirectory的methodInvokerMap中。
读取到所有符合条件的服务提供者invoker之后,由LoadBalance组件执行负载均衡。methodInvokerMap保存的是持有DubboInvoker(dubbo协议)实例的InvokerDelegete对象,是InvokerFilter链的头部,先先激活Filter链,最后调用DubboInvoker.invoke,此时远程调用分三种类型:
  • 单向调用,无需获取关注调用结果的,无需等待接口返回结果,注意调用结果不要单纯跟返回值混淆了,异常也是调用结果。
  • 异步调用,需要关注返回结果 ,但是不会同步等待接口调用结束,会异步的获取返回结果,这种情况给调用者返回一个Future,但是不同步等待Future.get返回调用结果。
  • 同步调用,需要同步等待服务调用结束获取调用结果,给调用者返回一个Future并且Future.get等待结果,此时接口调用线程会挂起等等待响应。
我们大部分使用场景都是同步调用,所以主要看一下同步调用,如果使用者配置了多个connectins,按顺序选择一个ExchangeClient和服务器通信,同步调用时调用HeaderExchangeClient.request->HeaderExchangeChannel.request。
这里的request参数是RpcInvocation对象,包含调用的方法、参数等信息,timeout参数是接口超时时间,把这些信息封装在Request对象中,调用channel.send,这个channel对象就是和服务端打交道的NettyClient实例,NettyClient.send调用NettyChannel.send。

这里的sent参数决定是否等待请求消息发出,sent=true等待消息发出,消息发送失败将抛出异常,sent=false不等待消息发出,将消息放入IO队列中,即刻返回。默认情况下都是false,NettyChannel中有channel属性,这个channel是Netty框架中的组件,负责客户端和服务端链路上的消息传递,channel.write把请求消息写入,这里的message是上面封装的Request对象,这里的IO模型是非阻塞的,线程不用同步等待所有消息写完,而是直接返回。调用Netty框架的IO事件之后触发Netty框架的IO事件处理链。

消费端请求编码

在消费者初始化创建NettyClient时,NettyClient添加了三个事件处理器组成链。
NettyCodecAdapter.decoder->NettyCodecAdapter.encoder->NettyHandler,
其中NettyCodecAdapter.encoder是下行事件处理器(实现了ChannelDownstreamHandler接口),NettyCodecAdapter.decoder是上行事件处理器(实现了ChannelUpstreamHandler接口),NettyHandler是上行+下行处理器。channel.write是Netty框架里一个下行事件,所以NettyCodecAdapter.encoder和NettyHandler处理器会被回调,下行事件的处理器调用是后添加的处理器先执行。
NettyHandler没有对请求消息做任何加工,只是触发dubbo框架的一些回调,这些回调里面没有做任何核心的事情。
    @Override    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {        super.writeRequested(ctx, e);        NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);        try {            handler.sent(channel, e.getMessage());        } finally {            NettyChannel.removeChannelIfDisconnected(ctx.getChannel());        }    }

NettyCodeCAdapter.encode中使用InternalEncoder,对请求信息进行编码:
    //Netty中用于标示一个ChannelHandler可以被安全地共享    @Sharable    private class InternalEncoder extends OneToOneEncoder {        @Override        protected Object encode(ChannelHandlerContext ctx, Channel ch, Object msg) throws Exception {            com.sitech.hsf.remoting.buffer.ChannelBuffer buffer =                com.sitech.hsf.remoting.buffer.ChannelBuffers.dynamicBuffer(1024);            NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);            try {            codec.encode(channel, buffer, msg);            } finally {                NettyChannel.removeChannelIfDisconnected(ch);            }            return ChannelBuffers.wrappedBuffer(buffer.toByteBuffer());        }    }

这里面的Code2组件是DubboCountCodec实现,DubboCountc.encode调用DubboCodec.encode,DubboCodec继承自ExchangeCodec,最后调用ExchangeCodec.encode
    public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {        if (msg instanceof Request) {            encodeRequest(channel, buffer, (Request) msg);        } else if (msg instanceof Response) {            encodeResponse(channel, buffer, (Response) msg);        } else {            super.encode(channel, buffer, msg);        }    }



在encoder中对Request对象进行编码,这个msg参数就是上面的被write的Request对象,这里的Codec2组件是DubboCountCodec实现,DubboCountCodec.encode调用DubboCodec.Encode。
根据协议,消息中写入16个字节的消息头:
(1)1~2字节,固定的魔数。
(2)第3个字节,第8位存储数据类型是请求数据还是响应数据,其它7位存储序列化类型,约定和服务端的序列化-反序列化协议。
(3)5~12个字节,请求id
(4)13~16个字节,请求数据长度。
    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);//2字节魔数        // set request and serialization flag.   1字节序列化类        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);//8字节请求id        // encode request data.        int savedWriteIndex = buffer.writerIndex();        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());        }        out.flushBuffer();        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);    }



ExchangeCodec.encodeRequest从URL中查找序列化扩展点名称,加载序列化组件把请求对象序列成二进制,消费端和提供端的序列化反序列化协议要配套,所以这个序列化协议一般在提供端指定,指定的协议类型会在提供者和消费者初始化的时候写入URL对象中,框架默认的序列化协议是hessian2。消息体数据包含dubbo版本号、接口名称、接口版本、方法名称、参数类型列表、参数、附加信息、把它们按顺序依次序列化,数据写入类型为ChannelBuffer的buffer参数中,如果参数中有回调接口,还需要在消费端启动监听提供端的回调,这里不展开。
DubboCodec.encodeRequestData
    @Override    protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException {        RpcInvocation inv = (RpcInvocation) data;        out.writeUTF(inv.getAttachment(Constants.HSF_VERSION_KEY, HSF_VERSION));//hsf版本        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));//参数        }        out.writeObject(inv.getAttachments());//附加信息    }


然后把封装好的ChannelBuffer写到链中发送到服务端,这里消费端前半部分的工作就完成,接下来的目光移到服务端。

提供端请求解码

在看提供端初始化代码的时候看到,框架在创建NettyServer时,也会创建netty框架的IO事件处理器链:
NettyCodecAdapte.decoder->NettyCodecAdapter.encoder->NettyHandler.
客户端发送数据到服务端时会触发服务端的上行IO事件并且启动处理器回调,先触发NettyCodecAdapter.decode,再触发NettyHandler。

把数据读取到ChannelBuffer之后扔组Codec2组件进行解码处理,这里有个半包传输处理,因为这里使用的是非组塞的IO模型,非阻塞IO的特点是线程的读取数据是事件触发式,是由一个Selector组件轮询准备就绪的IO事件,发现准备就绪的事件之后通知线程读取,这种模式的好处是可以极大的优化线程模型,只需少数几个线程处理所有客户端和服务端连接,而阻塞IO需要线程和连接一对一,但是非阻塞IO处理能力远高于阻塞于IO,不像阻塞式IO读定数据时只有数据读完或者超时才会返回,这样能保证读到的数据肯定是完整,而非阻塞模式方法返回之后可能只能读到一部分数据,框架的处理是在解析消息时检查消息的长度确定是否有完整的数据,如果数据不完整返回NEED_MORE_INPUT,保存当前解析的位置等待链路的下次IO事件,在下次IO事件到达时从上次保存的位置开始解析。

读取到完整的数据之后解析数据头,读取魔数,序列化类型、以及请求id,读取第3个字节判断数据是消费端请求还是提供端响应数据,并且从1-7位从读出序列化类型,并且根据此序列化类型加载序列化组件对消息进行反序列化按顺序写入dubbo版本号、接口名称、接口版本、方法名称、参数类型列表、参数、附加信息,写入DecodeableRpcInvocation对象对应的属性中。

创建一个Request对象,把DecodeableRpcInvocation对象设置到Request对象的data属性中。

Decoder执行完之后,事件进入到下一个处理器NettyHandler,这个handler封装了很多层:DecodeHandler->HeaderExchangeHandler->DubboProtocol.requestHandler,中间封装了很多层,这里只把重要的列出来,源头是从创建NettryServer时候传递来的。


提供端处理请求

请求处理再走下一个handler--DubboProtocol.requestHandler,调用其reply方法 ,HeaderExchangeHandler.handleRequest把Request对象中的data取出来传到requestHandler中,这个data就是前面的解码后的DecodeableRpcInvocation对象,它是Invocation接口的一个实现。

reply查找请求对应提供端的Invoker,在接口提供者初始化时,每个接口都会创建一个Invoker的Exporter,Exporter持有invoker实例,Exporter对象保存在DubboProtocol的exporterMap中,key是由URL生成的serviceKey,此时通过Invocation中的信息就可还原该serviceKey并且找到对应的Exporter和Invoker,在分析提供者初始化代码时知道它是Invoker-Filter的头节点,激活Filter后调用由ProxyFactory生成的Invoker。
调用invoker.invoke时,通过反射调用最终的服务实现执行相关逻辑。
服务执行结束之后,创建一个Response对象返回给客户端。在执行服务实现时会出现两种结果:成功和失败,如果成功,把返回值设置到Response的result中,Response的status设置成ok,如果失败,把失败异常设置到Response的errorMessage中,status设置成SERVICE_ERROR。

回到HeaderExchangeHandler.received中的代码,在handlerRequest之后,调用channel.send把Response发送到客户端,这个channel封装客户端-服务端通信链路,最终会调用netty框架,把响应写回客户端。

提供端响应结果编码

提供端要按照和消费端的协议把Response按照特定的协议进行编码,把编码后的数据写回到消费端,先激活NettyHandler,再激活NettyCodecAdapter.encoder,在NettyCodecAdapter.encoder对响应结果进行编码,还是通过Code2组件和请求编码时使用的组件一样,把响应和响应结果依次写回到客户端,根据协议写入16个字节的数据冰头:
(1)1~2字节魔数
(2)第3个字节,序列化组件类型,约定和客户端的序列化-反序列化协议
(3)第4个字节,响应状态,是ok还是error
(4)5~13个字节,响应id,
(5)13~16个字节,响应数据长度

返回结果有三种结果:(1)没有返回值即返回类型是void(2)有返回值 并且执行成功(3)服务调用异常

消费端响应结果解码

服务端组客户端回写数据之后,客户端会收到IO事件,NettyClient会先调用 NettyCodecAdapter.decoder,再调用 NettyHandler。
对数据进行解码,解析出序列化协议、响应状态、响应id(即请求id),把响应body数据读到DecodeableRpcResult对象中,进行解析同时加载原始的Request数据,这个Request对象在请求时会缓存到DefaultFuter中,加载Request的目的是因为Request中Invocation携带了服务接口的返回值类型信息,需要根据这个类型把响应解析创建对应类型的对象。
创建Response对象并且把解析出结果或异常设置到Response中,decoder把响应解析成Response对象,NettyHandler接着处理,同样触发它的messageReceive事件,在提供端解码的时候看到了,它的handler封装关系是:DecodeHandler->HeaderExchangeHandler->DubboProtocol.requestHandler,主要处理在HeaderExchangeHandler中。
它会调用DefaultFuture.doReceived,这里主要做的事情是唤醒调用者线程,并且把Response设置到DefaultFuture中,在消费者触发请求的代码中可以看到,在消费者触发请求的代码中,消费端调用接口的时候请注写到提供端之后,会调用DefaultFuture.get阻塞等待响应结果。

DefaultFuture.doReceive时,设置 response,唤醒lock.condition.await,此时调用线程被唤醒并且检查是否已经有了response(避免假唤醒),唤醒之后返回response中的result,调用端即拿到了接口的调用结果,整个远程服务接口的调用流程就完成了。

转载源于:http://blog.csdn.net/tolihaifeng/article/details/60972120

原创粉丝点击