扩展oozie_扩展Oozie

扩展oozie

在我们以前的文章[1、2]中,我们描述了Oozie工作流服务器,并提供了几个工作流示例。 我们还描述了Oozie工作流的部署和配置以及用于启动,停止和监视Oozie工作流的工具。

在本文中,我们将展示如何利用Oozie的可扩展性来实现自定义编排语言扩展。

为什么要使用自定义节点?

正如我们在[1]中所解释的那样,Oozie开箱即用地提供了一种“最小”工作流语言,该语言仅包含少数控件和动作节点。 尽管动作节点之一是java动作节点,它允许使用Oozie工作流程中的main方法调用任何java类,但这种方法并不总是最佳方法。 原因之一是,Java操作在Hadoop集群中作为map-reduce任务与单个Mapper任务一起执行。 一方面,这提供了许多优点:

  • 内置的可伸缩性和对map / reduce框架的故障转移支持减轻了将这些功能内置到Oozie中的必要性。
  • 外部化执行使Oozie引擎更轻巧,从而可以支持更多同时运行的进程

另一方面,此方法有一些缺点:

  • 作为映射器任务启动每个Java节点会导致在Hadoop集群中启动新JVM的开销。
  • 外部化java类执行将创建额外的网络流量,以使该执行与Oozie服务器同步。
  • 从Java代码传递参数变得相当昂贵的操作

尽管在运行相对较长(几分钟,几小时)的map / reduce或Pig作业的情况下,其优势远远超过了缺点,但在简单的Java节点(请参见[2])的情况下,外部化执行的开销要难得多证明。 因此,使用自定义动作节点的原因之一是直接在Oozie执行上下文中支持轻量级Java类[1]的执行。

使用自定义动作的另一个原因是为了改善工作流的语义/可读性。 由于Oozie是用于支持基于Hadoop的编排的工作流引擎,因此其语言语义非常以Hadoop执行为中心-Hadoop文件系统,map / reduce,Pig等。此语义套件非常适合Hadoop开发人员,但并没有过多提及功能给定动作的 可以提出与业务相关的动作本身的命名约定,但这只能部分解决问题。动作命名仅反映给定流程的语义,而不是整个主题领域,也不能解决动作问题参数,这些参数仅对开发人员有意义。

幸运的是,Oozie支持一种非常优雅的可扩展机制–自定义动作节点[3],可以轻松解决这两个问题。 自定义动作节点允许使用其他动作(动词)扩展Oozie的语言。 Oozie动作节点可以是同步或异步的。

  • 同步节点-由Oozie内联执行,Oozie在继续进行之前等待这些节点的完成。 这些节点适用于轻量级任务,例如自定义计算,FileSystem move,mkdir,delete。
  • 异步节点–由Oozie启动,但在Oozie引擎外部执行,监视正在执行的动作是否完成。 这是通过操作的回调或Oozie轮询操作状态来完成的。

实施Oozie自定义操作处理程序

在此示例中,我们将把[2]中显示的独立邮件程序转换为自定义电子邮件操作(清单1)。

package com.navteq.assetmgmt.oozie.custom;

import java.util.Properties;
import java.util.StringTokenizer;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.oozie.ErrorCode;
import org.apache.oozie.action.ActionExecutor;
import org.apache.oozie.action.ActionExecutorException;
import org.apache.oozie.action.ActionExecutorException.ErrorType;
import org.apache.oozie.client.WorkflowAction;
import org.apache.oozie.util.XmlUtils;
import org.jdom.Element;
import org.jdom.Namespace;

public class EmailActionExecutor extends ActionExecutor {

         private static final String NODENAME = "eMail";

         private static final String SUCCEEDED = "OK";
  private static final String FAILED = "FAIL";
  private static final String KILLED = "KILLED";

         private static final String DEFAULMAILSERVER = "imailchi.navtech.com";
         private static final String EMAILSERVER = "emailServer";
         private static final String SUBJECT = "emailSubject";
         private static final String MESSAGE = "emailBody";
         private static final String FROM = "emailFrom";
         private static final String TO = "emailTo";

  public EmailActionExecutor() {
    super(NODENAME);
}

         @Override
         public void check(Context context, WorkflowAction action) throws ActionExecutorException {

                  // Should not be called for synch operation
                  throw new UnsupportedOperationException();
         }

         @Override
         public void end(Context context, WorkflowAction action)throws ActionExecutorException {

                  String externalStatus = action.getExternalStatus();
                  WorkflowAction.Status status = externalStatus.equals( SUCCEEDED ) ?
                         WorkflowAction.Status. OK : WorkflowAction.Status. ERROR ;
                  context.setEndData(status, getActionSignal(status));
         }

         @Override
         public boolean isCompleted(String arg0) {

                  return true;
         }

         @Override
         public void kill(Context context, WorkflowAction action) throws ActionExecutorException {

context.setExternalStatus(KILLED);
context.setExecutionData(KILLED, null);
         }

         @Override
         public void start(Context context, WorkflowAction action) throws ActionExecutorException {

                  // Get parameters from Node configuration
                  try{
                           Element actionXml = XmlUtils.parseXml(action.getConf());
                           Namespace ns = Namespace.getNamespace("uri:custom:email-action:0.1");

                           String server = actionXml.getChildTextTrim(EMAILSERVER, ns);
                           String subject = actionXml.getChildTextTrim(SUBJECT, ns);
                           String message = actionXml.getChildTextTrim(MESSAGE, ns);
                           String from = actionXml.getChildTextTrim(FROM, ns);
                           String to = actionXml.getChildTextTrim(TO, ns);

                           // Check if all parameters are there
                           if(server == null)
                                    server = DEFAULMAILSERVER;
                           if((message == null) || (from == null) || (to == null))
                                    throw new ActionExecutorException(ErrorType.FAILED,
   ErrorCode.E0000.toString(), "Not all parameters are defined");

                           // Execute action synchronously
                           SendMail(server, subject, message, from, to);
                           context.setExecutionData(SUCCEEDED, null);
                }
                catch(Exception e){
     context.setExecutionData(FAILED, null);
                       throw new ActionExecutorException(ErrorType.FAILED,
   ErrorCode.E0000.toString(), e.getMessage());
                }
     }

     // Sending an email
     public void SendMail(String server, String subject, String message,
                           String from, String to) throws Exception {

                // create some properties and get the default Session
                Properties props = new Properties();
                props.setProperty("mail.smtp.host", server);
                Session session = Session.getDefaultInstance(props, null);

                // create a message
                Message msg = new MimeMessage(session);

                // set the from and to address
                InternetAddress addressFrom = new InternetAddress(from);
                msg.setFrom(addressFrom);

                // To is a comma separated list
                StringTokenizer st = new StringTokenizer(to, ",");
                String [] recipients = new String[st.countTokens()];
                int rc = 0;
                while(st.hasMoreTokens())
                        recipients[rc++] = st.nextToken();
                InternetAddress[] addressTo = new InternetAddress[recipients.length];
                for (int i = 0; i < recipients.length; i++){
                        addressTo[i] = new InternetAddress(recipients[i]);
                }
                msg.setRecipients(Message.RecipientType.TO, addressTo);

                // Setting the Subject and Content Type
                msg.setSubject(subject);
                msg.setContent(message, "text/plain");
                Transport.send(msg);
     }
}

清单1:电子邮件自定义操作

此实现扩展了ActionExecutor [2]类(由Oozie提供)并覆盖了所需的方法。 因为发送电子邮件是非常快速的操作,所以我们决定将其实现为同步操作处理程序,这意味着它是在Oozie执行上下文中执行的。

我们的实现(清单1)遵循Oozie文档并实现所有必需的方法:

  • 任何自定义操作处理程序都不需要参数构造函数。 此构造函数注册将在工作流XML内使用的动作处理程序名称(使用动作名称调用super)。
  • InitActionType [3]方法可用于在执行操作时注册可能的异常及其类型和错误消息,并对执行程序本身进行初始初始化。
  • Start方法用于开始执行动作。 因为我们已经实现了同步动作,所以整个动作都在这里执行。 Oozie使用两个参数Context和WorkflowAction调用此方法。 上下文提供对Oozie工作流执行上下文的访问,该上下文除其他内容外还包含工作流变量,并提供用于操作它们的非常简单的API(set,get) [4] 。 WorkflowAction提供Oozie对当前操作的定义。
  • 检查方法,Oozie使用它来检查动作的状态。 永远不要调用同步动作。
  • Kill方法,用于杀死正在运行的作业或动作。
  • End方法用于完成操作后可能需要进行的任何清理或处理。 它还必须设置执行结果。

部署和使用Oozie自定义操作处理程序

使用自定义动作执行实现后,有必要为我们的新电子邮件动作[5]定义XML模式(清单2)

<?xml version = "1.0" encoding = "UTF-8" ?>
< xs:schema xmlns:xs = "http://www.w3.org/2001/XMLSchema" 
                 xmlns:email = "uri:custom:email-action:0.1"
     elementFormDefault = "qualified"
     targetNamespace = "uri:custom:email-action:0.1" >
< xs:complexType name = "EMAIL" >
       < xs:sequence >
                 < xs:element name = "emailServer" type = "xs:string" minOccurs = "0" maxOccurs = "1" />
                 < xs:element name = "emailSubject" type = "xs:string" />
                 < xs:element name = "emailFrom" type = "xs:string" />
                 < xs:element name = "emailTo" type = "xs:string" />
                 < xs:element name = "emailBody" type = "xs:string" />
       </ xs:sequence >
</ xs:complexType >

< xs:element name = "eMail" type = "email:EMAIL" ></ xs:element >
</ xs:schema >

清单2:电子邮件组件的XML模式

自定义操作代码和XML模式都必须打包在一个jar中,例如emailAction。 罐。 Oozie的oozie-setup.sh脚本可用于使用以下命令(清单3)将此(和其他)罐子添加到Oozie的战争中:

$ bin/oozie-setup.sh -jars emailAction.jar:mail.jar (See Adding Jars to Oozie)

清单3:部署命令

将罐子添加到Oozie

请注意,Cloudera建议的oozie-setup.sh命令行将重建您的Oozie war文件,如果您使用网页来监视作业,则将丢失Java脚本扩展名。 在测试中,我们很难同时包含-extjs和-jars选项。 为了方便起见,我们将jar文件复制到$ {CATALINA_BASE} / webapps / oozie / WEB-INF / lib中,其中$ {CATALINA_BASE}是/ var / lib / oozie / oozie-server。 请注意,如果有人重建战争文件,则可能会丢失这些扩展名,因此必须手动添加。 为了进行测试,我们建议复制您的jar文件,但是为了生产和实施,我们建议将jar文件添加到war文件中。

现在,我们需要在Oozie运行时中注册有关自定义执行程序的信息。 这是通过扩展oozie-site.xml [6]完成的 。 可以通过在oozie配置文件oozie-site.xml(清单4)中添加/更改“ oozie.service.ActionService.executor.ext.classes” [7]来完成自定义操作本身的注册。

……………………………………
<property>
       <name>oozie.service.ActionService.executor.ext.classes</name>
       <value>com.navteq.assetmgmt.oozie.custom. EmailActionExecutor </value>
</property>
……………………………………

清单4:定制执行配置

应该将新操作(清单2)的XML模式添加到oozie-site.xml中的属性“ oozie.service.WorkflowSchemaService.ext.schemas” [8] (清单5)。

………………………………………
<property>
       <name>oozie.service.SchemaService.wf.ext.schemas</name>
       <value> emailAction.xsd</value>
</property>
…………………………………

清单5:定制模式配置

最后,一旦重新启动Tomcat,就可以在工作流中使用自定义操作节点。

为了测试我们的实现,我们创建了一个简单的工作流(清单6),该工作流使用执行程序发送电子邮件。

<!--
Copyright (c) 2011 NAVTEQ! Inc. All rights reserved.
Test emailOozie Script
-->
<workflow-app xmlns = 'uri:oozie:workflow:0.1' name = 'emailTester' >
 <start to = 'simpleEmail' />
 <action name = 'simpleEmail' >
   <eMail xlmns =“ uri:custom:email-action:0.1”>
     <emailSubject> test </emailSubject>
     <emailFrom> mike.segel @<mycompany>.com </emailFrom>
     <emailTo> boris.lublinsky @<mycompany>.com </emailTo>
     <emailMessage> This is a test message, if you can see this, Mikey did something right! :) </emailMessage>
   </eMail>
  <error to = "fail" />
  <ok to = "end" />
 </action>
 <kill name = "fail" >
   <message> Workflow failed, error message[${wf:errorMessage(wf:lastErrorNode())}] </message>
 </kill>
 <end name = 'end' />
</workflow-app> 

清单6:使用电子邮件自定义操作执行器的简单工作流

结论

在本文中,我们展示了如何通过创建自定义动作执行程序来扩展Oozie。 这样做可以定义和实现与部门/企业功能一致的部门/企业特定的Oozie方言(域特定语言)。 这种领域特定的语言可以简化给定部门/企业的构建过程,并提高其可读性。

尽管Oozie仍相对不成熟,但它提供了一个基本的框架来处理由多个映射/归约作业组成的流程,并且能够向整个业务流程中添加非映射归约作业。 随着越来越多的用户使用Oozie并提供反馈,我们相信它有可能成为Hadoop环境的强大组成部分。

与我们在这三篇小文章中介绍的内容相比,Oozie的意义要大得多。 我们希望他们能使您对Oozie工作流引擎产生足够的兴趣,并为您进一步进行Oozie探索提供良好的起点。

参考资料

  1. 鲍里斯·卢布林斯基,迈克·西格尔。 Oozie简介。
  2. 鲍里斯·卢布林斯基,迈克·西格尔。 Oozie以身作则
  3. Oozie自定义动作节点
  4. Oozie源代码

关于作者

Boris Lublinsky是NAVTEQ的首席架构师,在那里他致力于为大数据管理和处理以及SOA定义体系结构远景,并实施各种NAVTEQ项目。 他还是InfoQ的SOA编辑器,并且是OASIS中SOA RA工作组的参与者。 鲍里斯(Boris)是他的最新著作《应用的SOA》的作者兼经常演讲者。

迈克尔·塞格尔(Michael Segel )在过去20多年中一直与客户合作,以​​识别和解决他们的业务问题。 迈克尔曾在多个行业担任过多个职务。 他是一位独立顾问,他一直致力于解决任何具有挑战性的问题。 Michael拥有俄亥俄州立大学的软件工程学位。


[1]此类的示例可以是各种计数器操作,简单的计算等。

[2]所有Oozie动作执行者(属于Oozie发行版)都是通过扩展此类来实现的

[3]在我们的实现中,我们使用默认实现,这就是为什么它不出现在代码中的原因。 查看Oozie源代码[4],以了解如何在现有Oozie操作处理程序中实现此方法。

[4]配置自定义执行程序有两个选项-工作流变量和/或操作配置。 在我们的示例中,我们显示了后者,但实际上,它始终是两者的结合

[5]确保不仅定义复杂类型定义,还定义元素定义。 这就是OOzie期望的

[6]在oozie发行版中通常称为oozie-default.xml。

[7]对于多个执行程序,类名称应以逗号分隔。

[8]对于多个执行,可以使用多个模式的逗号分隔列表。

翻译自: https://www.infoq.com/articles/ExtendingOozie/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

扩展oozie

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值