• 【2020-03-28】Dubbo源码杂谈


    前言

        本周空闲时间利用了百分之六七十的样子。主要将Dubbo官网文档和本地代码debug结合起来学习,基本看完了服务导出、服务引入以及服务调用的过程,暂未涉及路由、字典等功能。下面对这一周的收获进行一下总结梳理。

    一、基于事件驱动的服务导出

       提起服务导出,不要被它的名字误导了,通俗点说就是服务的暴露和注册。服务的暴露是指将服务端的端口开放,等待消费端来连接。服务的注册即将服务信息注册到注册中心。针对服务暴露和注册的具体流程,可参见博主之前的一篇文章  https://www.cnblogs.com/zzq6032010/p/11275478.html ,讲述的比较详细,暂不赘述。

       注重提一下的是Dubbo启动服务暴露和注册的时机,是采用的事件驱动来触发的,跟SpringBoot有点神似。这种通过事件驱动来触发特定逻辑的方式,在实际开发工作中也可以灵活使用。

    二、服务引入及SPI

        对于Dubbo的SPI自适应扩展,可参见博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11219611.html,但此篇文章当时写的比较浅显,还未悟得全部。

    下面以Protocol类为例,看一下在ServiceConfig类中的成员变量  Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 是什么样子。

     1 package org.apache.dubbo.rpc;
     2 import org.apache.dubbo.common.extension.ExtensionLoader;
     3 public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
     4 
     5     public void destroy()  {
     6         throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
     7     }
     8 
     9     public int getDefaultPort()  {
    10         throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    11     }
    12 
    13     public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
    14         if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
    15         if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
    16         org.apache.dubbo.common.URL url = arg0.getUrl();
    17         String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    18         if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
    19         org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    20         return extension.export(arg0);
    21     }
    22 
    23     public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
    24         if (arg1 == null) throw new IllegalArgumentException("url == null");
    25         org.apache.dubbo.common.URL url = arg1;
    26         String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    27         if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
    28         org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    29         return extension.refer(arg0, arg1);
    30     }
    31 
    32     public java.util.List getServers()  {
    33         throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    34     }
    35 }

    这就是getAdaptiveExtension()之后得到的代理类,可见在初始化ServiceConfig时先获取的protocol只是一个代理Protocol类,程序运行时再通过传入的Url来判断具体使用哪个Protocol实现类。这才是SPI自适应扩展的精髓所在。

    除此之外,在通过getExtension方法获取最终实现类时,还要经过wrapper类的包装。详见ExtensionLoader类中的如下方法:

     1 private T createExtension(String name) {
     2         Class<?> clazz = getExtensionClasses().get(name);
     3         if (clazz == null) {
     4             throw findException(name);
     5         }
     6         try {
     7             T instance = (T) EXTENSION_INSTANCES.get(clazz);
     8             if (instance == null) {
     9                 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
    10                 instance = (T) EXTENSION_INSTANCES.get(clazz);
    11             }
    12             injectExtension(instance);
    13             Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    14             if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    15                 for (Class<?> wrapperClass : wrapperClasses) {
    16                     instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    17                 }
    18             }
    19             initExtension(instance);
    20             return instance;
    21         } catch (Throwable t) {
    22             throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
    23                     type + ") couldn't be instantiated: " + t.getMessage(), t);
    24         }
    25     }

    如果接口存在包装类,则在第16行进行wrapper类的处理,将当前instance封装进包装类中,再返回包装类的实例,即通过这一行代码实现了扩展类的装饰器模式改造。

    此处同样以Protocol类为例,Url中的协议是registry,那么我最终执行到RegistryProtocol的export方法时栈调用路径是这样的:

     即中间经过了三层Wrapper的封装,每层都有自己特定的功能,且各层之间互不影响。Dubbo在很多自适应扩展接口处加了类似这样的装饰扩展,程序的可扩展设计还可以这样玩,Interesting!

    服务引入的流程大体是这样的:消费端从注册中心获取服务端信息,封装成Invoker,再封装成代理类注入消费端Spring容器。流程比较简单,可自行根据上一节的内容debug调试。

    三、服务调用的疑问

        之前未看Dubbo源码时一直有一个疑问:dubbo的消费端代理类调用服务端接口进行消费时,是通过netty将消息发送过去的,服务端在接收到消息后,是如何调用的服务端目标类中的方法?反射吗?反射可以调用到方法,但是没法解决依赖的问题,而且正常情况服务端调用应该也是Spring容器中已经实例化好的的服务对象,那是如何通过netty的消息找到Spring中的对象的?

        实际dubbo处理的很简单,只要在服务暴露的时候将暴露的服务自己存起来就好了,等消费端传过来消息的时候,直接去map里面取,取到的就是Spring中封装的那个服务对象,very easy。

       

                                         服务调用过程草图

        如上图所示,服务调用的流程大体是这样的:调用之后通过client远程连接到server,在server端维护了暴露服务的一个map,服务端接收到请求后去map获取Exporter,exporter中有服务端封装好的Invoker,持有Spring中的服务bean,最终完成调用。中间还涉及很多细节,比如netty的封装与调用,序列化反序列化,负载均衡和容错处理等。

    小结

        Dubbo作为一个优秀的rpc服务框架,其优势不止在于它的rpc过程,还在于更多细节模块的实现以及可扩展的设计,比如序列化处理、负载均衡、容错、netty的线程调度、路由、字典...   内容挺多的,后面打算针对dubbo的四大负载均衡算法做一下研究,浅尝辄止,不求甚解!

  • 相关阅读:
    ROS安装
    安装octomap的问题与解决方案
    陀螺仪和加速度计MPU6050的单位换算方法
    概率基础
    Ubuntu使用多线程cmake时出现undefined reference to `pthread_create'
    C++中的static关键字的总结
    QSignalMapper的使用和使用场景
    Linux下C ,C ++, Qt开发环境
    void operator()()的功能
    C++11多线程编程--线程创建
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/12588538.html
Copyright © 2020-2023  润新知