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

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

Demo

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

 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
         <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
        </dependency>

如果你的是springboot项目也可以用springboot的starter包

<dependency>
            <groupId>io.dubbo.springboot</groupId>
            <artifactId>spring-boot-starter-dubbo</artifactId>
            <version>1.0.0</version>
        </dependency>

添加配置文件:
spring-dubbo.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <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"/>

</beans>

调用的时候也很简单

@Autowired
private xxxService xxxService;

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

源码分析

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

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

服务发布过程

通过上面的框架了解我们大致知道了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<URL> 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<URL> 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<T> 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传给信息交换层提供给消费方进行调用。

猜你喜欢

转载自blog.csdn.net/nethackatschool/article/details/78560574