我们在执行linux系统的shell命令时,会用到PHP调用系统命令函数来实现。那么在这些函数中,主要包括了system(),exec(),passthru()这三个经常用于外部命令调用的函数。
虽然这三个命令都能执行linux系统的shell命令,但是其实他们是有区别的:
system() 输出并返回最后一行shell结果。
exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。
passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。
相同点:都可以获得命令执行的状态码
在PHP中调用外部命令,可以用如下三种方法来实现:
用PHP提供的专门函数
PHP提供共了3个专门的执行外部命令的PHP调用系统命令函数:system(),exec(),passthru()。
system()
原型:string system (string command [, int return_var])
system()函数很其它语言中的差不多,这个PHP调用系统命令函数执行给定的命令,输出和返回结果。第二个参数是可选的,用来得到命令执行后的状态码。
例子:
system("/usr/local/bin/webalizer/webalizer")
exec()
原型:string exec(string command [, string array [, int return_var]])
exec()函数与system()这个PHP调用系统命令函数类似,也执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array 可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二个参数时,才可以用第三个参数,用来取得命令执行的状态码。
例子:
exec("/bin/ls -l")
exec("/bin/ls -l", $res)
exec("/bin/ls -l", $res, $rc)
passthru()
原型:void passthru (string command [, int return_var])
passthru ()只调用命令,这个PHP调用系统命令函数不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus (Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。
例子:
header("Content-type: image/gif")
passthru("./ppmtogif hunte.ppm")
六种用ruby调用执行shell命令的方法
碰到需要调用操作系统shell命令的时候,Ruby为我们提供了六种完成任务的方法:
1.Exec方法:
Kernel#exec方法通过调用指定的命令取代当前进程:
例子:
$ irb
>>exec 'echo "hello $HOSTNAME"'
hello nate.local
$
值得注意的是,exec方法用echo命令来取代了irb进程从而退出了irb。主要的缺点是,你无法从你的ruby脚本里知道这个命令是成功还是失败。
2.System方法。
Kernel#system方法操作命令同上, 但是它是运行一个子shell来避免覆盖当前进程。如果命令执行成功则返回true,否则返回false。
$ irb
>>system 'echo "hello $HOSTNAME"'
hello nate.local
=>true
>>system 'false'
=>false
>>puts $?
256
=>nil
>>
3.反引号(Backticks,Esc键下面那个键)
$ irb
>>today = `date`
=>"Mon Mar 12 18:15:35 PDT 2007n"
>>$?
=>#<Process::Status: pid=25827,exited(0)>
>>$?.to_i
=>0
这种方法是最普遍的用法了。它也是运行在一个子shell中。
4.IO#popen
$ irb
>>IO.popen("date") { |f| puts f.gets }
Mon Mar 12 18:58:56 PDT 2007
=>nil
5.open3#popen3
$ irb
>>stdin, stdout, stderr = Open3.popen3('dc')
=>[#<IO:0x6e5474>, #<IO:0x6e5438>, #<IO:0x6e53d4>]
>>stdin.puts(5)
=>nil
>>stdin.puts(10)
=>nil
>>stdin.puts("+")
=>nil
>>stdin.puts("p")
=>nil
>>stdout.gets
=>"15n"
6.Open4#popen4
$ irb
>>require "open4"
=>true
>>pid, stdin, stdout, stderr = Open4::popen4 "false"
=>[26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
>>$?
=>nil
>>pid
=>26327
>>ignored, status = Process::waitpid2 pid
=>[26327, #<Process::Status: pid=26327,exited(1)>]
>>status.to_i
=>256
1\模块如果退出Python解释程序然后再进入,原有的定义(函数和变量)就丢失了。所以,如
果需要写长一点的程序,最好用一个文本编辑程序为解释程序准备输入,然后以程序文
件作为输入来运行Python解释程序,这称为准备脚本(script)。当你的程序变长时,
最好把它拆分成几个文件以利于维护。你还可能想在几个程序中都使用某个很方便的函
数,但又不想把函数定义赋值到每一个程序中。
为了支持这些,Python有一种办法可以把定义放在一个文件中然后就可以在一个脚本中
或交互运行中调用。这样的文件叫做一个模块;模块中的定义可以导入其它模块或主模
块(主模块指在解释程序顶级执行的脚本或交互执行的程序所能访问的变量集合)。
模块是包含了Python定义和语句的文件。文件名由模块名加上后缀“.py”构成。在模块
内,模块的名字(作为一个字符串)可以由全局变量__name__的值获知。例如,在Pyth
on的搜索路径中用你习惯使用的文本编辑器(Python 1.5.2包含了一个用Tkinter编写的
IDLE集成开发环境,MS Windows下有一个PythonWin界面也可以进行Python程序编辑)生
成一个名为“fibo.py ”的文件,包含如下内容:
# Fibonacci numbers module
def fib(n):# 输出小于n的Fibonacci序列
a, b = 0, 1
while b <n:
print b,
a, b = b, a+b
def fib2(n): # 返回小于n的Fibonacci序列
result = []
a, b = 0, 1
while b <n:
result.append(b)
a, b = b, a+b
return result
然后进入Python解释程序(在IDLE或PythonWin中可以直接进入解释程序窗口),用如下
命令可以导入模块:
>>>import fibo
这不会把模块fibo中的函数的名字直接引入当前的符号表,这只是把模块名fibo引入。
可以用模块名来访问其中的函数:
>>>fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>>fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>>fibo.__name__
'fibo'
如果经常使用某个函数可以给它赋一个局部名字:
>>>fib = fibo.fib
>>>fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1 模块的进一步介绍
模块除了可以包含函数定义之外也可以包含可执行语句。这些可执行语句用来初始化模
块,它们只在模块第一次被导入时执行。
每个模块有自己私有的符号表,这个私有符号表对于模块中的所有函数而言却是它们的
全局符号表。因此,模块作者可以在模块中使用全局变量而不需担心与模块用户的全局
变量冲突。另一方面,如果你有把握的话也可以用访问模块中函数的格式,即modname.
itemname的方法来修改模块中的全局变量。
模块可以导入其它模块。我们通常把所有的导入语句放在模块(或脚本)的开始位置,
这不是规定要求的。导入的模块名放入模块的全局符号表中。
导入还有另一种用法,可以把模块中的名字直接导入使用者的符号表。例如:
>>>from fibo import fib, fib2
>>>fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这不会把模块名导入使用者的符号表中(例如,上面例子中fibo就没有定义)。
还有一种办法可以导入一个模块中定义的所有名字:
>>>from fibo import *
>>>fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这可以把模块中除了以下划线结尾的所有名字导入。
6.1.1 模块搜索路径
在导入名为spam的模块时,解释程序先在当前目录中寻找名为“spam.py”的文件,然后
从环境变量PYTHONPATH所定义的目录列表中寻找。PYTHONPATH的用法和可执行文件的搜
索路径PATH用法相同,都是一个目录列表。当PYTHONPATH未设置的时候,或者文件仍找
不到,则搜索继续在安装时设定的缺省路径搜索,在Unix中,这通常是“.:/usr/local
/lib/python” 。
实际上,模块是按变量sys.path指定的路径搜索的,此变量在解释程序启动时初始化为
包含输入脚本的目录(或当前路径),PYTHONPATH和安装缺省路径。这样,用户可以通
过修改sys.path 来修改和替换模块搜索路径。参见后面关于标准模块的一节。
6.1.2 “编译”的Python文件
为了提高调用许多标准模块的小程序的启动时间,一个重要的措施是,如果在找到“sp
am.py ”的目录中存在一个名为“spam.pyc”的文件,就认为此文件包含了模块spam的
一个所谓“ 字节编译”版本。用于生成“spam.pyc”的“spam.py”的修改时间被记入
了“spam.pyc”中,如果记录的修改时间与现在文件的时间不相符的话就忽略编译文件
。
一般不需要自己生成“spam.pyc”这样的编译文件。每当“spam.py”成功编译后解释程
序就尝试写编译版本“spam.pyc”,如果不可写也不会出错;如果因为某种原因此文件
没有写完则生成的“spam.pyc”被识别为不完整的而被忽略。编译文件“spam.pyc”的
格式是不依赖于平台的,所以不同结构的机器可以共享Python模块目录。
下面是对专家的一些窍门:
如果Python解释程序是以-O标志启动的,将生成优化的编译代码,保存在“.pyo”文件
中。目前优化不是很多,现在只是去掉assert语句和SET_LINENO指令。使用了-O标志时
,所有字节码都是优化的,“.pyc”文件被忽略,“.py”文件被编译为优化的字节码。
给Python解释程序两个优化标志(-OO)产生的优化代码有时会导致程序运行不正常。目
前双重优化只从字节码中删除了__doc__字符串,使得“.pyo”文件较小。有些程序可能
是依赖于文档字符串的,所以只有在确知不会有问题时才可以使用这样的优化。
从“.pyc”或“.pyo”读入的程序并不能比从“.py”读入的运行更快,它们只是调入速
度更快一些。
如果一个程序是用在命令行指定脚本文件名的方式运行的,脚本的字节码不会写入“.p
yc ”或“.pyo”文件。所以如果把程序的主要代码都移入一个模块,脚本中只剩下导入
该模块的引导程序则可以略微缩短脚本的启动时间。
可以有叫做“spam.pyc”(当用了-O标志时为“spam.pyo”)的文件而没有对应的源文
件“spam.py”。这可以用来分发一个比较难反编译的Python代码库。
模块compileall可以把一个目录中所有模块编译为“.pyc”文件(指定了-O选项时编译
为“.pyo”文件)。
6.2 标准模块
Python带有一个标准模块库,在另一个文档《Python库参考》中进行了描述。一些模块
直接编入了解释程序中,这些模块不是语言的核心,为了运行效率或者为了提供对于系
统调用这样的系统底层功能而编入了解释程序中。提供那些模块是编译时的选择,例如
,amoeba模块只在提供amoeba底层指令的系统中才能提供。
有一个模块值得特别重视:sys模块,每一个Python解释程序中都编译入了这个模块。变
量sys.ps1和sys.ps2定义了交互运行时的初始提示和续行提示。
>>>import sys
>>>sys.ps1
'>>>'
>>>sys.ps2
'... '
>>>sys.ps1 = 'C>'
C>print 'Yuck!'
Yuck!
C>
这两个变量只在解释程序以交互方式运行时才有定义。
变量sys.path是一个字符串列表,由它确定解释程序的模块搜索路径。它被初始化为环
境变量PYTHONPATH所指定的缺省路径,环境变量没有定义时初始化为安装时的缺省路径
。可以用标准的列表操作修改这个搜索路径,例如:
>>>import sys
>>>sys.path.append('/ufs/guido/lib/python')
6.3 dir()函数
内置函数dir()用于列出一个模块所定义的名字,它返回一个字符串列表:
>>>import fibo, sys
>>>dir(fibo)
['__name__', 'fib', 'fib2']
>>>dir(sys)
['__name__', 'argv', 'builtin_module_names', 'copyright', 'exit',
'maxint', 'modules', 'path', 'ps1', 'ps2', 'setprofile', 'settrace',
'stderr', 'stdin', 'stdout', 'version']
没有自变量时,dir()列出当前定义的名字。
>>>a = [1, 2, 3, 4, 5]
>>>import fibo, sys
>>>fib = fibo.fib
>>>dir()
['__name__', 'a', 'fib', 'fibo', 'sys']
注意dir()列出了所有各类名字:变量名、模块名、函数名,等等。dir()不会列出内置
函数、变量的名字。要想列出内置名字的话需要使用标准模块__builtin__:
>>>import __builtin__
>>>dir(__builtin__)
['AccessError', 'AttributeError', 'ConflictError', 'EOFError', 'IOError',
'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
'MemoryError', 'NameError', 'None', 'OverflowError', 'RuntimeError',
'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError',
'ZeroDivisionError', '__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce',
'compile', 'dir', 'divmod', 'eval', 'execfile', 'filter', 'float',
'getattr', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'len', 'long',
'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'range', 'raw_input',
'reduce', 'reload', 'repr', 'round', 'setattr', 'str', 'type', 'xrange']
6.4 包
Python中可以用“包”来组织Python的模块名字空间,名字引用时可以用“带点的模块
名。例如,模块名A.B代表包“A”内名为“B”的子模块。正如使用模块可以使不同模块
的作者不用顾虑彼此的全局变量名会冲突,使用带点的模块名可以使多模块包如NumPy和
PIL的作者不需要担心彼此的模块名会冲突。
假设你有一系列处理声音文件和声音数据的模块(称为一个“包”)。有许多种不同的
声音文件格式(通常用扩展名来识别,如“wav”,“.aiff”,“.au”),所以你可能
需要制作并维护一组不断增加的模块来处理不同文件格式的转换。你还可能需要对声音
数据进行许多不同的操作(如混音、回响、均衡、产生模拟立体声效果),所以你还需
要不断增加模块来执行这些操作。一下是你的程序包的可能的结构(用一个分层文件系
统表示):
Sound/ 顶层包
__init__.py 初始化音响包
Formats/ 用于文件格式转换的子程序包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
Effects/ 用于音响效果的子程序包
__init__.py
echo.py
surround.py
reverse.py
...
Filters/ 用于滤波的子程序包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
包目录中的“__init__.py”文件是必须得,用来指示Python把这个目录看成包,这可以
防止有相同名字如“string”的子目录掩盖住在搜索路径后面一些出现的模块定义。在
最简单的情况下,“__init__.py”可以是一个空文件,它也可以包含初始化包所需的代
码,和设置“__all__”变量,这些后面会加以讨论。
包的用户可以从包中导入单独的模块,如:
import Sound.Effects.echo
这可以把子模块Sound.Effects.echo导入。要引用它也必须用全名,例如:
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
导入子模块的另一种办法是:
from Sound.Effects import echo
这同样也导入子模块echo,但调用时不需写包前缀,所以可以用如:
echo.echofilter(input, output, delay=0.7, atten=4)
另外一种写法是直接导入所需的函数或变量:
from Sound.Effects.echo import echofilter
这一次同样是调入了子模块echo,但是使其函数echofilter直接可用:
echofilter(input, output, delay=0.7, atten=4)
注意使用“from 包 import 项”这样的格式时,导入的项可以是包的一个子模块(或子
包),也可以是包内定义的其它名字如函数、类、变量。导入语句首先查找包内是否定
义了所需的项,如果没有则假设它是一个模块然后调入。如果找不到,结果引起Import
Error。
相反的,当使用“import item.subitem.subsubitem”这样的格式时,除最后一个外其
它各项都应该是包,最后一项可以是包也可以是模块,不允许是前面一项内部定义的类
、函数或变量。
6.4.1 从包中导入*
现在,如果用户写“from Sound.Effects import *”会发生什么情况?理想情况下我们
希望这应该扫描文件系统,找到所有包内的子模块并把它们都导入进来。不幸的是这种
操作在Mac和Windows平台上不能准确实现,这两种操作系统对文件名的大小写没有准确
信息。在这些平台上,不知道名为“ECHO.PY”的文件会作为模块echo、Echo还是ECHO被
导入。(例如,Windows 95在显示文件名时总是讨厌地把第一个字母大写)。DOS的8+
3文件名限制更是对长模块名造成了有趣的困难。
这个问题的唯一解决办法是由模块作者显式地提供包的索引。引入*的import语句遵循如
下规定:如果包的“__init__.py”文件定义了一个名为“__all__”的列表,这个列表
就作为从包内导入*时要导入的所有模块的名字表。因此当包的新版本发布时需要包的作
者确保这个列表是最新的。包的作者如果认为不需要导入*的话也可以不支持这种用法。
例如,文件Sounds/Effects/__init__.py 可以包含如下代码:
__all__ = ["echo", "surround", "reverse"]
这意味着from Sound.Effects import *将从Sound包中导入指定的三个子包。
如果没有定义__all__,则from Sound.Effects import *语句不会导入Sound.Effects包
中的所有子模块;此语句只能保证Sound.Effects被导入(可能是执行其初始化代码“_
_init__.py ”)并导入包中直接定义的名字。这包括由“__init__.py”定义的任何名
字和显式导入的子模块名。这也包括模块中已经在前面用import显式地导入的子模块,
例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在这个例子中,echo和surround模块被导入当前名字空间,因为它们在执行from...imp
ort 语句时已定义(在定义了__all__的情况下这一点也是成立的)。
注意用户应尽量避免使用从模块或包中导入*的做法,因为这样经常导致可读性差的代码
。尽管如此,在交互运行时可以用导入*的办法节省敲键次数,而且有些模块在设计时就
考虑到了这个问题,它们只输出遵循某种约定的名字。注意,from 包 import 特定子模
块的用法并没有错,实际上这还是我们推荐的用法,除非程序还需要用到来自其它包的
同名的子模块。
6.4.2 包内部引用
子模块常常需要彼此引用。例如,模块surround可能要用到模块echo。事实上,这样的
引用十分常见,所以import语句首先从子模块的所在包中寻找要导入的子模块才在标准
模块搜索路径查找。所以,模块surround只要写import echo或from echo import echo
filter。如果在包含本模块的包中没有找到要导入的模块,import语句将去寻找指定名
字的顶级模块。
当包组织成子包时(比如例中的Sound包),没有一种简单的办法可以引用兄弟包中的子
模块――必须使用子模块的全名。例如,如果模块Sound.Filters.vocoder要引用Sound
.Effects 包中的echo模块,它可以用Sound.Effects import echo。
通过执行类代码体创建或扩展类Class的对象
ruby 代码
class [scope::] classname [
body
end
注意:
1、如果superexpr存在,那么该superexpr应当是一个以Class对象为结果的表达式,而且它将是被定义的类的超类。
如果省略superexpr,则默认为类Object
2、所有类定义是在代码执行时候生成的。
3、所有类定义在执行后都是一个Class类的对象。
4、所有的这些对象都将赋给名为classname的常量。
5、使用域作用符(::)可以为类定义中的classname前置一个已存在的类或模块名。这样会将新的定义插入到前面定义
的模块和/或类的名字空间中,但不是在这些外部类的作用域中解释此定义。前面带有域作用符的classname将被放置在
那个类或模块所处的顶层(这样作有个什么好处)
方式二、匿名类
ruby 代码
class <<obj
body
end
该类是一个匿名类,将会和指定的对象关联。这时这个匿名类将是该关联对象的虚拟类,原来该对象的类,变成了该虚拟 类的超类。
注意:如果直接在类定义外,给你对象新增一些方法和这样为一个对象创建一个类是等价的。同样是创建一个虚拟类。
Ruby类定义和其他语言不一样,其他语言都是在编译期处理的。而ruby是在代码运行时候根据类的定义创建的。这样如下特点:
1、可以在运行时候决定类的定义。这样根据不同条件,得出不同的类的定义结构
2、如果类定义是可执行的代码,那么它一定是在某个对象的上下文中执行的。Ruby中直接把该类作为当前对象
3、这个对象的类属于Class。说明,所有创建类就是一个对象,他们的类就是Class
太多了!对不起!没有给您找完
转载 表面上看,是一套基于B/S方式实现的分布式管理系统,但其实背后的架构是基于C/S完成的。你以为他是一只鞋吗?其实他是一个吹风机。作为界面化的系统,浏览器框架是不可或缺的,但更加重要的东西在Socket上面。一、需要解决中央控制端到各节点服务器之间的通信。
这个其实牵扯到一个通信协议的问题,各语言都有自己的socket,thread的库,直接调用即可。但是这个通信协议就需要自己来完成了。既不能太简单,太简单了,明码传输,如果别人获知了这个接口,就很容易执行一些令人讨厌的操作。也不能太复杂,太复杂了等于是给自己找麻烦,所以简单的数据包编解码的工作或者用token验证的方式是需要的。通信协议起码要两种,一种是传输命令执行的协议,一种是传输文件的协议。
二、跨语言的socket通信
为什么要跨语言,主控端和代理端通信,用什么语言开发其实无所谓。但是为了给自己省事,尽可能使用服务器上已经有了的默认语言,Ambari前期采用php+puppet的方式管理集群,这不是不可以,puppet自己解决了socket通信协议和文件传输的问题,可你需要为了puppet在每台服务器上都安装ruby。我是个有点服务器和代码洁癖的人。光是为了一个puppet就装个ruby,我觉得心里特对不起服务器的资源。所以我自己写了一个python的代理端。python是不管哪个linux系统在安装的时候就都会有了。然后主控端的通信,可以用python实现,也可以用php实现,但是考虑到对于更多的使用者来说,改php可能要比改tornado简单许多,所以就没用python开发。hadoop分支版本众多,发布出去,用户要自己修改成安装适合自己的hadoop发行版,就势必要改源码,会php的明显比会python的多。php里面的model封装了所有的操作,而python只是个操作代理人的角色而已。
所以也延伸出一个问题,什么语言用来做这种分布式管理系统的代理端比较合适,我自己觉得,也就是python比较合适了,操作系统自带,原生的package功能基本够用。用java和php也可以写agent,但是你势必在各节点预先就铺设好jre或者php运行环境。这就跟为什么用python和java写mapred的人最多是一样的。没人拦着你用nodejs写mapred,也可以写,就是你得在每个节点都装v8的解释引擎,不嫌麻烦完全可以这样干。原理参看map/reduce论文,不解释。perl也是操作系统原生带的,但是perl的可维护性太差了,还是算了吧。
所以这就牵扯到一个跨语言的socket问题,理论上来说,这不存在什么问题。但这是理论上的,实际开发过程中确实存在问题,比如socket长连接,通信数据包在底层的封装方式不同。我没有使用xml-rpc的原因之一就是我听说php的xmlrpc跟其他语言的xmlrpc有不同的地方,需要修改才能用,我就没有用这种办法。最早是自己定义的操作协议,这时就遇到了这些问题,所以后来直接采用了thrift方式。就基本不存在跨语言的socket通信问题了。
三、代理端执行结果的获取
无论命令还是文件是否在代理端执行成功,都需要获取到执行结果返回给中央端。所以这里也涉及一个读取节点上的stdout和stderr的问题。这个总体来说不是很难,都有现成的包。当然这个时候你需要的是阻塞执行,而不能搞异步回调。
还有个问题是,我要尽可能使用python默认就带的包,而尽量不让服务器去访问internet下载第三方的包。
还有代理端最重要的一点,就是python的版本兼容性。centos5用python 2.4,centos6用python 2.6,ubuntu基本默认都是2.7。所以一定要最大限度的保证语言的跨版本兼容性,要是每个操作系统和每一个版本我都写一个代理,我一个人就累死了。
四、浏览器端的model,view,controller
这里面你要封装好所有的通信协议,以及需要在节点上面执行的脚本。发送文件的操作和数据库操作也要在model里面完成。
如果对tcl/tk很熟,也可以写基于操作系统界面方式的管理,不用浏览器就是了。
view对我来说是最痛苦的事,都是现学的jQuery怎么用,前端的工作太可怕了。关于这方面,没有太多可描述的,html和js带给我的只有痛苦的回忆,万恶的undefined。
五、跨操作系统的安装文件封装。
要适应不同的操作系统也是个很麻烦的事情,需要用agent提前获知操作系统的发行分支,版本号。然后去找到对应的安装文件去执行。你不能保证一个分布式系统的集群中所有的节点都可以访问internet,更多的情况是这些节点都存在在一个安全的内网中。只有个别几个节点是可以访问外网的。所以,我势必要把所有的安装文件以及他们的依赖尽可能集中起来。我不确定安装操作系统的lzo,yum或者apt-get会去下什么鬼东西,甚至无论是yum还是apt-get,里面都没有hadoop-lzo的库文件。所以,最好的办法是自己编译打包rpm和deb包。直接安装就好了,别去找repo下载什么。
这就是第五步工作,把需要的依赖的东西自己编译打包成rpm和deb。
deb包很好解决,但是rpm就没那么好办了,需要学习rpm的编译文件如何编写,这块是挺麻烦的,但是这玩意用好了还是挺不错的。现在我自制的安装包里面就已经包含了自己编译的lzo和snappy两种压缩库,以及hadoop-gpl-packaging的rpm和deb。下一个发布的easyhadoop将直接支持centos5,6,suse,以及ubuntu/debian的系统上安装hadoop。已经自带了lzo和snappy以及lzop和snzip。
六、把这些所有东西,整合到一个系统里面。
关联这些所有事情间的联系,整合到一个浏览器界面里面去。写一个分布式的管理脚本不难,写一个界面也不难,但是也许是我的水平不行,这两件事结合起来让他们协同工作还是有点难度的。对我来说,写界面的工作可能更难一点。
Cloudera可能是十来个人在写Manager的东西,ambari也是放到github和apache svn上面,apache基金会的各种committer在写。easyhadoop没他们功能那么强大,一年来只有我一个人设计架构,功能,各种语言的编码,测试,发布。For the love of god, What have I done(英文部分请站在山顶仰天长啸)? T_T。从前台到后台,到hadoop和生态系统以及他们的依赖软件的单独patch、编译打包。(系统yum或者apt-get的包不如自己打的好使。)
从时间上来看,全球第一款开源的hadoop部署管理系统应该还是属于ambari,2011年8月开始写的,2012年9月底进入apache的incubator。我是大概2012年8月开始写的easyhadoop,全球第一没赶上,估计国内第一个开源的hadoop管理系统还是可以排上的。