之前一直忙于写代码,也没有整理一下思路来做一个技术点的分享,也该整理一下技术相关的东西。分两篇吧,一篇太长了也不利于阅读,废话写多了,大家看着困。当然我的技术水平也很烂,懂行的不要见笑就好了。

第一篇写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。界面现在做的还比较土鳖,没那么炫,不过起码也能使,算是实现了一个模型出来了。

也希望这种思路对大家在集群自动化运维方面有所帮助,下一篇总结一下phpHiveAdmin用到的比较好玩的技术点。