Dubbo调用流程一览

作者: 清幽之地 | 来源:发表于2020-02-28 16:21 被阅读0次

    前言

    Apache Dubbo作为一款高性能的Java RPC框架,在国内服务化体系的演进过程中扮演了一个非常重要的角色,被大量公司广泛使用。

    临近年关,或许有小伙伴有着寻找新机会的想法,那么在面试过程中很可能会常常见到这样一个问题:

    你了解Dubbo吗 ? 能不能讲一讲它的调用流程。

    对于不了解的盆友而言,无疑会降低印象分;如果仅仅会使用,其实也不太够,最起码我们要了解它的基本原理。

    本文试图从Dubbo使用者的角度上,结合流程图和关键代码把相应知识点串联起来,回答我们上面的问题。

    一、服务提供者

    从程序开发者的角度来看,我们要先有服务提供者。通常,我们在具体接口的实现上标注Dubbo的Service注解。

    package com.viewscenes.producer.dubbo;
    import org.apache.dubbo.config.annotation.Service;
    import com.viewscenes.common.service.DubboUserService;
    @Service
    public class DubboUserServiceImpl implements DubboUserService {
    }
    

    这个实现类在Dubbo中对应的解析类为ServiceBean,它负责将这个实现对外暴露成一个服务。过程如下:

    Dubbo服务暴露过程

    结合上图来看,我们可以说在提供者端,暴露一个服务的过程如下:

    首先,ServiceConfig类引用对外提供服务的实现类ref (如DubboUserServiceImpl) , 然后通过ProxyFactoty接口的扩展实现类的getInvoker()方法使用ref生成一个AbstractProxyInvoker实例,到此就完成了具体服务到Invoker的转化。

    接下来,通过Dubbo协议的export()方法,将Invoker转化为Exporter。那么在这里,就会先启动Netty Server的监听,然后将服务注册到服务注册中心。

    在这里,我们必须要注意的是,作为服务提供者端,已经通过Netty开启了TCP端口的监听。那么,当消费者调用的时候,通过一系列Netty Handler处理器,就会调用到DubboProtocol > ExchangeHandler.reply()

    在这个方法里,就是一个反推的过程。通过要调用的服务接口名称,找到Exporter ,然后再获取到Invoker对象。

    Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
        int port = channel.getLocalAddress().getPort();
        String path = inv.getAttachments().get(PATH_KEY);
        String serviceKey = serviceKey(port, path, inv.getAttachments().get(VERSION_KEY),  inv.getAttachments().get(GROUP_KEY));
        DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
        return exporter.getInvoker();
    }
    

    从上面的分析中我们已经知道,这里的Invoker对象是根据服务实现类生成的一个AbstractProxyInvoker实例。它最终会调用到wrapper.invokeMethod()方法。这里的wrapper类是通过Javassist生成的,在内存中的类,它的核心方法长这样:

    image

    Dubbo会给每个服务提供者的实现生成一个Wrapper类。当接收到消费方的请求后,根据传递的方法名和参数,Wrapper类调用服务提供者的接口类实现即可。这样做的目的主要是为了减少反射的调用。

    二、服务消费者

    在服务消费者端,我们直接引用一个接口即可。

    @Reference
    DubboUserService userService;
    

    或许你可能要问,为啥只注入了这么一个普通的接口,就可以调用到远端的服务呢 ?

    我们想想在 Mybatis中的Dao接口和XML文件里的SQL是如何建立关系的? 这个问题中,它们是怎么关联起来的呢 ?

    说穿了还是Spring的功劳,或者说是Spring FactoryBean的功劳。

    Dubbo中,标注了@Reference的接口,都会被当成一个Factory Bean ,这个Bean一般都会返回一个代理对象,来屏蔽底层一些复杂的操作。比如Mybatis里的mapper接口和xml文件关联,Dubbo中的网络通信等。

    我们还是先来通过一张图看看消费者端的具体过程:

    服务引用

    结合上图来看,我们总结下服务引用的过程:

    Reference注解标注的Dubbo接口,会被注册成FactoryBean,并最终返回一个代理对象。

    在创建代理的过程中,会调用其他方法构建以及合并 Invoker 实例。

    首先,调用DubboProtocol的refer方法,返回DubboInvoker对象。在这里,比较重要的是获取客户端实例。比如NettyClientDubbo要依靠它来进行网络通信。

    然后,还需要将多个服务提供者实例合并成一个,这是集群容错机制的实现。

    最后,通过JavassistProxyFactory创建代理并返回。在这里,它的处理器是InvokerInvocationHandler ,这就意味着,当我们在消费者端调用一个Dubbo接口的时候,实际上会调用到InvokerInvocationHandler.invoke()方法,在这里面Dubbo完成了譬如集群容错、负载均衡、调用远程方法的一系列动作。

    Dubbo消费者发送请求的时候,最终会调用到DubboInvoker中的方法。在这里,会完成具体的请求逻辑,比如发送请求数据。

    final class HeaderExchangeChannel implements ExchangeChannel {
        //创建请求消息对象
        Request req = new Request();
        req.setVersion(Version.getProtocolVersion());
        req.setTwoWay(true);
        req.setData(request);
        
        //创建Future,用于获取返回结果
        DefaultFuture future = DefaultFuture.newFuture(this.channel, req, timeout, executor);
        try {
            //通过Netty客户端发送数据
            this.channel.send(req);
            return future;
        } catch (RemotingException var7) {
            future.cancel();
            throw var7;
        }
    }
    

    三、请求-响应过程

    Dubbo请求响应过程

    相关文章

      网友评论

        本文标题:Dubbo调用流程一览

        本文链接:https://www.haomeiwen.com/subject/ajmlhhtx.html