前后分离JAVA架构

Dubbo调用流程一览

2020-02-28  本文已影响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请求响应过程
上一篇 下一篇

猜你喜欢

热点阅读