之前一直忙于写代码,也没有整理一下思路来做一个技术点的分享,也该整理一下技术相关的东西。分两篇吧,一篇太长了也不利于阅读,废话写多了,大家看着困。当然我的技术水平也很烂,懂行的不要见笑就好了。
第一篇写EasyHadoop,第二篇写phpHiveAdmin
EasyHadoop最早只是一个hadoop的安装Shell脚本,这就没有什么可说的了,界面化的分布式安装还有有些知识点可以总结和分享下的,或许能对开发者有所帮助。
EasyHadoopManager
分为两个部分,Central和Agent
Central为前端展示界面,可以实现对Agent的界面化操作,php语言编写。模型时期采用自己编写Socket协议与Agent之间进行通信,用fsockopen实现命令的发送和文件传输。后来发现这种方式不太稳定,Agent所在的tty关闭后会出现Agent进程僵死。所以后来放弃了,采用thrift方式重构了socket的所有相关代码。以后前端再开发一个用expect脚本推送安装agent就可以了。
Agent部分,跟Central配合,模型时期自己写的一个python脚本,用atexit和os等模块实现了daemon,socket部分自己实现了一个基于线程的socket server。但是因为上述问题,最后放弃了。后来采用thrift方式重构了Agent的代码,更稳定,更合理,也更标准。
模型时期Agent代码片段如下:
import sys
import socket
import threading
import time
import atexit
import os
import subprocess
import string
import platform
from optparse import OptionParser
from signal import SIGTERM
......
class Server:
def __init__( self ):
self.sock = None
self.thread_list = []
def close ( self ):
del self.sock
sys.exit(1)
def run( self ):
all_good = False
try_count = 0
while not all_good:
if 0 < try_count:
sys.exit( 1 )
try:
self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
self.sock.bind( ( '0.0.0.0', 30050 ) )
self.sock.listen( 5 )
all_good = True
break
except socket.error, err:
print 'Socket was in used'
del self.sock
time.sleep( 1 )
try_count += 1
try:
while not QUIT:
try:
self.sock.settimeout( 0.500 )
client = self.sock.accept()[0]
except socket.timeout:
time.sleep( 1 )
if QUIT:
break
continue
threaded = ClientThread( client )
print threaded.getName()
self.thread_list.append( threaded )
threaded.start()
for thread in self.thread_list:
if not thread.isAlive():
self.thread_list.remove( thread )
thread.join()
for thread in self.thread_list:
thread.join( 1.0 )
self.sock.close()
......
程序执行后会退出主进程,在后台fork一个子进程,该子进程会创建基于线程的socket server出来。篇幅所限,fork部分的代码就不贴了。fork由引入的sys,os,atexit等完成。socket由socket,threading等完成。
subprocess的作用是把Central发过来的命令进行执行,并从stderr和stdout读取执行结果返回Central界面,让用户知道该节点的执行结果。
class Install:
def __init__( self, stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr' ):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
def RunShellScript(self, command):
a = subprocess.Popen( command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
if command.find("start") == -1:
tmp_out = a.stdout.readlines()
tmp_err = a.stderr.readlines()
else:
tmp_out = a.stdout.readline()
tmp_err = a.stderr.readline()
tmp = tmp_out + tmp_err
return tmp
之后是第二版的Agent,采用thrift接口开发。开发简单了许多,thrift把socket的通信和协议都封装好了,只要调用封装的类和方法就好了。不用自己区分命令执行和文件传输的协议了,也不用自己去做文件传输的结构化封装,比较省事。thrift之前有些过文章介绍使用方法,不再赘述,很好的一个接口开发工具。
目前的代码片段:
import atexit
import subprocess
import string
import platform
import os
import sys
import time
from optparse import OptionParser
from signal import SIGTERM
sys.path.append('./thrift')
from thrift.EasyHadoop.EasyHadoop import *
from thrift.EasyHadoop.EasyHadoop.ttypes import *
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
......
class Daemon:
def _run(self):
handler = EasyHadoopHandler()
processor = EasyHadoop.Processor(handler)
transport = TSocket.TServerSocket(self.host,self.port)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
while True:
print 'Starting server'+os.linesep
server.serve()
......
代码更加精简,清晰,基于线程池的socket server。
访问全部代码可以去 http://github.com/xianglei/easyhadoop
软件的设计构思是,Agent只负责接收命令执行和文件的传输保存,其他一切控制都放在php的Central中封装成类和方法。这样对需要改造的用户来说是最清晰和简单的。至于安装部署hadoop,你只需要具备基本的linux操作技能就可以了,只要会部署LNMP的server就可以用EasyHadoop安装部署hadoop,并且这个思路也易于用户改造成其他自动化运维的应用。
我的前端水平很烂的,前端相关代码就没什么可说的了,bootstrap框架还是别人教我用的。
至于为什么自己开发了一套分布式Hadoop的安装部署软件,主要是懒,大公司的要花钱,ambari免费,但要求用户部署ruby, puppet, ssh免密码,LDAP,太麻烦了。明明很简单就能做的事,为什么要搞那么复杂呢?也正好看看自己能不能按照Cloudera的方式做一套开源的东西出来,省得大家花钱去买那么贵的东西。Python也是服务器装系统的时候就自带了,不用用户自己去安装。最大限度简化用户操作。目前支持跑在CentOS5-6上,兼容python 2.4-2.7。界面现在做的还比较土鳖,没那么炫,不过起码也能使,算是实现了一个模型出来了。