Skip to content

1.5_深入插件

elevenqq edited this page Sep 30, 2018 · 2 revisions

插件和Task

plugin

  • 【插件体系】
    插件体系一般由两部分组成:Framework+Plugin,DataLink中的Framework主要指【TaskRuntime】,Plugin对应的是各种类型的【TaskReader&TaskWriter】
  • 【TaskRuntime】
    提供了Task的高层抽象、Task的运行时环境和Task的插件规范
  • 【TaskReader&TaskWriter】
    一个个具体的数据同步插件,遵从Task插件规范,功能自治,和TaskRuntime完全解耦,理论上插件数量可无限扩充
  • 【Task】
    DataLink中数据同步的基本单位是Task,一个Worker进程中可以运行一批Task,一个运行中的Task由一个TaskReader和至少一个TaskWriter组成,即有:
    1> 程序运行期,同一类型的插件在一个进程中可以有多个实例,实例个数取决于有多少个Task用到了该插件
    2> 程序运行期,插件的生命周期归属于Task,在不同的生命周期阶段,依照Task的配置信息或相关指令,进行创建、初始化、运行或销毁等操作
    3> 理论上,TaskReader和TaskWriter可动态任意组合(能否组合,主要取决于待组合的TaskWriter能否适配TaskReader的Record类型)
    4> 理论上,每新增一种插件,可支持的同步场景可以成倍数的增加(具体几倍,和插件类型和当前已有的插件数量有关系),举例如下所示:

    TaskReader插件 TaskWriter插件 同步场景
    MySqlTaskReader RdbmsTaskWriter Mysql → Rdbms
    HdfsTaskWriter Mysql → Hdfs
    ElasticSearchTaskWriter Mysql → ElasticSearch
    KafkaTaskWriter(新增) Mysql → Kafka
    HbaseTaskReader


    RdbmsTaskWriter Hbase → Rdbms
    HdfsTaskWriter Hbase → Hdfs
    ElasticSearchTaskWriter Hbase → ElasticSearch
    KafkaTaskWriter(新增) Hbase → Kafka
    MongodbTaskReader
    (新增)


    RdbmsTaskWriter Mongodb → Rdbms
    HdfsTaskWriter Mongodb → Hdfs
    ElasticSearchTaskWriter Mongodb → ElasticSearch

    新增一个TaskReader,可新增的同步场景数量取决于已有TaskWriter的数量,反之亦然

插件和Domain

plugin-2

  • 【Contract】
    > Contract是针对某种类型的数据源定义的【数据模型】,是一份契约和规范,是最高层次的抽象,和编程语言无关,和具体平台无关,和DataLink也没有必然关系
    > Contract是TaskReader和TaskWriter可任意组合的关键,TaskReader输出Contract数据,TaskWriter输入Contract数据,互不感知,但都理解Contract定义的【数据模型】
    > Contract定义的【数据模型】的主要表现形式是Record,如:RdbEventRecord,HRecord
  • 【Adapt】
    > TaskReader
        负责输出Contract数据,适配模式很简单,一对一,直接把底层数据组装成对应的Record即可,如:MysqlTaskReader对应RdbEventRecord
    > TaskWriter
        负责输入Contract数据,并写入目标数据源
        两个思路:
        一种是【中心化统一适配】,即:和TaskReader类似,TaskWriter和Record也是一对一的关系(假设如:HBaseTaskWriter只能接收HRecord),由【TaskRuntime】
        负责把TaskReader输出的【数据模型】转换为TaskWriter需要的 【数据模型】;一种是【各自为政分散适配】,即:TaskWriter可以接收不同类型的【数据模型】,
        内部由不同的Handler把不同【数据模型】的数据写入目标数据源;
        

    adapt-1
       

    adapt-2
        思路对比:
        前者的思路,是在数据模型层面进行适配转换,那么TaskWriter实现起来就非常简单了,看上去很美好,但可行性非常低,不同【数据模型】之间的差异是非常大的,
        如果能这样转换,那也就没必要定义多种【数据模型】了,定义一种岂不更简单,所以Pass掉;后者是在数据操作层面进行适配转换,每个TaskWriter的第一层都是适配层,
        适配完之后再调用各个功能组件进行写入操作

  • 【Business Model】
    TaskReader和TaskWriter的正常运行,离不开各种参数和规则配置,【Business Model】对数据同步领域的常见场景常用功能进行了充分的总结和抽象,为所有插件提供
    了一套统一的规则模型,并提供了灵活的扩展机制,简化了设计和开发的难度

插件和ClassLoader

  • 【预备知识】
    >  类加载机制:http://sts.10101111.com/ucarsymphony/article/1492936004824
    >《深入理解Java虚拟机:JVM高级特性与最佳实践》,第6、7、8、9章
  • 【Why ClassLoader】
    Datalink-Worker进程中,每个类型的插件,都有自己独立的classloader和classpath
    原因很简单:class版本隔离的需要,DataLink中,可以开发任意多个插件,出现jar包冲突很正常,必须通过classloader隔离这些冲突
  • 【Datalink-ClassLoader机制简介】

    classloader-1

    classloader-2
    > BasicModules被PluginModules依赖,但打包时会各自独立,BasicModules输出到基础lib目录"../dl-worker/lib",PluginModules输出到每个插件自己独有的目录
    > BasicModules和其依赖的jar,被默认的AppClassLoader加载
    > PluginModules和其依赖的jar(不包括依赖的BasicModules),被PluginClassLoader加载
    > PluginClassLoader有两个实现类,DevPluginClassLoader和RelPluginClassLoader,前者在开发环境使用,后者在部署环境使用
    > PluginClassLoader的类加载并没有遵循【双亲委派模型】,而是优先加载插件自己的class,找不到时才委托父加载器加载
    > PluginClassLoader提供了白名单机制(目前还比较简单,不支持参数配置),白名单内的class会跳过插件目录,直接交由父加载器加载,该机制主要用来解决的问题场景是:
       BasicModules引用了A.jar,PluginModules也引用了A.jar,BasicModules用到了A.jar中的A-Class(可能是因为版本不同,或者插件有必须用自己jar包的需求),
       并且会把A-Class的对象实例传给PluginModules使用(注意:这个实例所属的Class对象是由AppClassLoader加载的),如果没有白名单机制,PluginModules
       拿到这个对象之后,其所属的PluginClassLoader会尝试从插件目录进行类加载(因为其cache中没有对应的Class对象),但加载出来的Class对象和A实例所属的
       Class对象并不匹配,会触发LinkageError

    举例:
       BasicModule引用了guava包,MysqlTaskReader插件也引用了guava包,我们有使用guava包中EventBus类的需求,EventBus首先被AppClassLoader加载和实例化,
       MysqlReader插件会拿到该EventBus对象并调用其方法,如果没有白名单机制,便会触发LinkageError异常

    > 接上文,BasicModules和PluginModules之间的共享对象应该遵循什么样的原则呢?
       

    share
       原则一:共享对象所属Class尽量是【JDK-Class】或【自定义Class】,这两种类型的Class插件目录中都没有,都由AppClassLoader加载,不存在LinkageError问题;
       原则二:非【JDK-Class】和非【自定义Class】类型的对象尽量不共享;
                     如果实在有需求,两个解决办法:
                     其一,可进行封装调用,原理如下所示:
                    

    reason
                     其二,利用白名单机制,强制使用BasicModule中的类(如果这些类,插件也有需求必须用自己jar包里的,只能自己实现一套机制了)   

插件和EventBus

eventBus

  • 每个插件除了需要实现数据同步,还必须包含一个PluginListener,PluginListener负责在EventBus上注册事件监听
  • 插件有自己独立的classloader,我们无法在框架层直接调用,通过事件提供的解耦机制,我们可以让插件提供更多【增值服务】,举例如下:
    > 解决Manager端Jar包冲突的问题。
       Manager想获取各种DB的元数据,无需引入各种Jar包,直接给Worker发指令,Worker构造一个特定事件并发送到EventBus,不同类型的插件接收到事件,并判断自己是否需要处理,
       是的话把元数据返回
    > 提供状态查询
       把插件内部的一些运行详情汇报给事件发送者