• 面试败给微服务?别怕,我带你一起手撕Dubbo,SpringBoot与Cloud
  • 面试终败“高并发”,25天苦心钻研,居然整出一份并发宝典?
  • 2020“闭关”跳槽季,啃透分布式三大技术:限流、缓存、通讯
  • 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够?

前言

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;@Servicepublic class DubboUserServiceImpl implements DubboUserService {}

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




dubbo 调用指定url 调用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生成的,在内存中的类,它的核心方法长这样:


dubbo 调用指定url 调用dubbo接口_dubbo 调用指定url_02


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

二、服务消费者

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

@ReferenceDubboUserService userService;

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

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

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

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

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


dubbo 调用指定url 调用dubbo接口_Java_03


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

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

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

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

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

最后,通过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 调用指定url 调用dubbo接口_Java_04


作者:清幽之地