前言
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
,它负责将这个实现对外暴露成一个服务。过程如下:
结合上图来看,我们可以说在提供者端,暴露一个服务的过程如下:
首先,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
会给每个服务提供者的实现生成一个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
对象。在这里,比较重要的是获取客户端实例。比如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;
}
}
网友评论