• Hadoop基于Shell命令与底层Unix操作系统的交互


    在阅读Hadoop源代码过程中,在org.apache.hadoop.security.UnixUserGroupInformation类中,需要获取到Unix系统的用户名和所属组的信息,就需要通过执行Shell命令得到相应的结果,这里,通过阅读Hadoop项目org.apache.hadoop.util包、org.apache.hadoop.fs.shell包、org.apache.hadoop.fs包中文件来了解,Hadoop基于Shell命令与底层Unix操作系统的交互,以及在MapReduce模型中通过命令行的方式提交管理计算任务的一些详细情况。

    首先看一下,与Unix系统命令行执行有关的一些类的继承层次关系:

    [java] view plaincopy
    1. ◦org.apache.hadoop.util.Shell  
    2.      ◦org.apache.hadoop.util.Shell.ShellCommandExecutor  
    3.      ◦org.apache.hadoop.fs.DF  
    4.      ◦org.apache.hadoop.fs.DU  
    5.      ◦org.apache.hadoop.fs.FileUtil.CygPathCommand  

    Shell命令最顶层的抽象类是org.apache.hadoop.util.Shell,它定义了如何在当前文件系统环境中,与底层的Unix系统通过命令行进行必要的交互。

    从org.apache.hadoop.util.Shell类定义的属性来看,可以分为两种类型的属性,一种是static final的字符串命令,另一种是与实现命令的执行相关的属性。第一种属性(我把两个static final的获取命令的方式也列出,放到这里的属性的后面)如下所示:

    [java] view plaincopy
    1. /** Unix命令whoami :执行命令得到当前用户名 */  
    2. public final static String USER_NAME_COMMAND = "whoami";  
    3.   
    4. /** Unix命令chmod :执行命令设置用户操作权限 */  
    5. public static final String SET_PERMISSION_COMMAND = "chmod";  
    6.   
    7. /** Unix命令chown :执行命令设置属主 */  
    8. public static final String SET_OWNER_COMMAND = "chown";  
    9.   
    10. /** Unix命令chgrp :执行命令设置组 */  
    11. public static final String SET_GROUP_COMMAND = "chgrp";  
    12.   
    13. /** Unix命令bash -c groups :执行命令得到当前用户所属的组列表 */  
    14. public static String[] getGROUPS_COMMAND() {  
    15.   return new String[]{"bash""-c""groups"};  
    16. }  
    17.   
    18. /** Unix命令ls -ld :执行命令设置组,不支持Windows系统,但可以支持Cygwin */  
    19. public static String[] getGET_PERMISSION_COMMAND() {  
    20.   return new String[] {(WINDOWS ? "ls" : "/bin/ls"), "-ld"};  
    21. }  

    看到这些Unix的命令,应该非常熟悉。

    第二种属性,都属于与如何实现定义的上述命令行的执行有关的,如下所示:

    [java] view plaincopy
    1. private long    interval;   // 刷新间隔  
    2. private long    lastTime;   // 最后执行命令的时间  
    3. private Map<String, String> environment; // 命令行执行所需要的操作系统环境  
    4. private File dir;  
    5. private Process process; // 执行命令行的子进程  
    6. private int exitCode; // 执行命令行完成后,退出状态码  

    dir属性表示当前执行命令所在的工作目录,environment属性表示当前命令执行的环境,它们在Shell类中都提供了一个受保护的设置操作,可以在Shell抽象类的子类中根据需要设置不同工作目录和环境,其中,dir默认为系统“user.dir”变量值,environment使用系统默认的环境。

    通过interval与lastTime属性来检查,是否有必要重新执行一次,如果是就执行,否则重置退出状态码exitCode为0,正常退出,可以在Shell类的run方法中看到:

    [java] view plaincopy
    1. protected void run() throws IOException {  
    2.   if (lastTime + interval > System.currentTimeMillis())  
    3.     return// 不需要重新执行命令行,返回  
    4.   exitCode = 0;   
    5.   runCommand(); // 调用:需要重新执行命令行  
    6. }  

    通过runCommand方法就可以执行指定的Shell命令,它是Shell类的核心。在分析runCommand方法之前,先了解一下与Shell命令执行相关的环境信息。

    当在Windows系统下,打开一个cmd窗口的时候,执行set命令,就能看到当前系统的环境变量的信息,如下所示:

    [xhtml] view plaincopy
    1. ALLUSERSPROFILE=C:/Documents and Settings/All Users  
    2. APPDATA=C:/Documents and Settings/Administrator/Application Data  
    3. CLASSPATH=.;E:/Program Files/Java/jdk1.6.0_14/lib/tools.jar;E:/Program Files/Java/jdk1.6.0_14/lib/dt.jar;E:/Program Files/Java/jdk1.6.0_14/jre/lib/rt.jar;E:/Program Files/Java/jdk1.6.0_14/jre/lib/charsets.jar  
    4. CLIENTNAME=Console  
    5. CommonProgramFiles=C:/Program Files/Common Files  
    6. COMPUTERNAME=SYJ  
    7. ComSpec=C:/WINDOWS/system32/cmd.exe  
    8. DEVMGR_SHOW_NONPRESENT_DEVICES=1  
    9. FP_NO_HOST_CHECK=NO  
    10. HERITRIX_HOME=E:/MyEclipse/workspace/heritrix  
    11. HOME=C:/Documents and Settings/Administrator  
    12. HOMEDRIVE=C:  
    13. HOMEPATH=/Documents and Settings/Administrator  
    14. JAVA_HOME=E:/Program Files/Java/jdk1.6.0_14  
    15. JSERV=D:/oracle/ora92/Apache/Jserv/conf  
    16. LOGONSERVER=//SYJ  
    17. NUMBER_OF_PROCESSORS=2  
    18. NUTSUFFIX=1  
    19. NUT_SUFFIXED_SEARCHING=1  
    20. OS=Windows_NT  
    21. Path=D:/oracle/ora92/bin;C:/Program Files/Oracle/jre/1.3.1/bin;C:/Program Files/Oracle/jre/1.1.8/bin;E:/Program Files/CollabNet Subversion Client;E:/Program Files/Java/jdk1.6.0_14/bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/Program Files/Microsoft SQL Server/80/Tools/Binn/;C:/Program Files/Microsoft SQL Server/90/Tools/binn/;C:/Program Files/MyEclipse 7.0M1/jre/bin;E:/Program Files/TortoiseSVN/bin;E:/PROGRA~1/F-Secure/SSHTRI~1;D:/Program Files/MySQL/MySQL Server 5.1/bin;F:/Program Files/Rational/common;C:/Program Files/StormII/Codec;C:/Program Files/StormII;C:/Program Files/SSH Communications Security/SSH Secure Shell;C:/Program Files/IDM Computer Solutions/UltraEdit/  
    22. PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH  
    23. PROCESSOR_ARCHITECTURE=x86  
    24. PROCESSOR_IDENTIFIER=x86 Family 6 Model 23 Stepping 10, GenuineIntel  
    25. PROCESSOR_LEVEL=6  
    26. PROCESSOR_REVISION=170a  
    27. ProgramFiles=C:/Program Files  
    28. PROMPT=$P$G  
    29. RATL_RTHOME=F:/Program Files/Rational/Rational Test  
    30. SESSIONNAME=Console  
    31. SystemDrive=C:  
    32. SystemRoot=C:/WINDOWS  
    33. TEMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp  
    34. TMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp  
    35. TMPDIR=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp  
    36. USERDOMAIN=SYJ  
    37. USERNAME=Administrator  
    38. USERPROFILE=C:/Documents and Settings/Administrator  
    39. VBOX_INSTALL_PATH=E:/Program Files/Sun/xVM VirtualBox/  
    40. VS90COMNTOOLS=d:/Microsoft Visual Studio 9.0/Common7/Tools/  
    41. windir=C:/WINDOWS  
    42. WV_GATEWAY_CFG=D:/oracle/ora92/Apache/modplsql/cfg/wdbsvr.app  

    这些环境变量的信息都是以键值对的形式出现的,当在操作系统上运行相关的应用程序的时候,其实就是在上述环境变量所设置的一个上下文中运行,环境是应用程序运行不可缺少的条件。你在Unix系统中执行env命令,同样也能得到与上面类似的键值对的环境变量信息。

    所以,org.apache.hadoop.util.Shell作为Shell命令的抽象,一定要获取到当前所在操作系统(Unix系统) 的环境变量,在这样一个上下文中,才能如同从Unix系统中输入执行Shell命令进行执行一样。

    在Java中,实现了一个java.lang.ProcessBuilder类,该类能够创建一个操作系统的进程,通过为该进程设置运行环境变量,从而启动进行执行。默认情况下ProcessBuilder类已经实现了设置当前操作系统环境的功能,可以通过environment()方法查看它的实例所具有的环境信息,这等价于使用System.getenv()获取到的环境变量,都是以键值对的形式存在于ProcessBuilder类实例的上下文中。

    下面,分析Shell类实现执行命令的过程,实现方法为runCommand,如下所示:

    [java] view plaincopy
    1. private void runCommand() throws IOException {   
    2.   
    3.   ProcessBuilder builder = new ProcessBuilder(getExecString()); // 方法getExecString()在该类中式抽象的,需要在实现类中实现,获取到一个命令名称及其参数,从而基于此 构造一个ProcessBuilder进程实例  
    4.   boolean completed = false// 标识执行命令完成情况  
    5.     
    6.   if (environment != null) {  
    7.     builder.environment().putAll(this.environment); // 如果有必要,设置命令行执行环境  
    8.   }  
    9.   if (dir != null) {  
    10.     builder.directory(this.dir); // 如果必要,设置命令行执行所在工作目录  
    11.   }  
    12.     
    13.   process = builder.start(); // 启动ProcessBuilder builder进程,返回一个用来管理命令行执行情况的子进程process  
    14.   final BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); // 当builder进程启动后,检查提交的命令行是否合法,如果不合法或者执行出错,将出错信息写入到缓冲流中,可以从其中解析读取出来  
    15.   BufferedReader inReader = new BufferedReader(new InputStreamReader(process.getInputStream())); // 执行命令返回执行结果,通过process管理子线程来获取执行流中的执行结果信息  
    16.   final StringBuffer errMsg = new StringBuffer(); // 存放执行命令出错信息的String缓冲区  
    17.     
    18.   // 定义解析线程,解析命令行执行出错信息所在的流,解析完成后释放流缓冲区  
    19.   Thread errThread = new Thread() {  
    20.     @Override  
    21.     public void run() {  
    22.       try {  
    23.         String line = errReader.readLine();  
    24.         while((line != null) && !isInterrupted()) {  
    25.           errMsg.append(line);  
    26.           errMsg.append(System.getProperty("line.separator"));  
    27.           line = errReader.readLine();  
    28.         }  
    29.       } catch(IOException ioe) {  
    30.         LOG.warn("Error reading the error stream", ioe);  
    31.       }  
    32.     }  
    33.   };  
    34.   
    35.   try {  
    36.     errThread.start(); // 启动线程,处理出错信息  
    37.   } catch (IllegalStateException ise) { }  
    38.   
    39.   try {  
    40.     parseExecResult(inReader); // 调用:解析执行命令返回的结果信息  
    41.     String line = inReader.readLine();  
    42.     while(line != null) {   
    43.       line = inReader.readLine();  
    44.     }  
    45.     exitCode = process.waitFor(); // 等待进程process处理完毕,置exitCode状态码  
    46.     try {  
    47.       errThread.join(); // 等待出错信息处理线程执行完成  
    48.     } catch (InterruptedException ie) {  
    49.       LOG.warn("Interrupted while reading the error stream", ie);  
    50.     }  
    51.     completed = true// 置命令行执行完成状态  
    52.     if (exitCode != 0) {  
    53.       throw new ExitCodeException(exitCode, errMsg.toString());  
    54.     }  
    55.   } catch (InterruptedException ie) {  
    56.     throw new IOException(ie.toString());  
    57.   } finally {  
    58.     try {  
    59.       inReader.close(); // 最后,需要关闭流对象  
    60.     } catch (IOException ioe) {  
    61.       LOG.warn("Error while closing the input stream", ioe);  
    62.     }  
    63.     if (!completed) {  
    64.       errThread.interrupt(); // 可能处理错误信息的线程发生异常,未能置completed=true,最后终止要该线程  
    65.     }  
    66.     try {  
    67.       errReader.close(); // 关闭流对象  
    68.     } catch (IOException ioe) {  
    69.       LOG.warn("Error while closing the error stream", ioe);  
    70.     }  
    71.     process.destroy(); // 终止子进程process  
    72.     lastTime = System.currentTimeMillis(); // 设置当前时间为该命令行执行的最后时间,为了判断一个命令是否需要被重新执行  
    73.   }  
    74. }  

    上面已经做了详细的注释,基本上阐明了一个命令行的执行过程。

    在类中,还提供了一个static方法execCommand,为执行命令提供入口:

    [java] view plaincopy
    1. public static String execCommand(String ... cmd) throws IOException {  
    2.   return execCommand(null, cmd);  
    3. }  

    执行该方法,调用了另一个重载的execCommand方法,返回命令执行结果的信息。

    注意,在Shell抽象类中并没有实现该怎样获取一个命令名称及其参数的方法,需要在实现类中给出,因此,在Shell类内部定义了一个静态内部类ShellCommandExecutor,该类实现了获取命令名称及其参数的方法。在上面方法execCommand中,调用了一个重载的execCommand方法,该方法中通过实例化一个ShellCommandExecutor类,来提供获取命令名称及其参数,进而构造一个ProcessBuilder实例,创建一个操作系统线程来执行命令行。

     

    ? extends Shell 

     

    下面看实现Shell抽象类的一些子类的实现。

    • ShellCommandExecutor类

    ShellComandExecutor类的实现如下所示:

    [java] view plaincopy
    1. public static class ShellCommandExecutor extends Shell {  
    2.     
    3.   private String[] command; // 命令名称及其参数  
    4.   private StringBuffer output; // 存放执行命令行返回结果的String缓冲区  
    5.     
    6.   public ShellCommandExecutor(String[] execString) {  
    7.     command = execString.clone();  
    8.   }  
    9.   
    10.   public ShellCommandExecutor(String[] execString, File dir) {  
    11.     this(execString);  
    12.     this.setWorkingDirectory(dir);  
    13.   }  
    14.   
    15.   public ShellCommandExecutor(String[] execString, File dir, Map<String, String> env) {  
    16.     this(execString, dir);  
    17.     this.setEnvironment(env);  
    18.   }  
    19.     
    20.   /** 继承自Shell基类,执行命令行 */  
    21.   public void execute() throws IOException {  
    22.     this.run();      
    23.   }  
    24.   
    25.   protected String[] getExecString() {  
    26.     return command; // 输入的就是命令名称+参数的格式,直接得到  
    27.   }  
    28.   
    29. /**   
    30.  * 解析命令行执行后的输出结果流,存放到String缓冲区中 
    31.  */  
    32.   protected void parseExecResult(BufferedReader lines) throws IOException {  
    33.     output = new StringBuffer();  
    34.     char[] buf = new char[512];  
    35.     int nRead;  
    36.     while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {  
    37.       output.append(buf, 0, nRead);  
    38.     }  
    39.   }  

    • DF类

    org.apache.hadoop.fs.DF类实现了Unix系统中Shell命令df,用来获取磁盘使用情况的统计数据。该Shell实现类中定义域df命令操作相关的内容,可以从属性来看:

    [java] view plaincopy
    1. public static final long DF_INTERVAL_DEFAULT = 3 * 1000// 设置df命令刷新间隔为3s     
    2. private String  dirPath; // 执行df命令所在工作目录  
    3. private String filesystem; // 磁盘设备名  
    4. private long capacity; // 磁盘总容量  
    5. private long used; // 磁盘使用量  
    6. private long available; // 磁盘可用量  
    7. private int percentUsed; // 磁盘使用率  
    8. private String mount; // 磁盘挂载位置  

    只需要实现Shell类定义的getExecString与parseExecResult方法即可。比较简单,getExecString方法实现如下:

    [java] view plaincopy
    1. protected String[] getExecString() {  
    2.   return new String[] {"bash","-c","exec 'df' '-k' '" + dirPath  + "' 2>/dev/null"};  
    3. }  

    该方法返回的字符串数组,用来构造一个ProcessBuilder进程实例。

    parseExecResult方法实现如下所示:

    [java] view plaincopy
    1. protected void parseExecResult(BufferedReader lines) throws IOException {  
    2.   lines.readLine();   // 去掉流中的首行    
    3.   String line = lines.readLine();  
    4.   if (line == null) {  
    5.     throw new IOException( "Expecting a line not the end of stream" );  
    6.   }  
    7.   StringTokenizer tokens = new StringTokenizer(line, " /t/n/r/f%");  
    8.     
    9.   this.filesystem = tokens.nextToken();  
    10.   if (!tokens.hasMoreTokens()) {              
    11.     line = lines.readLine();  
    12.     if (line == null) {  
    13.       throw new IOException( "Expecting a line not the end of stream" );  
    14.     }  
    15.     tokens = new StringTokenizer(line, " /t/n/r/f%");  
    16.   }  
    17. /**   
    18.  * 下面处理并设置执行df -k命令的结果信息 
    19.  */  
    20.   this.capacity = Long.parseLong(tokens.nextToken()) * 1024;  
    21.   this.used = Long.parseLong(tokens.nextToken()) * 1024;  
    22.   this.available = Long.parseLong(tokens.nextToken()) * 1024;  
    23.   this.percentUsed = Integer.parseInt(tokens.nextToken());  
    24.   this.mount = tokens.nextToken();  
    25. }  

    • DU类

    DU类实现了Unix的du命令,显示目录或者文件大小的信息,具体实现可以参考org.apache.hadoop.fs.DU类,这里跳过。

    • CygPathCommand类

    CygPathCommand类是org.apache.hadoop.fs.FileUtil类的一个内部静态类,实现了Windows系统上模拟Unix系统的Cygwin系统的cygpath命令,这里跳过。

  • 相关阅读:
    20155302 课堂实践二
    20155302 课堂实践
    2017-2018-1 20155302 《信息安全系统设计基础》第6周学习总结
    2017-2018-1 20155302 《信息安全系统设计基础》第5周学习总结
    2017-2018-1 20155302 《信息安全系统设计基础》第四周学习总结
    2017-2018-1 20155301 《信息安全系统设计基础》第九周学习总结
    课下作业和课上作业
    2017-2018-1 20155301 《信息安全系统设计基础》第八周学习总结
    信息安全系统设计基础实验二
    信息安全系统设计基础第二次实验
  • 原文地址:https://www.cnblogs.com/java20130722/p/3206977.html
Copyright © 2020-2023  润新知