解析Apache Dubbo的SPI实现机制

一、SPI

在Java中,SPI体现了面向接口编程的思想,满足开闭设计原则。

1.1、JDK自带SPI实现

从JDK1.6开始引入SPI机制后,可以看到很多使用SPI的案例,比如最常见的数据库驱动实现,在JDK中只定义了java.sql.Driver的接口,具体实现由各数据库厂商来提供。下面一个简单的例子来快速了解下Java SPI的使用方式:

1)定义一个接口

package com.vivo.study
public interface Car {
void getPrice();
}

2)接口实现

package com.vivo.study.impl

/**
 * 实现一
 *
 */
public class AudiCar implements Car {
    @Override
    public void getPrice() {
        System.out.println("Audi A6L's price is  500000 RMB.");
    }
}

package com.vivo.study.impl
/**
 * 实现二
 *
 */
public class BydCar implements Car {
    @Override
    public void getPrice() {
        System.out.println("BYD han's price is 220000 RMB.");
    }
}

3)挂载扩展类信息

在META-INF/services目录下以接口全名为文件名的文本文件,对应此处即在META-INF/services目录下创建一个文件名为com.vivo.study.Car的文件,文件内容如下:

com.vivo.study.impl.AudiCar
com.vivo.study.impl.BydCar

4)使用

public class SpiDemo {
public static void main(String[] args) {
        ServiceLoader<Car> load = ServiceLoader.load(Car.class);
        Iterator<Car> iterator = load.iterator();
while (iterator.hasNext()) {
            Car car = iterator.next();
            car.getPrice();
        }
    }
}

上面的例子简单的介绍了JDK SPI机制的使用方式,其中最关键的类为ServiceLoader,通过ServiceLoader类来加载接口的实现类,ServiceLoader是Iterable接口的实现类,对于ServiceLoader加载的详细过程此处不展开。

JDK对SPI的加载实现存在一个较为突出的小缺点,无法按需加载实现类,通过ServiceLoader.load加载时会将文件中的所有实现都进行实例化,如果想要获取具体某个具体的实现类需要进行遍历判断。

1.2、Dubbo SPI

SPI扩展是Dubbo的最大的优点之一,支持协议扩展、调用拦截扩展、引用监听扩展等等。在Dubbo中,根据不同的扩展定位,扩展文件分别被放置在META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个路径下。

Dubbo中有直接使用JDK SPI实现的方式,比如org.apache.dubbo.common.extension.LoadingStrategy放在META-INF/services/路径下,但大多情况下都是使用其自身对JDK SPI的实现的一种优化方式,可称为Dubbo SPI,也就是本文要讲解的点。

相比于JDK的SPI的实现,Dubbo SPI具有以下特点:

  • 配置形式更灵活:支持以key:value的形式在文件里配置类似name:xxx.xxx.xxx.xx,后续可以通过name来进行扩展类按需精准获取。
  • 缓存的使用:使用缓存提升性能,保证一个扩展实现类至多会加载一次。
  • 对扩展类细分扩展:支持扩展点自动包装(Wrapper)、扩展点自动装配、扩展点自适应(@Adaptive)、扩展点自动激活(@Activate)。

Dubbo对扩展点的加载主要由ExtensionLoader这个类展开。

二、加载-ExtensionLoader

ExtensionLoader在Dubbo里的角色类似ServiceLoader在JDK中的存在,用于加载扩展类。在Dubbo源码里,随处都可以见到ExtensionLoader的身影,比如在服务暴露里的关键类ServiceConfig中等,弄清楚ExtensionLoader的实现细节对阅读Dubbo源码有很大的帮助。

2.1、获取ExtensionLoader的实例

ExtensionLoader没有提供共有的构造函数,

只能通过ExtensionLoader.getExtensionLoader(Class type)来获取ExtensionLoader实例。

public

    // ConcurrentHashMap缓存,key -> Class value -> ExtensionLoader实例
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 检查是否是接口,如果不是则抛出异常
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }

        // 检查接口是否是被@SPI注解修饰,如果没有则抛出异常
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        // 从缓存里取,没有则初始化一个并放入缓存EXTENSION_LOADERS中
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
}

上面的代码展示了获取ExtensionLoader实例的过程,可以看出,每一个被@SPI修饰的接口都会对应同一个ExtensionLoader实例,且对应ExtensionLoader只会被初始化一次,并缓存在ConcurresntHashMap中。

2.2、加载扩展类

加载扩展类入口,当使用ExtensionLoader时,getExtensionName、getActivateExtension或是getDefaultExtension都要经过getExtensionClasses方法来加载扩展类,如下图;

getExtensionClasses方法调用的路径如下图,getExtensionClasses是加载扩展类的一个起点,会首先从缓存中获取,如果缓存中没有则通过loadExtensionClasses方法来加载扩展类,所以说实际上的加载逻辑入口在loadExtensionClasses。

getExtensionClasses

  |->loadExtensionClasses

    |->cacheDefaultExtensionName

    |->loadDirectory

      |->loadResource

        |->loadClass

2.2.1、loadExtensionClasses加载扩展类

由于整个加载过程设计的源码较多,因此用一个流程图来进行描述,具体细节可以结合源码进行查看。

loadExtensionClasses主要做了以下这几件事:

默认扩展名:

抽取默认扩展实现名并缓存在ExtensionLoader里的cachedDefaultName,默认扩展名配置通过@SPI注解在接口上配置,如配置@SPI("defaultName")则默认扩展名为defaultName。

加载扩展类信息:

从META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个路径下寻找以类的全路径名命名的文件,并逐行读取文件里的内容。

加载class并缓存:

对扩展类分为自适应扩展实现类(被@Adaptive修饰的实现类)、包装类(拥有一个只有一个为这个接口类型的参数的构造方法)、普通扩展类,其中普通扩展类中又包含自动激活扩展类(被@Activate修饰的类)和真普通的类,对自适应扩展实现类、包装类、自动激活扩展类这三种类型的类分别加载并分别缓存到cachedAdaptiveClass、cachedActivates、cachedWrapperClasses。

返回结果Map<String, Class<?>>:

结果返回Map,其中key对应扩展文件里配置的name,value对应扩展的类class,最后在getExtensionClasses方法里会将此结果放入缓存cachedClasses中。此结果Map中包含除了自适应扩展实现类和包装实现类的其他所用的扩展类名与对应类的映射关系。

通过loadExtensionClasses方法把扩展类(Class对象)都加载到相应的缓存中,是为了方便后面实例化扩展类对象,通过newInstance()等方法来实例化。

2.2.2、扩展包装类

什么是扩展包装类?是不是类名结尾包含了Wrapper的类就是扩展包装类?

在Dubbo SPI接口的实现扩展类中,如果此类包含一个此接口作为参数的构造方法,则为扩展包装类。扩展包装类的作用是持有具体的扩展实现类,可以一层一层的被包装,作用类似AOP。

包装扩展类的作用是类似AOP,方便扩展增强。具体实现代码如下:

从代码中可以得出,可以通过boolean wrap选择是否使用包装类,默认情况下为true;如果有扩展包装类,实际的实现类会被包装类按一定的顺序一层一层包起来。

如Protocol的实现类ProtocolFilterWrapper、ProtocolListenerWrapper都是扩展包装类。

2.2.3、自适应扩展实现类

@Adaptive

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}

从源码以及源码注释中可以得出以下几点:

  • Adaptive是一个注解,可以修饰类(接口,枚举)和方法。
  • 此注解的作用是为ExtensionLoader注入扩展实例提供有用的信息。

从注释中理解value的作用:

  • value可以决定选择使用具体的扩展类。
  • 通过value配置的值key,在修饰的方法的入参org.apache.dubbo.common.URL中通过key获取到对应的值value,根据value作为extensionName去决定使用对应的扩展类。
  • 如果通过2没有找到对应的扩展,会选择默认的扩展类,通过@SPI配置默认扩展类。

@Adaptive简单例子

由于@Adaptive修饰类时比较好理解,这里举一个@Adaptive修饰方法的例子,使用@Adaptive修饰方法的这种情况在Dubbo也是随处可见。

/**
* Dubbo SPI 接口
*/
@SPI("impl1")
public interface SimpleExt {
    @Adaptive({"key1", "key2"})
    String yell(URL url, String s);
}

如果调用

ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension().yell(url, s)方法,最终调用哪一个扩展类的实例去执行yell方法的流程大致为:先获取扩展类的名称extName(对应上面说的name:class中的name),然后通过extName来获取对应的类Class,再实例化进行调用。所以关键的步骤在怎么得到extName,上面的这个例子得到extName的流程为:

  • 通过url.getParameters.get("key1")获取,
  • 没有获取到则用url.getParameters.get("key2"),如果还是没有获取到则使用impl1对应的实现类,最后还是没有获取到则抛异常IllegalStateException。

可以看出,@Adaptive的好处就是可以通过方法入参决定具体调用哪一个实现类。下面会对@Adaptive的具体实现进行详细分析。

@Adaptive加载流程

流程关键点说明:

1)黄色标记的,cachedAdaptiveClass是在ExtensionLoader#loadClass方法中加载Extension类时缓存的。

2)绿色标记的,如果Extension类中存在被@Adaptive修饰的类时会使用该类来初始化实例。

3)红色标记的,如果Extension类中不存在被@Adaptive修饰的类时,则需要动态生成代码,通过javassist(默认)来编译生成Xxxx$Adaptive类来实例化。

4)实例化后通过injectExtension来将Adaptive实例的Extension注入(属性注入)。

后续围绕上述的关键点3详细展开,关键点4此处不展开。

动态生成Xxx$Adaptive类:下面的代码为动态生成Adaptive类的相关代码,具体生成代码的细节在AdaptiveClassCodeGenerator#generate中

public class ExtensionLoader<T> {
// ...
private Class<?> getAdaptiveExtensionClass() {
// 根据对应的SPI文件加载扩展类并缓存,细节此处不展开
        getExtensionClasses();
// 如果存在被@Adaptive修饰的类则直接返回此类
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
        }
// 动态生成Xxxx$Adaptive类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
private Class<?> createAdaptiveExtensionClass() {
// 生成Xxxx$Adaptive类代码,可自行加日志或断点查看生成的代码
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
// 获取动态编译器,默认为javassist
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
    }
}

AdaptiveClassCodeGenerator#generate生成code的方式是通过字符串拼接,大量使用String.format,整个代码过程比较繁琐,可通过debug去了解细节。

最关键的的部分是生成被@Adaptive修饰的方法的内容,也就是最终调用实例的@Adaptive方法时,可通过参数来动态选择具体使用哪个扩展实例。下面对此部分进行分析:

public class AdaptiveClassCodeGenerator {
// ...
/**
     * generate method content
     */
private String generateMethodContent(Method method) {
// 获取方法上的@Adaptive注解
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// 方法时没有@Adaptive注解,生成不支持的代码
return generateUnsupported(method);
        } else {
// 方法参数里URL是第几个参数,不存在则为-1
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
if (urlTypeIndex != -1) {
// Null Point check
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
// did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));
            }
// 获取方法上@Adaptive配置的value
// 比如 @Adaptive({"key1","key2"}),则会返回String数组{"key1","key2"}
// 如果@Adaptive没有配置value,则会根据简写接口名按驼峰用.分割,比如SimpleExt对应simple.ext
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 参数里是否存在org.apache.dubbo.rpc.Invocation
            boolean hasInvocation = hasInvocationArgument(method);
            code.append(generateInvocationArgumentNullCheck(method));
// 生成String extName = xxx;的代码 ,extName用于获取具体的Extension实例
            code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
            code.append(generateExtNameNullCheck(value));
            code.append(generateExtensionAssignment());
// return statement
            code.append(generateReturnAndInvocation(method));
        }
return code.toString();
    }
}

上述生成Adaptive类的方法内容中最关键的步骤在生成extName的部分,也就是generateExtNameAssignment(value,hasInvocation),此方法if太多了(有点眼花缭乱)。

以下列举几个例子对此方法的实现流程进行简单展示:假设方法中的参数不包含org.apache.dubbo.rpc.Invocation,包含org.apache.dubbo.rpc.Invocation的情况会更加复杂。

1)方法被@Adaptive修饰,没有配置value,且在接口@SPI上配置了默认的实现

@SPI("impl1")
public interface SimpleExt {
@Adaptive
String echo(URL url, String s);
}

对应生成extName的代码为:

String extName = url.getParameter("simple.ext", "impl1")

2)方法被@Adaptive修饰,没有配置value,且在接口@SPI上没有配置默认的实现

@SPI("impl1")
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

对应生成extName的代码为:

String extName = url.getParameter( "simple.ext")

3)方法被@Adaptive修饰,配置了value(假设两个,依次类推),且在接口@SPI上配置了默认的实现

@SPI
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

对应生成extName的代码为:

String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));

4)方法被@Adaptive修饰,配置了value(假设两个,依次类推),且在接口@SPI没有配置默认的实现

@SPI
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

对应生成extName的代码为:

String extName = url.getParameter("key1", url.getParameter("key2"));

完整的生成类可参见附录。

2.2.4、自动激活扩展类

如果你有扩展实现过Dubbo的Filter,那么一定会对@Activate很熟悉。@Activate注解的作用是可以通过给定的条件来自动激活扩展实现类,通过ExtensionLoader#getActivateExtension(URL,String, String)方法可以找到指定条件下需要激活的扩展类列表。

下面以一个例子来对@Activate的作用进行说明,在Consumer调用Dubbo接口时,会经过消费方的过滤器链以及提供方的过滤器链,在Provider暴露服务的过程中会拼接需要使用哪些Filter。

对应源码中的位置在ProtocolFilterWrapper#buildInvokerChain(invoker, key, group)方法中。

// export:key-> service.filter ; group-> provider
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    // 在Provider暴露服务服务export时,会根据获取Url中的service.filter对应的值和group=provider来获取激活对应的Filter
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
}

ExtensionLoader#getActivateExtension(URL, String, String)是怎么根据条件来自动激活对应的扩展类列表的可以自行查看该方法的代码,此处不展开。

三、总结

本文主要对Dubbo SPI机制的扩展类加载过程通过ExtensionLoader类源码来进行总结,可以概况为以下几点:

1.Dubbo SPI结合了JDK SPI的实现,并在此基础上进行优化,如精准按需加载扩展类、缓存提升性能。

2.分析ExtensionLoader加载扩展类的过程,加载META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个路径下的文件,并分类缓存在ExtensionLoader实例。

3.介绍扩展包装类及其实现过程,扩展包装类实现了类似AOP的功能。

4.自适应扩展类,分析@Adptive修饰方法时动态生成Xxx$Adaptive类的过程,以及通过参数自适应选择扩展实现类完成方法调用的案例介绍。

简单介绍自动激活扩展类及@Activate的作用。

四、附录

Xxx$Adaptive完整案例

@SPI接口定义

@SPI("impl1")
public interface SimpleExt {
    // @Adaptive example, do not specify a explicit key.
    @Adaptive
    String echo(URL url, String s);

    @Adaptive({"key1", "key2"})
    String yell(URL url, String s);

    // no @Adaptive
    String bang(URL url, int i);
}

生成的Adaptive类代码

package org.apache.dubbo.common.extension.ext1;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt {

    public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
        return extension.yell(arg0, arg1);
    }

    public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("simple.ext", "impl1");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }

    public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) {
        throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!");
    }

}

以上就是解析Apache Dubbo的SPI实现机制的详细内容,更多关于Apache Dubbo的SPI实现机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • IDEA整合Dubbo+Zookeeper+SpringBoot实现

    本文主要介绍了IDEA整合Dubbo+Zookeeper+SpringBoot实现,分享给大家,具体如下: 放上一张Dubbo的架构图 刚刚学过SpringCloud,Dubbo虽然没有Spring家族那么强悍,但始终不能违背Dubbo是一款高性能优秀的RPC框架. click官网地址了解更多 自己也是初学者,想自己动手做一个简单的demo,于是上网查资料,发现针对那种入门的案例,网上主要有两个比较经典的,一个是尚硅谷雷神版,还有一个是官网推荐的入门 案例. 雷神的Dubbo视频教程大神讲解 视

  • Java Dubbo框架知识点梳理

    1.Dubbo是什么 Dubbo 是一个分布式.高性能.透明化的 RPC 服务框架,提供服务自动注册.自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成. RPC 指的是远程调用协议,也就是说两个服务器交互数据. 2.Dubbo的由来 互联网的快速发展,Web应用程序的规模不断扩大,一般会经历如下四个发展阶段. 单一应用架构:当网站流量很小时,只需一个应用,将所有功能都部署在一起即可. 垂直应用架构:当访问量逐渐增大,单一应用按照有业务线拆成多个应用,以提升效率.此时,用于加速前端

  • 详解SPI在Dubbo中的应用

    目录 一.概述 二.JDK自带SPI 2.1.代码示例 2.2.简单分析 三.SPI与双亲委派 3.1.SPI加载到何处 3.2.SPI是否破坏了双亲委派 四.Dubbo SPI 4.1.基本概念 4.2.代码示例 4.3.源码分析 五.总结 一.概述 SPI 全称为 Service Provider Interface,是一种模块间组件相互引用的机制.其方案通常是提供方将接口实现类的全名配置在classPath下的指定文件中,由调用方读取并加载.这样需要替换某个组件时,只需要引入新的JAR包并

  • Apache Dubbo的SPI机制是如何实现的

    一.SPI 1.1 JDK自带SPI实现 从JDK1.6开始引入SPI机制后,可以看到很多使用SPI的案例,比如最常见的数据库驱动实现,在JDK中只定义了java.sql.Driver的接口,具体实现由各数据库厂商来提供.下面一个简单的例子来快速了解下Java SPI的使用方式: 1)定义一个接口 package com.vivo.study public interface Car { void getPrice(); } 2)接口实现 package com.vivo.study.impl

  • 仅用5分钟极速入门Dubbo使用教程

    Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了Dubbo就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册) 其核心部分包含: 1. 远程通讯: 提供对多种基于长连接的NIO框架抽

  • 解析Apache Dubbo的SPI实现机制

    一.SPI 在Java中,SPI体现了面向接口编程的思想,满足开闭设计原则. 1.1.JDK自带SPI实现 从JDK1.6开始引入SPI机制后,可以看到很多使用SPI的案例,比如最常见的数据库驱动实现,在JDK中只定义了java.sql.Driver的接口,具体实现由各数据库厂商来提供.下面一个简单的例子来快速了解下Java SPI的使用方式: 1)定义一个接口 package com.vivo.study public interface Car { void getPrice(); } 2)

  • 深入解析Apache Hudi内核文件标记机制

    目录 1. 摘要 2. 为何引入Markers机制 3. 现有的直接标记机制及其局限性 4. 基于时间线服务器的标记机制提高写入性能 5. 标记相关的写入选项 6. 性能 7. 总结 1. 摘要 Hudi 支持在写入时自动清理未成功提交的数据.Apache Hudi 在写入时引入标记机制来有效跟踪写入存储的数据文件. 在本博客中,我们将深入探讨现有直接标记文件机制的设计,并解释了其在云存储(如 AWS S3.Aliyun OSS)上针对非常大批量写入的性能问题. 并且演示如何通过引入基于时间轴服

  • Java和Dubbo的SPI机制原理解析

    SPI: 简单理解就是,你一个接口有多种实现,然后在代码运行时候,具体选用那个实现,这时候我们就可以通过一些特定的方式来告诉程序寻用那个实现类,这就是SPI. JAVA的SPI 全称为 Service Provider Interface,是一种服务发现机制.它是约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名. 这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包

  • 深入解析Spring Boot 的SPI机制详情

    目录 简介 Java SPI实现 示例说明 创建动态接口 实现类1 实现类2 相关测试 运行结果 源码分析 Spring SPI Spring 示例 定义接口 相关实现 相关测试类 输出结果 源码分析 总结 简介 SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要用于框架中开发,例如Dubbo.Spring.Common-Logging,JDBC等采用采用SPI机制,针对同一接口采用不同的实现提供给不同的用户,从

  • Apache Hudi灵活的Payload机制硬核解析

    1.摘要 Apache Hudi 的Payload是一种可扩展的数据处理机制,通过不同的Payload我们可以实现复杂场景的定制化数据写入方式,大大增加了数据处理的灵活性.Hudi Payload在写入和读取Hudi表时对数据进行去重.过滤.合并等操作的工具类,通过使用参数 "hoodie.datasource.write.payload.class"指定我们需要使用的Payload class.本文我们会深入探讨Hudi Payload的机制和不同Payload的区别及使用场景. 2

  • 使用 Apache Dubbo 实现远程通信(微服务架构)

    目录 前言 1. Dubbo 基础知识 1.1 Dubbo 是什么 1.2 Dubbo 的架构图 1.3 Spring Cloud 与 Dubbo 的区别 1.4 Dubbo 的特点 1.5 Dubbo 的 6 种容错模式容错模式 1.7 主机绑定规则 2. 构建 Dubbo 服务提供方 2.1 构建服务接口模块 2.2 添加 pom.xml 依赖文件 2.3 修改 application.yml 配置文件 2.4 在主程序类上添加注解 2.5 实现 2.1 定义的接口 3. 构建 Dubbo

  • Spring Boot 整合 Apache Dubbo的示例代码

    Apache Dubbo是一款高性能.轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 注意,是 Apache Dubbo,不再是 Alibaba Dubbo.简单来说就是 Alibaba 将 Dubbo 移交给 Apache 开源社区进行维护.参见 dubbo-spring-boot-project Spring Boot 系列:整合 Alibaba Dubbo 一.本文示例说明 1.1 框架版本Dubbo 版本

  • 一文解析Apache Avro数据

    摘要:本文将演示如果序列化生成avro数据,并使用FlinkSQL进行解析.  Avro官方文档所写,http://avro.apache.org/docs/current/index.html. Avro简介 avro是一个数据序列化系统 提供了: 丰富的数据结构 紧凑的,快速的,二进制的数据格式 一种文件格式,用于存储持久化数据 远程过程调用系统(RPC) 和动态语言的简单交互.并不需要为数据文件读写产生代码,也不需要使用或实现RPC协议.代码生成是一种优化方式,但是只对于静态语言有意义.

  • 一文解析Apache Avro数据

    摘要:本文将演示如果序列化生成avro数据,并使用FlinkSQL进行解析. Avro官方文档所写,http://avro.apache.org/docs/current/index.html. Avro简介 avro是一个数据序列化系统 提供了: 丰富的数据结构 紧凑的,快速的,二进制的数据格式 一种文件格式,用于存储持久化数据 远程过程调用系统(RPC) 和动态语言的简单交互.并不需要为数据文件读写产生代码,也不需要使用或实现RPC协议.代码生成是一种优化方式,但是只对于静态语言有意义. 技

随机推荐