在阅读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系统命令行执行有关的一些类的继承层次关系:
- ◦org.apache.hadoop.util.Shell
- ◦org.apache.hadoop.util.Shell.ShellCommandExecutor
- ◦org.apache.hadoop.fs.DF
- ◦org.apache.hadoop.fs.DU
- ◦org.apache.hadoop.fs.FileUtil.CygPathCommand
Shell命令最顶层的抽象类是org.apache.hadoop.util.Shell,它定义了如何在当前文件系统环境中,与底层的Unix系统通过命令行进行必要的交互。
从org.apache.hadoop.util.Shell类定义的属性来看,可以分为两种类型的属性,一种是static final的字符串命令,另一种是与实现命令的执行相关的属性。第一种属性(我把两个static final的获取命令的方式也列出,放到这里的属性的后面)如下所示:
-
- public final static String USER_NAME_COMMAND = "whoami";
-
-
- public static final String SET_PERMISSION_COMMAND = "chmod";
-
-
- public static final String SET_OWNER_COMMAND = "chown";
-
-
- public static final String SET_GROUP_COMMAND = "chgrp";
-
-
- public static String[] getGROUPS_COMMAND() {
- return new String[]{"bash", "-c", "groups"};
- }
-
-
- public static String[] getGET_PERMISSION_COMMAND() {
- return new String[] {(WINDOWS ? "ls" : "/bin/ls"), "-ld"};
- }
看到这些Unix的命令,应该非常熟悉。
第二种属性,都属于与如何实现定义的上述命令行的执行有关的,如下所示:
- private long interval;
- private long lastTime;
- private Map<String, String> environment;
- private File dir;
- private Process process;
- private int exitCode;
dir属性表示当前执行命令所在的工作目录,environment属性表示当前命令执行的环境,它们在Shell类中都提供了一个受保护的设置操作,可以在Shell抽象类的子类中根据需要设置不同工作目录和环境,其中,dir默认为系统“user.dir”变量值,environment使用系统默认的环境。
通过interval与lastTime属性来检查,是否有必要重新执行一次,如果是就执行,否则重置退出状态码exitCode为0,正常退出,可以在Shell类的run方法中看到:
- protected void run() throws IOException {
- if (lastTime + interval > System.currentTimeMillis())
- return;
- exitCode = 0;
- runCommand();
- }
通过runCommand方法就可以执行指定的Shell命令,它是Shell类的核心。在分析runCommand方法之前,先了解一下与Shell命令执行相关的环境信息。
当在Windows系统下,打开一个cmd窗口的时候,执行set命令,就能看到当前系统的环境变量的信息,如下所示:
- ALLUSERSPROFILE=C:/Documents and Settings/All Users
- APPDATA=C:/Documents and Settings/Administrator/Application Data
- 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
- CLIENTNAME=Console
- CommonProgramFiles=C:/Program Files/Common Files
- COMPUTERNAME=SYJ
- ComSpec=C:/WINDOWS/system32/cmd.exe
- DEVMGR_SHOW_NONPRESENT_DEVICES=1
- FP_NO_HOST_CHECK=NO
- HERITRIX_HOME=E:/MyEclipse/workspace/heritrix
- HOME=C:/Documents and Settings/Administrator
- HOMEDRIVE=C:
- HOMEPATH=/Documents and Settings/Administrator
- JAVA_HOME=E:/Program Files/Java/jdk1.6.0_14
- JSERV=D:/oracle/ora92/Apache/Jserv/conf
- LOGONSERVER=//SYJ
- NUMBER_OF_PROCESSORS=2
- NUTSUFFIX=1
- NUT_SUFFIXED_SEARCHING=1
- OS=Windows_NT
- 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/
- PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
- PROCESSOR_ARCHITECTURE=x86
- PROCESSOR_IDENTIFIER=x86 Family 6 Model 23 Stepping 10, GenuineIntel
- PROCESSOR_LEVEL=6
- PROCESSOR_REVISION=170a
- ProgramFiles=C:/Program Files
- PROMPT=$P$G
- RATL_RTHOME=F:/Program Files/Rational/Rational Test
- SESSIONNAME=Console
- SystemDrive=C:
- SystemRoot=C:/WINDOWS
- TEMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp
- TMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp
- TMPDIR=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp
- USERDOMAIN=SYJ
- USERNAME=Administrator
- USERPROFILE=C:/Documents and Settings/Administrator
- VBOX_INSTALL_PATH=E:/Program Files/Sun/xVM VirtualBox/
- VS90COMNTOOLS=d:/Microsoft Visual Studio 9.0/Common7/Tools/
- windir=C:/WINDOWS
- 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,如下所示:
- private void runCommand() throws IOException {
-
- ProcessBuilder builder = new ProcessBuilder(getExecString());
- boolean completed = false;
-
- if (environment != null) {
- builder.environment().putAll(this.environment);
- }
- if (dir != null) {
- builder.directory(this.dir);
- }
-
- process = builder.start();
- final BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
- BufferedReader inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
- final StringBuffer errMsg = new StringBuffer();
-
-
- Thread errThread = new Thread() {
- @Override
- public void run() {
- try {
- String line = errReader.readLine();
- while((line != null) && !isInterrupted()) {
- errMsg.append(line);
- errMsg.append(System.getProperty("line.separator"));
- line = errReader.readLine();
- }
- } catch(IOException ioe) {
- LOG.warn("Error reading the error stream", ioe);
- }
- }
- };
-
- try {
- errThread.start();
- } catch (IllegalStateException ise) { }
-
- try {
- parseExecResult(inReader);
- String line = inReader.readLine();
- while(line != null) {
- line = inReader.readLine();
- }
- exitCode = process.waitFor();
- try {
- errThread.join();
- } catch (InterruptedException ie) {
- LOG.warn("Interrupted while reading the error stream", ie);
- }
- completed = true;
- if (exitCode != 0) {
- throw new ExitCodeException(exitCode, errMsg.toString());
- }
- } catch (InterruptedException ie) {
- throw new IOException(ie.toString());
- } finally {
- try {
- inReader.close();
- } catch (IOException ioe) {
- LOG.warn("Error while closing the input stream", ioe);
- }
- if (!completed) {
- errThread.interrupt();
- }
- try {
- errReader.close();
- } catch (IOException ioe) {
- LOG.warn("Error while closing the error stream", ioe);
- }
- process.destroy();
- lastTime = System.currentTimeMillis();
- }
- }
上面已经做了详细的注释,基本上阐明了一个命令行的执行过程。
在类中,还提供了一个static方法execCommand,为执行命令提供入口:
- public static String execCommand(String ... cmd) throws IOException {
- return execCommand(null, cmd);
- }
执行该方法,调用了另一个重载的execCommand方法,返回命令执行结果的信息。
注意,在Shell抽象类中并没有实现该怎样获取一个命令名称及其参数的方法,需要在实现类中给出,因此,在Shell类内部定义了一个静态内部类ShellCommandExecutor,该类实现了获取命令名称及其参数的方法。在上面方法execCommand中,调用了一个重载的execCommand方法,该方法中通过实例化一个ShellCommandExecutor类,来提供获取命令名称及其参数,进而构造一个ProcessBuilder实例,创建一个操作系统线程来执行命令行。
? extends Shell
下面看实现Shell抽象类的一些子类的实现。
ShellComandExecutor类的实现如下所示:
- public static class ShellCommandExecutor extends Shell {
-
- private String[] command;
- private StringBuffer output;
-
- public ShellCommandExecutor(String[] execString) {
- command = execString.clone();
- }
-
- public ShellCommandExecutor(String[] execString, File dir) {
- this(execString);
- this.setWorkingDirectory(dir);
- }
-
- public ShellCommandExecutor(String[] execString, File dir, Map<String, String> env) {
- this(execString, dir);
- this.setEnvironment(env);
- }
-
-
- public void execute() throws IOException {
- this.run();
- }
-
- protected String[] getExecString() {
- return command;
- }
-
-
-
-
- protected void parseExecResult(BufferedReader lines) throws IOException {
- output = new StringBuffer();
- char[] buf = new char[512];
- int nRead;
- while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {
- output.append(buf, 0, nRead);
- }
- }
org.apache.hadoop.fs.DF类实现了Unix系统中Shell命令df,用来获取磁盘使用情况的统计数据。该Shell实现类中定义域df命令操作相关的内容,可以从属性来看:
- public static final long DF_INTERVAL_DEFAULT = 3 * 1000;
- private String dirPath;
- private String filesystem;
- private long capacity;
- private long used;
- private long available;
- private int percentUsed;
- private String mount;
只需要实现Shell类定义的getExecString与parseExecResult方法即可。比较简单,getExecString方法实现如下:
- protected String[] getExecString() {
- return new String[] {"bash","-c","exec 'df' '-k' '" + dirPath + "' 2>/dev/null"};
- }
该方法返回的字符串数组,用来构造一个ProcessBuilder进程实例。
parseExecResult方法实现如下所示:
- protected void parseExecResult(BufferedReader lines) throws IOException {
- lines.readLine();
- String line = lines.readLine();
- if (line == null) {
- throw new IOException( "Expecting a line not the end of stream" );
- }
- StringTokenizer tokens = new StringTokenizer(line, " /t/n/r/f%");
-
- this.filesystem = tokens.nextToken();
- if (!tokens.hasMoreTokens()) {
- line = lines.readLine();
- if (line == null) {
- throw new IOException( "Expecting a line not the end of stream" );
- }
- tokens = new StringTokenizer(line, " /t/n/r/f%");
- }
-
-
-
- this.capacity = Long.parseLong(tokens.nextToken()) * 1024;
- this.used = Long.parseLong(tokens.nextToken()) * 1024;
- this.available = Long.parseLong(tokens.nextToken()) * 1024;
- this.percentUsed = Integer.parseInt(tokens.nextToken());
- this.mount = tokens.nextToken();
- }
DU类实现了Unix的du命令,显示目录或者文件大小的信息,具体实现可以参考org.apache.hadoop.fs.DU类,这里跳过。
CygPathCommand类是org.apache.hadoop.fs.FileUtil类的一个内部静态类,实现了Windows系统上模拟Unix系统的Cygwin系统的cygpath命令,这里跳过。