纯脚本实现 sftp采集lzo压缩文件解压缩直接入hdfs(中途不落地,全程linux管道操作)

     最近处理大数据项目,客户有两个数据中心,数据采集,传输成了整个项目的最棘手问题。最近又有sftp数据文件采集要求,一天13TB之巨,将近8-13万个文件,并且每个文件都是lzo格式压缩过的。进入Hadoop集群之前,还需要解压缩成明文,才能进一步处理。

     第一版实现是个什么过程呢?,首先一个expect批处理,所有文件下载到文件接口机。然后再一个批处理,全部文件解压缩。之后再把解压缩的文件put到本地数据中心hadoop,之后再distcp跨集群copy到远程中心。流程简单,但是时间是老长了,采集1天的数据需要3天时间。

      慢的原因很简单,首先sftp到本地要落地一次硬盘,本地解压缩,又要从硬盘里读出来再写回本地硬盘,之后又要把解压缩膨胀后的数据再写本地数据中心hadoop集群HDFS硬盘,之后又要把本地中心的HSDF硬盘数据读出来,发送到远程中心写硬盘。这一个流程里,光数据落硬盘就经历4次。13TB数据写4次硬盘,才能采集到目标,数据写入硬盘这种低速设备的开销很显然太大。

       怎么解决?并发来,边采集,边解压缩,边put,边distcp,任务切块,多线程并发。但是集群磁盘和本地磁盘的读写压力都骤增,速度提升也并不十分明显。如何解决呢?很快有答案,减少落地次数,最好一次到位。于是尝试直接用java工程去解决,采集很麻利的搞定了,然后解压缩lzo,出了奇葩的问题。对端不知道用的什么压缩技术,lzo文件用linux的lzop命令可以解压,但是java的各种包试了个遍,解不出内容。于是这事儿就一直拖着,难有进展。

        最近客户有些发飙,不能够理解采集1天的数据需要3天。迫于客户压力,自然由我出马。于是细致介入之后。最终决定化繁为简。全部使用linux管道来完成操作,不需要开发代码。命令串成一条线。入库本地中心,只需要一次直接入HDFS,中途不需要落地。

         写个测试脚本,一次成功!

scptohdfs.sh

#!/bin/bash
scp   sftpUserName@$1:$2 /dev/fd/1 | lzop -dcf | hadoop fs -put /dev/fd/0 /tmp/datatest/$3

          就这么简单,简单说一下:

          $1脚本第一个参数是目标sftp服务器IP或者主机名。

          $2sftp主机需要采集的文件名。

          $3目标HDFS文件系统路径下的文件名,这里只写目标文件。

          /dev/fd/# 是进程所有已打开的文件描述符列表,例如 /dev/fd/0 代表标准输入,/dev/fd/1 代表标准输出,/dev/fd/2 代表标准错误输出,等等,打开 /dev/fd/n 相当于调用 dup(n) 。每个进程都有自己的/dev/fd。

          命令读起来就很容易了,scp的目标是标准输出,标准输出通过管道之后,变成lzop的标准输入,lzop -f强制输出解压缩的内容会输出在标准输出,标准输出又作为了hadoop命令的标准输入,以hadoop fs -put 命令的标准输入,最终落地在HDFS文件系统中。是不是很强大。其实大数据相关业务,工具功能都很强大,配合linux自身的特性,90%的业务,都不需要高级语言进行编码,就可以实现。比如数据入hdfs之后,可以用beeline链接hive用hsql搞定业务数据处理。

  使用示例:

  scptohdfs.sh  xxx.xxx.xxx.xxx   /data/xxxx/xxxx_20190930_xxxx.lzo       xxxx_20190930_xxxx.txt

   测试一次性成功。只是这只实现了,采集1个文件,并且在管道里解压缩,然后put到本地数据中心对应目录的作用。很显然,脚本嵌套嘛!另外expect一下,自动输入登陆密码,不就可以搞定批量文件。

            但是噩梦出现了,不管怎么修改scptohdfs.sh,expect总是无法识别到password标准提示输出,搜遍全球。没人像我这样用linux!!难道是我用的过于炉火纯青了?思来想去,感觉可能是expect无法正常确定使用管道的哪个标准输入和输出,导致的混乱无法识别。于是,scptohdfs.sh就不动了。单独写个expect脚本来调用整个脚本,而不是scp命令。这样虽然scp的标准输入和输出经过了多次管道中转,但在脚本之下,脚本进程自身的标准输入、输出是明确的。于是试一下,终于成功,此时已过去3个多小时!!解决问题不容易啊!!上脚本代码!!

    scputil.sh        

#!/usr/bin/expect
set timeout -1
set ip [lindex $argv 0]
set int_file [lindex $argv 1]
set out_file [lindex $argv 2]


spawn  sh  ./scptohdfs.sh $ip $int_file $out_file

expect {
" (yes/no)?"
  {
  send "yes\n"
  expect "*assword:" { send "password\n"}
 }
 "*assword:"
{
 send "password\n"
}
}
expect eof

搞定,这可以自动执行了。考虑到linux的shell并发控制不太好,比较繁琐。刚好机器上有anaconda3的python3.6的环境。干脆用简单的python脚本,读配置文件,搞个多线程并发调用就行了。

 废话不多说,直接上脚本。

hscpThreadPool.py

#!/usr/anaconda3/bin/python

import subprocess
import pandas as pd
import logging 
import logging.config 
from concurrent.futures import ThreadPoolExecutor,as_completed

#logging打日志很方便,下面这个自然是配置文件咯
LOG_CONFIG = './log.ini' 

logging.config.fileConfig(LOG_CONFIG)
log = logging.getLogger()

#继续调脚本啦,当然为了线程池并发,要封装成方法
def scpToHdfs(srcPath,disPath):
    log.debug(srcPath+"-->"+disPath)
    re=subprocess.getoutput("./scputil.sh  10.1.1.1 "+srcPath+" "+disPath)
    log.debug(re)
    #来点儿特殊标记,方便看日志统计进度,什么?不会统计?哎~~知识要系统化点儿嘛
    #送佛到西天吧! cat logfile.log|grep cpOK|wc -l 就可以知道完成了多少个文件的采集
    #不知道总量有多少?哎!我要打人了! cat  fileList.ini|wc -l 不就知道总量了嚒!!
    log.info("cpOK "+srcPath+"-->"+disPath)
    


#最大并行度10个的线程池
ex = ThreadPoolExecutor(max_workers=10)
future_list=[]
#读配置文件啦!!什么?不知道配置文件怎么生成?二货啊,我告诉你吧
#ssh  sftpusernam@ip    ls /xxxx/xxxx_20190930_* >fileList.ini
#这样就搞定啦!!
df =pd.read_csv('/home/bduser/bxufile/fileList.ini',sep=';',header=None,dtype='str')
#最爽还是pandas,读文件优雅,遍历更优雅
for index, row in df.iterrows():
    sPath=row[0]
    #这句看不懂?回家种田吧,你做不了开发!!~~不是打击你,是苦海无涯回头是岸,施主!
    dPath=row[0].split("/")[3][0:-4]
    #提交任务喽,不会阻塞的,会全部提交完哦,注意啦~~
    f=ex.submit(scpToHdfs,sPath,dPath)
    future_list.append(f)

#as_completed对任务集进行遍历,注意哦,这里任务不全部执行完会在这里一直循环,as_completed是阻塞的。
for res in as_completed(future_list):
    log.debug(res.result())

"日志的配置可以给一下嚒?"我说啊,人懒到这种地步已经是无敌了。不过还是提供给吧:

log.ini

[loggers]
keys=root,xzs

[handlers]
keys=consoleHandler,fileHandler,rotatingFileHandler

[formatters]
keys=simpleFmt,messageFmt

[logger_root]
level=DEBUG
handlers=consoleHandler,rotatingFileHandler
#handlers=fileHandler
#handlers=rotatingFileHandler

[logger_xzs]
level=DEBUG
handlers=rotatingFileHandler
qualname=xzs
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=messageFmt
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFmt
args=("./cpfile.log", "a")


[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFmt
args=("./cpfile.log", "a", 20*1024*1024, 10)

[formatter_simpleFmt]
format=%(asctime)s-[%(filename)s:%(lineno)s]-PID[%(process)d]-TID[%(thread)d]-%(levelname)s 
 %(message)s
datefmt=%Y-%m-%d %H:%M:%s

[formatter_messageFmt]
format=%(levelname)s - %(message)s

ok,至此关键技术已经解决。剩下的事情小组成员自己去添砖加瓦,就不需要我这个老家伙出场了。整个效率大概提升4倍多,基本13TB的数据采集入本地集群只需要8个小时左右。

        一入IT深似海,活到老学到老,学无止境,也请山外高人点评此贴,给予指正,切磋切磋。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值