oozie自定义节点注意事项

为什么需要自定义节点(Custom Node)?


正如我们在文章[1]中所说明的,Oozie之所以与众不同,是因为它提供了一种“最小化”的工作流语言,其中只包含少数几种控制和动作节点。尽管其中的一种动作节点是java动作节点,它让我们可以从Oozie工作流调用任意一个带有main方法的java类,但这种方法并非总是最佳的。原因之一就在于,java动作是在Hadoop簇集中作为map-reduce作业执行的,并且只带有唯一的Mapper任务。一方面,这带来了很多好处:

它拥有内建的可伸缩性以及对map/reduce框架的灾难恢复支持,这样我们就不必这些特性构建到Oozie中。
外部执行机制,这让Oozie引擎变得更轻量级,从而可以支持更多并发运行的过程。
另一方面,这种方法也有一些缺点:

它把每个java节点都作为mapper任务启动,这会导致在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启动的,但是在Oozie引擎的外部执行,它会监控正在执行的动作,直到完成。这是通过动作的回调或者Oozie针对动作状态的polling操作完成的。
实现Oozie自定义动作处理程序

在这个例子中,我们会对独立的邮件程序进行转换,并展现到自定义的email动作中[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: Email自定义动作

这个实现对ActionExecutor[2]类(由Oozie提供)进行了扩展,并重写了一些必要的方法。因为邮件的发送过程是一种非常快速的操作,所以我们决定将其实现为同步的动作处理程序,那意味着它会在Oozie的执行上下文中执行。

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

对于所有自定义动作处理程序都需要没有参数的构造函数。这个构造函数会注册动作处理程序的名称(使用动作名称来调用父类),我们会在工作流XML中使用它。
我们可以使用InitActionType[3]方法来注册执行动作时可能发生的异常,以及它们的类型和错误信息,并为执行程序本身执行初始化操作。
Start方法是用来启动动作的执行操作的。因为我们实现的是同步动作,所以整个动作都会在此执行。这个方法是由Oozie调用的,它有两个参数:Context和WorkflowAction。Context参数提供了对Oozie工作流执行上下文的访问,其中主要包含了工作流的变量,并提供了对其进行操作的非常简单的API(set、get)[4]。而WorkflowAction则提供了Oozie对当前动作的定义。
还有Check方法,Oozie会使用它来检查动作的状态。它不能作为同步动作来调用。
Kill方法,可以用来中断运行的作业或者动作。
End方法,可以用于所有清理动作,或者用于可能在完成动作之后所要做的处理。它还需要设置执行的结果。

部署并使用Oozie自定义动作处理程序

实现了自定义的动作执行方式之后,我们需要为新的email动作定义XML模式[5](代码2)。





       
                 

                 
                 

                 
                 

       


代码2: 为email组件所用的XML schema

自定义动作节点和XML schema文件都需要打包在单独的jar文件中,比方说emailAction.Jar。我们可以使用Oozie的oozie-setup.sh脚本执行下面的命令,从而把这个(以及其他所有)jar文件添加到Oozie的war文件中。

$ bin/oozie-setup.sh -jars emailAction.jar:mail.jar (See Adding Jars to Oozie) 
码3: 部署命令
向Oozie添加Jar文件
你要知道,Cloudera推荐的oozie-setup.sh命令行会重新构建你的war文件,并且, 如果你使用网页来监控作业,那么就会丢失java的脚本扩展。在测试方面,我们难以同时包含-extjs和-jars选项。作为权宜之计,我们会把jar文件复制到${CATALINA_BASE}/webapps/oozie/WEB-INF/lib中,其中${CATALINA_BASE}代表的是/var/lib/oozie/oozie-server。请注意,这里存在一定的风险,如果其他人重新构建了war文件,那么你就会丢失这些扩展,并且他们以后需要手动添加。对于测试来说,我们建议复制jar文件,然而,对于生产环境,我们建议把jar添加到war文件中。

现在我们需要把关于自定义执行器的信息注册到Oozie的运行时中。这是通过扩展oozie-site.xml完成的[6]。我们可以通过在Oozie配置文件oozie-site.xml中添加或者修改“oozie.service.ActionService.executor.ext.classes”[7]来注册自定义动作本身(代码4)。


       oozie.service.ActionService.executor.ext.classes
       com.navteq.assetmgmt.oozie.custom. EmailActionExecutor 
代码4: 自定义执行配置
为新动作(代码2)所用的XML schema应该添加到oozie-site.xml中,位于属性“oozie.service.WorkflowSchemaService.ext.schemas”[8]之下(代码5)。


       oozie.service.SchemaService.wf.ext.schemas
       emailAction.xsd
代码5: 自定义模式配置
最后,Tomcat启动之后,我们就可以在工作流中使用自定义的动作节点了。

为了测试我们的实现,我们创建了简单的工作流(代码6),它会使用我们的执行器来发送email。



 

 
   
     test
     mike.segel@.com

     boris.lublinsky@.com
     This is a test message, if you can see this, Mikey did something right! :)
   
  

  
 
 
   Workflow failed, error message[${wf:errorMessage(wf:lastErrorNode())}]
 
 
[1] 这种类的例子可能是各种计数器操作、简单的计算等等。
[2] 所有Oozie动作执行器都是Oozie分发程序的一部分,它们都是通过扩展这个类实现的。
[3] 在我们的实现中,我们使用的是默认的实现,这样就是为什么没有在代码中展现的原因。你可以查看一下Oozie的源代码[4],就可以知道在现存的Oozie动作处理程序中是如何实现这个方法的。
[4] 配置自定义执行器有两种方式——工作流变量和/或动作配置。在我们的例子中展示的是后者,但是在实际情况中,实际上总是二者的组合。
[5] 确保不仅要定义复杂的类型,还要对元素进行定义。那是Oozie所期望的。
[6] 通常在Oozie的分发包中叫做oozie-default.xml。
[7] 对于多个执行器,类名应该以逗号分隔。
[8] 对于多次执行,我们可以为多种模式使用逗号分隔的列表。

注意点:

  1. 新建的项目专门完成自定义节点,注意包依赖冲突的问题,有可能导致oozie起不来;
  2. 配置信息不要遗漏;
  3. oozie在运行的时候 只会去两个地方寻找自己需要的lib
             1 会去当前提交任务的workflow所在的hdfs目录下的lib下寻找 
     eg: /user/root/examples/apps/fork-merge的workflow下有  job.properties   lib   workflow.xml三个目录 会去lib目录下找对应jar
         
             2 如果是shell命令提交的话,他会主动去自己的公共资源库中寻找自己需要的jar文件,公共资源库为 /user/root/share/lib/lib_20150128185329
      其中共享库里面存放的是oozie 常见action需要的包  比如hive  hive2 pig  sqoop oozie  hcatalog  distcp等
      如果是java客户端提交任务的话,需要设置oozie.libpath(此时此路径下可以存放你工程需要的别的jar包而不需要存放在共享库中 防止混淆)
      properties.setProperty("oozie.use.system.libpath","true");  ---> 设置使用oozie共享库
      properties.setProperty("oozie.libpath","hdfs://master:9000/user/hdfs/examples/thirdlib");  ---> 设置存放工程使用的第三方的jar
      
    3 上面2是针对java代码写法,如果对应到job.properties写法为:
    	nameNode=hdfs://master:9000
    	jobTracker=master:8032
    	queueName=default
    	examplesRoot=examples
    	oozie.use.system.libpath=true
    	oozie.libpath=hdfs://master:9000/user/hdfs/examples/thirdlib
    	oozie.wf.application.path=${nameNode}/user/${user.name}/${examplesRoot}/apps/sqoop-sqlserver-to-hdfs
  4. 关于oozie使用调用sqoop action执行数据导出导入时对应的jar包:
        1.1 需要将 mysql/sqlserver/oracle等主流数据库的驱动包放在oozie的共享库对应hdfs目录下 eg:
        hdfs://master:9000/user/root/share/lib/lib_20141031094140/sqoop下
        1.2 将 oozie-sharelib-sqoop-4.0.0-cdh5.1.0.jar   sqoop-1.4.4-cdh5.1.0.jar也放在上述目录中
  5. hadoop运行时内存不足或者其他原因内存引起的错误

	mapred.child.java.opts
	-Xmx4096m


	mapred.map.child.java.opts
	-Xmx4096m


	mapred.reduce.child.java.opts
	-Xmx4096m


或者增加map的数量
6关于oozie安装时数据库有问题的:
由于oozie错误或者其他原因造成的oozie安装失败,第二次安装如果采用默认值可能会失败:eg 数据库初始化失败错误,
处理方式: 删除上一次默认的oozie数据文件data文件夹或者修改默认的值使得安装指向别的地方。
7.oozie提交任务时,job.properties文件的namenode属性值不建议写IP 建议写主机名

8.oozie的节点分为 动作节点(action) 和控制节点(比如 start end fork merge) 其中控制节点下目前只可以放动作节点,分支节点下不支持在存放分支节点(即分支套分支的写法)

  案例来自官网: http://oozie.apache.org/docs/4.0.0/WorkflowFunctionalSpec.html#a3.1.5_Fork_and_Join_Control_Nodes
  
  
    ...
    
        
        
    
    
        
            foo:8021
            bar:8020
            job1.xml
        
        
        
    
    
        
            foo:8021
            bar:8020
            job2.xml
        
        
        
    
    
    ...
oozie自定义节点注意事项_第1张图片

你可能感兴趣的:(Hadoop)