dubbo与http接口区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dubbo与http接口区别相关的知识,希望对你有一定的参考价值。

参考技术A OSI的七层网络结构模型
第一层:应用层。定义了用于在网络中进行通信和传输数据的接口;
第二层:表示层。定义不同的系统中数据的传输格式,编码和解码规范等;
第三层:会话层。管理用户的会话,控制用户间逻辑连接的建立和中断;
第四层:传输层。管理着网络中的端到端的数据传输;
第五层:网络层。定义网络设备间如何传输数据;
第六层:链路层。将上面的网络层的数据包封装成数据帧,便于物理层传输;
第七层:物理层。这一层主要就是传输这些二进制数据。

实际应用过程中,五层协议结构里面将表示层、会话层合并到了应用层。因为HTTP是应用层协议,而TCP/IP是传输层协议,因此使用TCP/IP协议的DUBBO自然性能要比HTTP协议快。

dubbo默认使用socket长连接,即首次访问建立连接以后,后续网络请求使用相同的网络通道
http1.1协议默认使用短连接,每次请求均需要进行三次握手,而http2.0协议开始将默认socket连接改为了长连接

Dubbo的SPI实现以及与JDK实现的区别



在 Java 里, 为了规范开发,制定了大量的「规范」与「标准」,这些上层的内容,大多是以接口的形式提供出来。那这些接口最终实现是谁呢,在哪里呢?


规范并不关心这个。


所谓规范,是指定了一系列内容,来指导我们的开发实现。比如 Servlet规范对于 Servlet 的行为做了说明,具体实现时,可以是 Tomcat,可以是Jetty 等等。


再比如 Java 的 JDBC 规范,规定了 Driver 提供者需要实现的内容,但具体是 Oracle,或者MySQL 都可以支持。关于JDBC 可以看之前一篇文章()。在之前我们可以通过 Class.forName来进行Driver 具体实现类的加载。从JDK1.6开始,官方提供了一个名为 「SPI」 的机制,来更方便快捷的进行对应实现类的加载,不需要我们关心。我们所需要做的,只需要将包含实现类的 JAR 文件放到 classpath中即可。


正好最近读了一些Dubbo的源码,其中有 Dubbo 的不同于JDK的另一种 SPI实现。所以这篇我们来看 Dubbo 的 「SPI」实现以及与 JDK 实现的区别。


首先,什么是 SPI 呢?


SPI(Service Provider Interfaces), 可以理解成一个交给第三方实现的API。JDK文档这样描述


A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.


在Java 中使用到SPI的这些地方:

  • JDBC

  • JNDI

  • Java XML Processing API

  • Locael

  • NIO Channel Provider

  • ……


通过这种SPI 的实现形式,我们的应用仿佛有了可插拔的能力。

我们之前的文章 里,也分析了容器中是如何做到可插拔的。


JDK中的SPI 是怎样实现的呢?


在JDK中包含一个SPI最核心的类:ServiceLoader,在需要加载Provider类的时候,我们所要做的是:

ServiceLoader.load(Provider.class);


在JDK中规范了 Service Provider的路径,所有 Provider必须在JAR文件的META-INF/services目录下包含一个文件,文件名就是我们要实现的Service的名称全路径。比如我们熟悉的JDBC 的MySQL实现, 在mysql-connector中,就有这样一个文件

META-INF/services/java.sql.Driver

Dubbo的SPI实现以及与JDK实现的区别


这些provider是什么时候加载的呢?


由于Provider 的加载和初始化是Lazy的实现,所以需要的时候,可以遍历Provider 的 Iterator,按需要加载,已经加载的会存放到缓存中。


但有些实现不想Lazy,就直接在 ServiceLoader 的load执行之后直接把所有的实现都加载和初始化了,比如这次说的JDBC,所以这里在Tomcat里有个处理内存泄漏的,可以查看之前的文章()


继续说回具体的加载时机。我们一般在Spring 的配置中会增加一个datasource,这个数据源一般会在启动时做为一个Bean被初始化,此时数据源中配置的driver会被设置。

这些内容传入Bean中,会调用DriverManager的初始化


static {
   loadInitialDrivers();
    println("JDBC DriverManager initialized");
}


loadInitialDrivers 执行的的时候,除了ServiceLoader.load外,还进行了初始化


ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
    while(driversIterator.hasNext()) {
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}
return null;



我们再来看 Dubbo 的SPI实现方式。如果你能看下 Dubbo 的源码就会发现,实现时并没有使用 JDK 的SPI,而是自已设计了一种。 


我们以Main class启动来看看具体的实现。

我们从使用的入口处来看,第一步传入一个接口, 然后再传入期待的实现的名称

1 SpringContainer container = (SpringContainer) ExtensionLoader.getExtensionLoader(Container.class).getExtension("spring");

这里传入的是Container.class, 期待的实现是spring

 1// synchronized in getExtensionClasses
2    private Map<String, Class<?>> loadExtensionClasses() {
3        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
4        if (defaultAnnotation != null) {
5            String value = defaultAnnotation.value();
6            if ((value = value.trim()).length() > 0) {
7                String[] names = NAME_SEPARATOR.split(value);
8                if (names.length > 1) {
9                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
10                            + ": " + Arrays.toString(names));
11                }
12                if (names.length == 1) cachedDefaultName = names[0];
13            }
14        }
15
16        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
17        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
18        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
19        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
20        return extensionClasses;
21    }

共从三个地方加载扩展的class

  • DUBBO_INTERNAL_DIRECTORY META-INF/dubbo/internal/

  • DUBBO_DIRECTORY META-INF/dubbo/

  • SERVICES_DIRECTORY META-INF/services/



 1private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
2        String fileName = dir + type.getName();
3        try {
4            Enumeration<java.net.URL> urls;
5            ClassLoader classLoader = findClassLoader();
6            if (classLoader != null) {
7                urls = classLoader.getResources(fileName);
8            } else {
9                urls = ClassLoader.getSystemResources(fileName);
10            }
11            if (urls != null) {
12                while (urls.hasMoreElements()) {
13                    java.net.URL resourceURL = urls.nextElement();  
14                    loadResource(extensionClasses, classLoader, resourceURL);
15                }
16            }
17        } catch (Throwable t) {
18            logger.error("Exception when load extension class(interface: " +
19                    type + ", description file: " + fileName + ").", t);
20        }
21    }

这里通过classLoader,寻找符合传入的特定名称的文件,java.net.URL resourceURL = urls.nextElement();
此时会得到一个包含该文件的URLPath, 再通过loadResource,将资源加载
此时得到的文件内容是
spring=com.alibaba.dubbo.container.spring.SpringContainer
再进一步,将等号后面的class加载,即可完成。

loadClass时,并不是直接通过类似Class.forName等形式加载,而是下面这个样子:

 1private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
2        if (!type.isAssignableFrom(clazz)) {
3            throw new IllegalStateException("Error when load extension class(interface: " +
4                    type + ", class line: " + clazz.getName() + "), class "
5                    + clazz.getName() + "is not subtype of interface.");
6        }
7        if (clazz.isAnnotationPresent(Adaptive.class)) {
8            if (cachedAdaptiveClass == null) {
9                cachedAdaptiveClass = clazz;
10            } else if (!cachedAdaptiveClass.equals(clazz)) {
11                throw new IllegalStateException("More than 1 adaptive class found: "
12                        + cachedAdaptiveClass.getClass().getName()
13                        + ", " + clazz.getClass().getName());
14            }
15        } else if (isWrapperClass(clazz)) {
16            Set<Class<?>> wrappers = cachedWrapperClasses;
17            if (wrappers == null) {
18                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
19                wrappers = cachedWrapperClasses;
20            }
21            wrappers.add(clazz);
22        } else {
23            clazz.getConstructor();
24            if (name == null || name.length() == 0) {
25                name = findAnnotationName(clazz);
26                if (name.length() == 0) {
27                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
28                }
29            }
30            String[] names = NAME_SEPARATOR.split(name);
31            if (names != null && names.length > 0) {
32                Activate activate = clazz.getAnnotation(Activate.class);
33                if (activate != null) {
34                    cachedActivates.put(names[0], activate);
35                }
36                for (String n : names) {
37                    if (!cachedNames.containsKey(clazz)) {
38                        cachedNames.put(clazz, n);
39                    }
40                    Class<?> c = extensionClasses.get(n);
41                    if (c == null) {
42                        extensionClasses.put(n, clazz);
43                    } else if (c != clazz) {
44                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
45                    }
46                }
47            }
48        }
49    }

加载之后,需要对class进行初始化,此时直接newInstance一个,再通过反射注入的方式将对应的属性设置进去。

 1private 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 (wrapperClasses != null && !wrapperClasses.isEmpty()) {
15                for (Class<?> wrapperClass : wrapperClasses) {
16                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
17                }
18            }
19            return instance;
20        } catch (Throwable t) {
21            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
22                    type + ")  could not be instantiated: " + t.getMessage(), t);
23        }
24    }
 1private T injectExtension(T instance{
2        try {
3            if (objectFactory != null) {
4                for (Method method : instance.getClass().getMethods()) {
5                    if (method.getName().startsWith("set")
6                            && method.getParameterTypes().length == 1
7                            && Modifier.isPublic(method.getModifiers())) {
8                        Class<?> pt = method.getParameterTypes()[0];
9                        try {
10                            String property = method.getName().length() > 3 ? method.getName().substring(34).toLowerCase() + method.getName().substring(4) : "";
11                            Object object = objectFactory.getExtension(pt, property);
12                            if (object != null) {
13                                method.invoke(instance, object);
14                            }
15                        } catch (Exception e) {
16                            logger.error("fail to inject via method " + method.getName()
17                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
18                        }
19                    }
20                }
21            }
22        } catch (Exception e) {
23            logger.error(e.getMessage(), e);
24        }
25        return instance;
26    }



通过上面的描述我们看到,JDK 与 Dubbo的 SPI 实现上,虽然都是从JAR中加载对应的扩展,但还是有些明显的区别,比如:Dubbo 支持更多的加载路径,同时,并不是通过Iterator的形式,而是直接通过名称来定位具体的Provider,按需要加载,效率更高,同时支持Provider以类似IOC的形式提供等等。



关注『 Tomcat那些事儿  』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。

                       转发是最大的支持,谢谢


更多精彩内容:

一台机器上安装多个Tomcat 的原理(回复001)

监控Tomcat中的各种数据 (回复002)

启动Tomcat的安全机制(回复003)

乱码问题的原理及解决方式(回复007)

Tomcat 日志工作原理及配置(回复011)

web.xml 解析实现(回复 012)

线程池的原理( 回复 014)

Tomcat 的集群搭建原理与实现 (回复 015)

类加载器的原理 (回复 016)

类找不到等问题 (回复 017)

代码的热替换实现(回复 018)

Tomcat 进程自动退出问题 (回复 019)

为什么总是返回404? (回复 020)

...

以上是关于dubbo与http接口区别的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo 接口 与http 接口Pegasus接口

用dubbo+zookeeper+spring搭建一个简单的http接口程序

Dubbo协议

Dubbo接口测试Dubbo接口调用Jmeter调用Dubbo接口,Jmeter测试Dubbo接口

Dubbo接口测试Dubbo接口调用Jmeter调用Dubbo接口,Jmeter测试Dubbo接口

jmeter测试dubbo接口