任务调度之Oozie详解

 利用shell脚本通过crontab进行定时执行,这样实现的话比较简单,但是随着多个job复杂度的提升,无论是协调工作还是任务监控都变得麻烦,我们选择使用oozie来对工作流进行调度监控。

1. Oozie的特点

  • Oozie是管理hadoop作业的调度系统
  • Oozie的工作流作业是一系列动作的有向无环图(DAG)
  • Oozie协调作业是通过时间(频率)和有效数据触发当前的Oozie工作流程
  • Oozie支持各种hadoop作业,例如:java map-reduce、Streaming map-reduce、pig、hive、sqoop和distcp等等,也支持系统特定的作业,例如java程序和shell脚本。
  • Oozie是一个可伸缩,可靠和可拓展的系统

2. 为什么选择Oozie

 在没有工作流调度系统之前,公司里面的任务都是通过 crontab 来定义的,时间长了后会发现很多问题:

1.大量的crontab任务需要管理
2.任务没有按时执行,各种原因失败,需要重试
3.多服务器环境下,crontab分散在很多集群上,光是查看log就很花时间

 于是,出现了一些管理crontab任务的调度系统,如 CronHub、CronWeb 等。
而在大数据领域,现在市面上常用的工作流调度工具有Oozie, Azkaban,Cascading,Hamake等,

我们往往把 Oozie和Azkaban来做对比:
 两者在功能方面大致相同,只是Oozie底层在提交Hadoop Spark作业是通过org.apache.hadoop的封装好的接口进行提交,而Azkaban可以直接操作shell语句。在安全性上可能Oozie会比较好。

  • 工作流定义: Oozie是通过xml定义的而Azkaban为properties来定义。
  • 部署过程: Oozie的部署相对困难些,同时它是从Yarn上拉任务日志。
  • 任务检测: Azkaban中如果有任务出现失败,只要进程有效执行,那么任务就算执行成功,这是BUG,但是Oozie能有效的检测任务的成功与失败。
  • 操作工作流: Azkaban使用Web操作。Oozie支持Web,RestApi,Java API操作。
  • 权限控制: Oozie基本无权限控制,Azkaban有较完善的权限控制,供用户对工作流读写执行操作。
  • 运行环境: Oozie的action主要运行在hadoop中而Azkaban的actions运行在Azkaban的服务器中。
  • 记录workflow的状态: Azkaban将正在执行的workflow状态保存在内存中,Oozie将其保存在Mysql中。
  • 出现失败的情况: Azkaban会丢失所有的工作流,但是Oozie可以在继续失败的工作流运行

3. Oozie-Azkaban详细对比

 对市面上最流行的两种调度器,给出以下详细对比。知名度比较高的应该是Apache Oozie,但是其配置工作流的过程是编写大量的XML配置,而且代码复杂度比较高,不易于二次开发。ooize相比azkaban是一个重量级的任务调度系统,功能全面,但配置使用也更复杂。如果可以不在意某些功能的缺失,轻量级调度器azkaban是很不错的候选对象。

从功能上来对比
  两者均可以调度linux命令、mapreduce、spark、pig、java、hive、java程序、脚本工作流任务
  两者均可以定时执行工作流任务

从工作流定义上来对比
  1、Azkaban使用Properties文件定义工作流
  2、Oozie使用XML文件定义工作流

从工作流传参上来对比
  1、Azkaban支持直接传参,例如${input}
  2、Oozie支持参数和EL表达式,例如${fs:dirSize(myInputDir)}

从定时执行上来对比
  1、Azkaban的定时执行任务是基于时间的
  2、Oozie的定时执行任务基于时间和输入数据

从资源管理上来对比
  1、Azkaban有较严格的权限控制,如用户对工作流进行读/写/执行等操作
  2、Oozie暂无严格的权限控制

从工作流执行上来对比
  1、Azkaban有三种运行模式:
    1.1、solo server mode:最简单的模式,数据库内置的H2数据库,管理服务器和执行服务器都在一个进程中运行,任务量不大项目可以采用此模式。
    1.2、 two server mode:数据库为mysql,管理服务器和执行服务器在不同进程,这种模式下,管理服务器和执行服务器互不影响
    1.3 、multiple executor mode:该模式下,执行服务器和管理服务器在不同主机上,且执行服务器可以有多个
    我这次采用第二种模式,管理服务器、执行服务器分进程,但在同一台主机上。
  2、Oozie作为工作流服务器运行,支持多用户和多工作流

从工作流管理上来对比
  1、Azkaban支持浏览器以及ajax方式操作工作流
  2、Oozie支持命令行、HTTP REST、Java API、浏览器操作工作流

另一版本区别:
  两者在功能方面大致相同,只是Oozie底层在提交Hadoop Spark作业是通过org.apache.hadoop的封装好的接口进行提交,而Azkaban可以直接操作shell语句。在安全性上可能Oozie会比较好。
  工作流定义:Oozie是通过xml定义的而Azkaban为properties来定义。
  部署过程: Oozie的部署太虐心了。有点难。同时它是从Yarn上拉任务日志。
        Azkaban中如果有任务出现失败,只要进程有效执行,那么任务就算执行成功,这是BUG,但是Oozie能有效的检测任务的成功与失败。
  操作工作流:Azkaban使用Web操作。Oozie支持Web,RestApi,Java API操作。
  权限控制: Oozie基本无权限控制,Azkaban有较完善的权限控制,入用户对工作流读写执行操作。
        Oozie的action主要运行在hadoop中而Azkaban的actions运行在Azkaban的服务器中。
  记录workflow的状态:Azkaban将正在执行的workflow状态保存在内存中,Oozie将其保存在Mysql中。
  出现失败的情况:Azkaban会丢失所有的工作流,但是Oozie可以在继续失败的工作流运行。

4. 主要概念

 我们在官网介绍中就注意到了,Oozie主要有三个主要概念,分别是 workflow,coordinator,bundle。
在这里插入图片描述
其中:
Workflow:工作流,由我们需要处理的每个工作组成,进行需求的流式处理。
Coordinator:协调器,可以理解为工作流的协调器,可以将多个工作流协调成一个工作流来进行处理。
Bundle:捆,束。将一堆的coordinator进行汇总处理。

 简单来说,workflow是对要进行的顺序化工作的抽象,coordinator是对要进行的顺序化的workflow的抽象,bundle是对一堆coordiantor的抽象。层级关系层层包裹。
Oozie本质是通过 launcher job 运行某个具体的Action。launcher job是一个MR作业,而且并不知道它将在集群的哪台机器上执行这个MR作业。

5. Job组成

一个oozie 的 job 一般由以下文件组成:
job.properties :记录了job的属性
workflow.xml :使用hPDL 定义任务的流程和分支
lib目录:用来执行具体的任务

5.1 Job.properties

KEY含义
nameNodeHDFS地址
jobTrackerjobTracker(ResourceManager)地址
queueNameOozie队列(默认填写default)
examplesRoot全局目录(默认填写examples)
oozie.usr.system.libpath是否加载用户lib目录(true/false)
oozie.libpath用户lib库所在的位置
oozie.wf.application.pathOozie流程所在hdfs地址(workflow.xml所在的地址)
user.name当前用户
oozie.coord.application.pathCoordinator.xml地址(没有可以不写)
oozie.bundle.application.pathBundle.xml地址(没有可以不写)

5.2 workflow.xml

这个文件是定义任务的整体流程的文件,官网wordcount例子如下:
在这里插入图片描述

<workflow-app name='wordcount-wf' xmlns="uri:oozie:workflow:0.1">
    <start to='wordcount'/>
    <action name='wordcount'>
        <map-reduce>
            <job-tracker>${jobTracker}</job-tracker>
            <name-node>${nameNode}</name-node>
            <configuration>
                <property>
                    <name>mapred.mapper.class</name>
                    <value>org.myorg.WordCount.Map</value>
                </property>
                <property>
                    <name>mapred.reducer.class</name>
                    <value>org.myorg.WordCount.Reduce</value>
                </property>
                <property>
                    <name>mapred.input.dir</name>
                    <value>${inputDir}</value>
                </property>
                <property>
                    <name>mapred.output.dir</name>
                    <value>${outputDir}</value>
                </property>
            </configuration>
        </map-reduce>
        <ok to='end'/>
        <error to='kill'/>
    </action>
    <kill name='kill'>
        <message>Something went wrong: ${wf:errorCode('wordcount')}</message>
    </kill/>
    <end name='end'/>
</workflow-app>

**[控制流节点]:主要包括start、end、fork、join等,其中fork、join成对出现,在fork展开。分支,最后在join结点汇聚
  ** start
  ** kill
  ** end
**[动作节点]:包括Hadoop任务、SSH、HTTP、EMAIL、OOZIE子任务
  ** ok --> end
  ** error --> kill
  ** 定义具体需要执行的job任务
  ** MapReduce、shell、hive

注意:
 文件需要被放在HDFS上才能被oozie调度,如果在启动需要调动MR任务,jar包同样需要在hdfs上

Lib目录:
 在workflow工作流定义的同级目录下,需要有一个lib目录,在lib目录中存在java节点MapReduce使用的jar包。

 需要注意的是,oozie并不是使用指定jar包的名称来启动任务的,而是通过制定主类来启动任务的。在lib包中绝对不能存在某个jar包的不同版本,不能够出现多个相同主类。

6. Workflow介绍

在这里插入图片描述
 workflow 是一组 actions 集合(例如Hadoop map/reduce作业,pig作业),它被安排在一个控制依赖项DAG(Direct Acyclic Graph)中。“控制依赖”从一个action到另一个action意味着第二个action不能运行,直到第一个action完成。
 Oozie Workflow 定义是用 hPDL 编写的(类似于JBOSS JBPM jPDL的XML过程定义语言)。
 Oozie Workflow actions在远程系统(如Hadoop、Pig)中启动工作。在action完成时,远程系统回调 Oozie通知action完成,此时Oozie将继续在workflow中进行下一步操作。
 Oozie Workflow 包含控制流节点(control flow nodes)和动作节点(action nodes).
控制流节点定义workflow的开始和结束(start、end 和 fail 节点),并提供一种机制来控制workflow执行路径(decision、fork和join节点)。
 action 节点是workflow触发计算/处理任务执行的机制。Oozie为不同类型的操作提供了支持:Hadoop map-reduce、Hadoop文件系统、Pig、SSH、HTTP、电子邮件和Oozie子工作流。Oozie可以扩展来支持其他类型的操作。
 Oozie Workflow 可以被参数化(在工作流定义中使用诸如$inputDir之类的变量)。在提交workflow作业值时,必须提供参数。如果适当地参数化(即使用不同的输出目录),几个相同的workflow作业可以并发。

7. Coordinator介绍

在这里插入图片描述
 用户通常在grid上运行map-reduce、hadoop流、hdfs或pig作业。这些作业中的多个可以组合起来形成一个workflow 作业。Hadoop workflow 系统定义了一个workflow 系统来运行这样的工作。
 通常,workflow 作业是基于常规的时间间隔(time intervals)和数据可用性(data availability)运行的。在某些情况下,它们可以由外部事件触发。
 表示触发workflow 作业的条件可以被建模为必须满足的谓词(predicate )。workflow 作业是在谓词满足之后开始的。谓词可以引用数据、时间和/或外部事件。在将来,可以扩展模型来支持额外的事件类型。
 还需要连接定期运行的workflow 作业,但在不同的时间间隔内。多个后续运行的workflow 的输出成为下一个workflow 的输入。例如,每15分钟运行一次的workflow 的4次运行的输出,就变成了每隔60分钟运行一次的workflow 的输入。将这些workflow 链接在一起会导致它被称为数据应用程序管道。
 Oozie Coordinator 系统允许用户定义和执行周期性和相互依赖的workflow 作业(数据应用程序管道)。

8. Bundle介绍

在这里插入图片描述
 Bundle 是一个更高级的oozie抽象,它将批处理一组Coordinator应用程序。
 用户将能够在bundle级别启动/停止/暂停/恢复/重新运行,从而获得更好、更容易的操作控制。
 更具体地说,oozie Bundle系统允许用户定义和执行一堆通常称为数据管道的Coordinator应用程序。在Bundle中,Coordinator应用程序之间没有显式的依赖关系。然而,用户可以使用Coordinator应用程序的数据依赖来创建隐式数据应用程序管道。

9. 案例演示

配置文件1:coordinator.xml
在这里插入图片描述
配置文件2:workflow.xml
在这里插入图片描述
配置文件3:job.properties
在这里插入图片描述
[补充]crontab执行时间计算:
各个字符代表的含义。0代表从0分开始,*代表任意字符,/代表递增。
crontab执行时间计算
Cron表达式生成器
在这里插入图片描述

将配置文件和jar包按照配置文件中配置的路径上传到HDFS的对应目录
注:严格注意目录位置
在这里插入图片描述

9.1 测试-步骤:

#在HDFS上建立如下文件夹
hdfs dfs -mkdir -p /apps/tags/models/Tag_001/lib
#将文件上传到HDFS
hdfs dfs -put job.properties /apps/tags/models/Tag_001/
hdfs dfs -put coordinator.xml /apps/tags/models/Tag_001/
hdfs dfs -put workflow.xml /apps/tags/models/Tag_001/
hdfs dfs -put model29.jar /apps/tags/models/Tag_001/lib

9.2 Oozie命令

在服务器中cd oozie_test/查看
coordinator.xml\job.properties\model29.jar\workflow.xml

#运行oozie coordinator.xml
#校验配置文件
oozie validate -oozie http://bd001:11000/oozie /root/oozie_test/coordinator.xml
#运行job(配置文件在/root/oozie_test/job.properties,需要查看服务器是否有改文件)
oozie job -oozie http://bd001:11000/oozie -config /root/oozie_test/job.properties -run
#查看信息
oozie job -oozie http://bd001:11000/oozie -info 0000029-191027171933033-oozie-root-C
#查看日志
oozie job -oozie http://bd001:11000/oozie -log 0000064-190923225831711-oozie-root-C
#Kill任务
oozie job -oozie http://bd001:11000/oozie -kill 0000064-190923225831711-oozie-root-C
#查看所有普通任务
oozie  jobs  -oozie http://bd001:11000/oozie
#查看定时任务
oozie jobs -jobtype coordinator -oozie http://bd001:11000/oozie

结果截图:
在这里插入图片描述
Web:http://bd001:11000/oozie/部分查看
思考:上述通过命令调度,在项目中整合oozieAPI整合SpringBoot完成调度

10. Oozie工具类代码开发

导入Oozie相关配置文件,放入resources文件夹
以下文件是up.conf

model: {
  user: "root"
  app: "tags"
  path: {
    jars: "/temp/jars"
    model-base: "/apps/tags_new/models"
  }
}
hadoop: {
  name-node: "hdfs://bd001:8020"
  resource-manager: "bd001:8032"
}
mysql: {
  url: "jdbc:mysql://bd001:3306/tags_new?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&user=root&password=123456"
  driver: "com.mysql.jdbc.Driver"
  tag-table: "tbl_basic_tag"
  model-table: "tbl_model"
}
oozie: {
  url: "http://bd001:11000/oozie"
  params: {
    "user.name": ${model.user}
    "nameNode": ${hadoop.name-node}
    "jobTracker": ${hadoop.resource-manager}
    "appName": ${model.app}
    "master": "yarn"
    "mode": "cluster"
    "queueName": "default"
    "oozie.use.system.libpath": "true"
    "oozie.libpath": "${nameNode}/user/root/share/lib/lib_20190802113508/spark2"
    "sparkOptions": " --driver-memory 512m --executor-memory 512m --num-executors 1 --executor-cores 1 --conf spark.yarn.historyServer.address=http://bd001:18081 --conf spark.eventLog.enabled=true --conf spark.eventLog.dir=hdfs://bd001:8020/apps/spark2/spark2-history/ --conf spark.yarn.jars=hdfs://bd001:8020/apps/archive/sparklib/spark-libs.jar"
    "freq": "0/10 * * * *"
  }
}

10.1 编写Oozie配置类

ConfigHolder.scala

package com.erainm.up.common

import com.typesafe.config.ConfigFactory
import pureconfig.ConfigSource
case class Config
(
  model: Model,
  hadoop: Hadoop,
  mysql: MySQL,
  oozie: Oozie
)

case class Model
(
  user: String,
  app: String,
  path: Path
)

case class Path
(
  jars: String,
  modelBase: String
)

case class Hadoop
(
  nameNode: String,
  resourceManager: String
)

case class MySQL
(
  url: String,
  driver: String,
  tagTable: String,
  modelTable: String
)

case class Oozie
(
  url: String,
  params: Map[String, String]
)

object ConfigHolder {
  import pureconfig._
  import pureconfig.generic.auto._

  private val configTool = ConfigFactory.load("up")
  val config: Config = ConfigSource.fromConfig(configTool).load[Config].right.get
  val model: Model = config.model
  val hadoop: Hadoop = config.hadoop
  val oozie: Oozie = config.oozie
  val mysql: MySQL = config.mysql
}

10.2 编写Oozie工具类

OozieUtils.scala 编写

package com.erainm.up.common

import java.util.Properties
import org.apache.commons.lang3.StringUtils
import org.apache.oozie.client.OozieClient

object OozieUtils {
  val classLoader: ClassLoader = getClass.getClassLoader

  /**
    * Properties 包含各种配置
    * OozieParam 外部传进来的参数
    * 作用: 生成配置, 有些配置无法写死, 所以外部传入
    */
  def genProperties(param: OozieParam): Properties = {
    val properties = new Properties()

    val params: Map[String, String] = ConfigHolder.oozie.params
    for (entry <- params) {
      properties.setProperty(entry._1, entry._2)
    }

    val appPath = ConfigHolder.hadoop.nameNode + genAppPath(param.modelId)
    properties.setProperty("appPath", appPath)

    properties.setProperty("mainClass", param.mainClass)
    properties.setProperty("jarPath", param.jarPath) // 要处理

    if (StringUtils.isNotBlank(param.sparkOptions)) properties.setProperty("sparkOptions", param.sparkOptions)
    properties.setProperty("start", param.start)
    properties.setProperty("end", param.end)
    properties.setProperty(OozieClient.COORDINATOR_APP_PATH, appPath)

    properties
  }

  /**
    * 上传配置
    * @param modelId 因为要上传到 家目录, 所以要传入 id 生成家目录
    */
  def uploadConfig(modelId: Long): Unit = {
    val workflowFile = classLoader.getResource("oozie/workflow.xml").getPath
    val coordinatorFile = classLoader.getResource("oozie/coordinator.xml").getPath

    val path = genAppPath(modelId)
    HDFSUtils.getInstance().mkdir(path)
    HDFSUtils.getInstance().copyFromFile(workflowFile, path + "/workflow.xml")
    HDFSUtils.getInstance().copyFromFile(coordinatorFile, path + "/coordinator.xml")
  }

  def genAppPath(modelId: Long): String = {
    ConfigHolder.model.path.modelBase + "/tags_" + modelId
  }

  def store(modelId: Long, prop: Properties): Unit = {
    val appPath = genAppPath(modelId)
    prop.store(HDFSUtils.getInstance().createFile(appPath + "/job.properties"), "")
  }

  def start(prop: Properties): Unit = {
    val oozie = new OozieClient(ConfigHolder.oozie.url)
    println(prop)
    val jobId = oozie.run(prop)
    println(jobId)
  }

  /**
    * 调用方式展示
    */
  def main(args: Array[String]): Unit = {
    val param = OozieParam(
      19,
      "com.erainm.up29.TestTag",
      "hdfs://bd001:8020/apps/tags/models/Tag_001/lib/model29.jar",
      "",
      "2019-09-24T06:15+0800",
      "2030-09-30T06:15+0800"
    )
    val prop = genProperties(param)
    println(prop)
    uploadConfig(param.modelId)
    store(param.modelId, prop)
    start(prop)
  }
}

case class OozieParam
(
  modelId: Long,
  mainClass: String,
  jarPath: String,
  sparkOptions: String,
  start: String,
  end: String
)

10.3 编写Oozie测试类

package com.erainm.up.common

import java.util.Properties
import org.apache.oozie.client.OozieClient
object OozieTest {
  def upload (): Unit = {
  }
  def genProperties(): Unit = {
  }

  def main(args: Array[String]): Unit = {
    // 1. 上传文件到 HDFS 中, workflow.xml, coordinator.xml, jar
    val path = getClass.getResource("oozie/workflow.xml").getPath
    HDFSUtils.getInstance().copyFromFile(path, "/apps/tags_new/models/tags_modelid")
    // 2. 创建配置
    // 2.1. 读取配置, 这些配置可能不变, 写死的
    val prop = new Properties()
   prop.load(getClass.getResource("oozie/job.properties").openStream())
    // 2.2. 有一些参数, 必须要不同的模型计算, 有不同的值
    // prop.setProperty(...)
    // 3. 运行 Oozie 任务, oozie -oozie ... -config job.properties -run
    val client = new OozieClient("http://bd001:11000/oozie")
    // run -> submit + start
    client.run(prop)
  }
}

 注意:有时候oozie调度会因为windows和服务器时间不一致,使用 date -s "2020-09-08 11:07:00"修改

11. Oozie整合SpringBoot编写任务调度【测试】

首先观察如下界面
在这里插入图片描述
浏览器查看返回的state信息
http://localhost:8081/tags/71/model
查看tbl_basic_tag完成对应71为id对应的name

在这里插入图片描述
注意:
Modelpo是用来操作数据库的
Modeldto是用来和前段交互的
这样区分是用来隐藏数据库表结构
防止黑客进行数据库攻击
而ModelRepo是dao用来把po保存到数据库的

11.1 编写Controller控制层代码

//http://localhost:8081/tags/71/model
//1-使用tagService更新状态
//2-返回HttpResult

    @PostMapping("tags/{id}/model")
    public HttpResult changeModelState(@PathVariable Long id, @RequestBody ModelDto modelDto){
        service.updateModelState(id, modelDto.getState());
        return new HttpResult(Codes.SUCCESS, "执行成功", null);
    }

11.2 编写Service服务层代码

//首先根据modelRepo使用id查找对应的信息
//启动任务
//调用engine类中的封装好的方法启动任务
//modelPo调用setName
//停止任务
//调用engine类中的封装好的方法停止任务
//modelPo更新状态,传过来是几修改为几,modelRepo保存oozie的在有关四级标签配置modelPo状态

    @Override
    public void updateModelState(Long id, Integer state) {
        ModelPo modelPo = modelRepo.findByTagId(id);
        if (state == ModelPo.STATE_ENABLE) {
            //启动流程
            engine.startModel(convert(modelPo));
//modelPo调用setName
           modelPo.setName(jobid);
        }
        if (state == ModelPo.STATE_DISABLE) {
            //关闭流程
            engine.stopModel(convert(modelPo));
        }
        //更新状态信息
        modelPo.setState(state);
        modelRepo.save(modelPo);
    }

11.3 编写引擎Service层代码

//1-new OozieParam的基本配置
//2-使用OozieUtils根据参数生成配置文件Properties对象
//3-通过标签Id(model)上传配置到HDFS(如coordinator.xml/workflow.xml)
//4-保留一份Properties方便后续如果出错可以查看
//5-运行任务
//6-返回JobId

@Override
    public void startModel(ModelDto model) {
        // 设置动态的参数, 例如如何调度, 主类名, jar 的位置
        OozieParam param = new OozieParam(
                model.getId(),
                model.getMainClass(),
                model.getPath(),
                model.getArgs(),
                ModelDto.Schedule.formatTime(model.getSchedule().getStartTime()),
                ModelDto.Schedule.formatTime(model.getSchedule().getEndTime())
        );
        // 生成配置
        Properties properties = OozieUtils.genProperties(param);
        // 上传各种配置, workflow.xml, coordinator.xml
        OozieUtils.uploadConfig(model.getId());
        // 因为如果不保留一份 job.properties 的文件, 无法调试错误
        OozieUtils.store(model.getId(), properties);
        // 运行 Oozie 任务
        OozieUtils.start(properties);
    }
    @Override
    public void stopModel(ModelDto model) {
    }

11.4 测试

根据数据库查看状态信息:
 model_main应该从oozie的properties配置中获取

注意:这里根据自己服务器标签可以自主选择,这里选择#蘑菇街女装#消费偏好,对应的tbl_model的tag_id=95
在这里插入图片描述
查看任务状态

oozie job -oozie http://bd001:11000/oozie -info 0000007-191209104842815-oozie-root-C

注意:任务id从控制台或者数据库查看
在这里插入图片描述
在这里插入图片描述
停止后可以在重启,观察数据库表变化
在这里插入图片描述
在这里插入图片描述
从而完成Oozie和SpringBoot整合。
总结:

  1. Oozie支持基于Hadoop的任务调度,稳定成熟,无缝集成Hadoop
  2. Oozie支持任务状态的监控和持久化保存,支持失败流程控制
  3. Oozie支持命令行调度,还支持Rest-API调用

当然Oozie也有一些缺点:界面丑陋,xml文件编写复杂,功能太多,有的用不上较冗余,

注意:Oozie整合Spark的jar有的需要重新编译

  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

erainm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值