深入理解dubbo之服务发布源码分析

dubbo 是阿里开源的一个分布式服务框架,它的最大特点是按照分层的方式来架构,使各层之间充分解耦,并且它是无侵入性的,dubbo可以无缝与spring整合,更重要的是dubbo还提供了强大的容错和监控功能。 对于业务方来说,dubbo使用上手足够简单,调用过程对业务方透明,对开发人员友好。

Demo
在spring项目中添加如下pom包:

com.alibaba dubbo org.apache.zookeeper zookeeper com.github.sgroschupf zkclient 1 2 3 4 5 6 7 8 9 10 11 12 如果你的是springboot项目也可以用springboot的starter包 io.dubbo.springboot spring-boot-starter-dubbo 1.0.0 1 2 3 4 5 添加配置文件: spring-dubbo.xml

<dubbo:application  name="ifenqu-web"  />
<dubbo:registry protocol="zookeeper" address="${dubbo.address}"  />
<dubbo:protocol name="dubbo" port="${dubbo.port}"/>
<dubbo:consumer timeout="60000" check="false"/>
<dubbo:service interface="com.xxx.xxx.xxxService" ref="xxxService" timeout="5000"/>

调用的时候也很简单

@Autowired
private xxxService xxxService;

demo 接介绍到这,今天的重点不是讲如何使用dubbo,今天的重点是说一说dubbo的架构设计。

源码分析
dubbo框架设计总共分了10层:

  1. 服务接口层(Service):该层是与实际业务逻辑相关,就如上面demo配置的
    <dubbo:service interface=“com.xxx.xxx.xxxService” ref=“xxxService” timeout=“5000”/>,这个service就是业务方自己定义的接口与其实现。

配置层(Config):该层是将业务方的service信息,配置文件的信息收集起来,主要是以ServiceConfig和ReferenceConfig为中心,ServiceConfig是服务提供方的配置,当Spring启动的时候会相应的启动provider服务发布和注册的过程,主要是加入一个ServiceBean继承ServiceConfig在Spring注册。同理ReferenceConfig是consumer方的配置,当消费方启动时,会启动consumer的发现服务订阅服务的过程,当然也是使用一个ReferenceBean继承ReferenceConfig注册在spring上。
服务代理层(Proxy):对服务接口进行透明代理,生成服务的客户端和服务器端,使服务的远程调用就像在本地调用一样。默认使用JavassistProxyFactory,返回一个Invoker,Invoker则是个可执行核心实体,Invoker的invoke方法通过反射执行service方法。
服务注册层(Registry):封装服务地址的注册和发现,以服务URL为中心,基于zk。
集群层(Cluster):提供多个节点并桥接注册中心,主要负责loadBanlance、容错。
监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
远程调用层(Protocol):封装RPC调用,provider通过export方法进行暴露服务/consumer通过refer方法调用服务。而Protocol依赖的是Invoker。通过上面说的Proxy获得的Invoker,包装成Exporter。
信息交换层(Exchange):该层封装了请求响应模型,将同步转为异步,信息交换层依赖Exporter,最终将通过网络传输层接收调用请求RequestFuture和ResponseFuture。
网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
数据序列化层:该层无需多言,将数据序列化反序列化。
服务发布过程
通过上面的框架了解我们大致知道了dubbo是怎么工作的,接下来我们来通过代码来具体看看dubbo的服务发布过程,进一步理解dubbo的工作原理。
先看到demo中的spring-dubbo配置文件。这些配置文件全都会被装配成RegistryConfig,其属性如下:

public class RegistryConfig extends AbstractConfig {

private static final long serialVersionUID = 5508512956753757169L;

public static final String NO_AVAILABLE = "N/A";

// 注册中心地址
private String            address;

// 注册中心登录用户名
private String            username;

// 注册中心登录密码
private String            password;

// 注册中心缺省端口
private Integer           port;

// 注册中心协议
private String            protocol;

// 客户端实现
private String            transporter;

private String            server;

private String            client;

private String            cluster;

private String            group;

private String            version;

// 注册中心请求超时时间(毫秒)
private Integer           timeout;

// 注册中心会话超时时间(毫秒)
private Integer           session;

// 动态注册中心列表存储文件
private String            file;

// 停止时等候完成通知时间
private Integer           wait;

// 启动时检查注册中心是否存在
private Boolean           check;

// 在该注册中心上注册是动态的还是静态的服务
private Boolean           dynamic;

// 在该注册中心上服务是否暴露
private Boolean           register;

// 在该注册中心上服务是否引用
private Boolean           subscribe;

// 自定义参数
private Map<String, String> parameters;

// 是否为缺省
private Boolean             isDefault;

这些配置文件根据注册中心的个数会被装配拼接成Dubbo的URL(该url是dubbo中自定义的),该URL长这个样子:

registry://sit-zk.host:2181/com.alibaba.dubbo.registry.RegistryService?application=ifenqu-web&dubbo=2.5.3&pid=13168&registry=zookeeper&timestamp=1510828420296

看完配置信息,接下来让我们看下Service发布的核心方法:ServiceConfig类中的doExportUrls

private void doExportUrls() {
//该方法根据配置文件装配成一个URL的list
List registryURLs = loadRegistries(true);
//根据每一个协议配置来分别暴露服务
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}

这个protocols长这个样子<dubbo:protocol name=“dubbo” port=“20888” id=“dubbo” /> protocols也是根据配置装配出来的。接下来让我们进入doExportUrlsFor1Protocol方法看看dubbo具体是怎么样将服务暴露出去的。这个方法特别大,有将近300多行代码,但是其中大部分都是获取类似protocols的name、port、host和一些必要的上下文,代码太长就不全都贴出来了,只贴关键部分。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
//…省略获取上下文代码
//通过interfaceClass获取要暴露服务的所有要暴露的方法
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
//…省略非核心代码
//根据上下文创建URL对象
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? “” : contextPath + “/”) + path, map);

//通过proxyFactory来获取Invoker对象
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//将invoker对象在protocol中封装成Exporter方便提供给信息交换层进行网络传输
Exporter<?> exporter = protocol.export(invoker);
//将exporter添加到list中
exporters.add(exporter);

看到这里就比较明白dubbo的工作原理了doExportUrlsFor1Protocol方法,先创建URL,URL创建出来长这样dubbo://192.168.xx.63:20888/com.xxx.xxx.VehicleInfoService?anyhost=true&application=test-web&default.retries=0&dubbo=2.5.3&interface=com.xxx.xxx.VehicleInfoService&methods=get,save,update,del,list&pid=13168&revision=1.2.38&side=provider&timeout=5000&timestamp=1510829644847,是不是觉得这个URL很眼熟,没错在注册中心看到的services的providers信息就是这个,再传入url通过proxyFactory获取Invoker,再将Invoker封装成Exporter的数组,只需要将这个list提供给网络传输层组件,然后consumer执行Invoker的invoke方法就行了。让我们再看看这个proxyFactory的getInvoker方法。proxyFactory下有JDKProxyFactory和JavassistProxyFactory。官方推荐也是默认使用的是JavassistProxyFactory。因为javassist动态代理性能比JDK的高。

public class JavassistProxyFactory extends AbstractProxyFactory {

@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper类不能正确处理带$的类名
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName, 
                                  Class<?>[] parameterTypes, 
                                  Object[] arguments) throws Throwable {
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

}

可以看到使用了动态代理的方式调用了要暴露的service的方法。并且返回了Invoker对象。在dubbo的服务发布中我们可以看到,这个Invoker贯穿始终,都可以看成是一个context的作用了,让我们进Invoker里面去看看这个Invoker到底是何方神圣。

public interface Invoker extends Node {

/**
 * get service interface.
 * 
 * @return service interface.
 */
Class<T> getInterface();

/**
 * invoke.
 * 
 * @param invocation
 * @return result
 * @throws RpcException
 */
Result invoke(Invocation invocation) throws RpcException;

这个Invoker就两个方法,一个getInterface,也就是要暴露的服务接口,一个就是invoke方法,这个invoke方法在AbstractProxyInvoker中是这样的:

public Result invoke(Invocation invocation) throws RpcException {
    try {
    //调用doInvoke方法,返回一个Result
        return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
    } catch (InvocationTargetException e) {
        return new RpcResult(e.getTargetException());
    } catch (Throwable e) {
        throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

其实看到JavassistProxyFactory大家就应该大概明白了这个Invoker的作用,同时这个类的名字就叫Invoker也可以猜个大概,Invoker就是调用service的方法的实体类。其中doInvoke方法已经在JavassistProxyFactory中定义了,通过反射调用要暴露的service的方法。

服务发布总结
看完源码,我们已经知道了dubbo的主要发布过程,现在我们回过头来结合dubbo的总体架构和源码的分析,总结一下dubbo服务发布。服务发布过程总共五个步骤:

业务方将服务接口和实现编写定义好,添加dubbo相关配置文件。
Config层加载配置文件形成上下文,Config层包括:ServiceConfig、ProviderConfig、RegistryConfig等。
ServiceConfig根据Protocol类型,根据ProtocolConfig、ProviderConfig加载registry,根据加载的registry创建dubbo的URL。
准备工作做完后ProxyFactory上场,dubbo中有两种代理方式,JDK代理和Javassist代理,默认使用Javassist代理,Proxy代理类根据dubbo配置信息获取到接口信息、通过动态代理方式将接口的所有方法交给Proxy代理类进行代理,并封装进Invoker里面。
将所有需要暴露的service封装的Invoker组成一个list传给信息交换层提供给消费方进行调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值