利用scala解析hdfs上tar.gz文件中的xml文件并存入hdfs
前言
上一篇文章介绍了利用java对tar.gz文件进行解压并对原始xml文件的数据进行解析,如果不了解的可以去看看上一篇文章!!当时就存在很多问题,比如数据存储在hdfs上,上一篇的方法需要将数据解压在本地再上传到hdfs上,数据量过大导致集群IO根本就承受不了!所以就对方案进行整改,目前的架构就是shell脚本从ftp拉取数据后存入hdfs中,如果数据存入hdfs成功就将数据在hdfs上的路径存入kafka,类似spark streaming消费kafka数据(文件的路径)然后对其解压解析,最后再存入hdfs中!本篇文章就介绍其中的一个板块!读取tar.gz压缩文件并解析入库!
依赖
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>2.11.8</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-compiler</artifactId> <version>2.11.8</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-reflect</artifactId> <version>2.11.8</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-10_2.11</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>1.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.12</artifactId> <version>1.0.0</version> </dependency>
代码
import java.io.{BufferedInputStream, ByteArrayInputStream, ByteArrayOutputStream} import java.net.URI import java.util.zip.GZIPInputStream import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveStreamFactory} import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, Path} import scala.xml.XML object boke { def main(args: Array[String]): Unit = { val Array(pwd,hdfspath) = args var datalist:List[String] = List() //path 类似于 /tmp/test/1.tar.gz val fileNameList = pwd.split('/') if (!pwd.endsWith("tar.gz")) { println(pwd + ":不是一个tar.gz文件。") } val conf = new Configuration(); val fs = FileSystem.get(URI.create(pwd), conf); if(! fs.isFile(new Path(pwd))){ println(pwd + ":文件不存在。") } println("start......") //读取hdfs上压缩文件 tar.gz文件 val GZIPInputStream = new GZIPInputStream(fs.open(new Path(pwd))) //in 这个流是tar.gz 读取到数据是gz文件 val in = new ArchiveStreamFactory().createArchiveInputStream("tar", GZIPInputStream) var tBufferedInputStream = new BufferedInputStream(in) // entry 可以看作是 gz压缩文件 var entey:ArchiveEntry = null while (in.canReadEntryData( entey = in.getNextEntry)){ //字节缓冲流 存放gz文件的流 var gzipTmp = new ByteArrayOutputStream var b = tBufferedInputStream.read() while (b != -1){ gzipTmp.write(b) b = tBufferedInputStream.read() } if (gzipTmp.size() >0 && gzipTmp.size() == entey.getSize){ println("gz文件已经读取完成") }else{ println("文件未读取完成") } //用于存放xml文件的流 var xmlin = new ByteArrayOutputStream() //再次读取gz压缩文件到xml流中 val is = new GzipCompressorInputStream(new BufferedInputStream(new ByteArrayInputStream(gzipTmp.toByteArray))); var c = is.read() while (c != -1){ xmlin.write(c) c = is.read() } if (xmlin.size()>0){ println("xml文件读取完成") } //接收输出流 转换输入流 进行xml 解析 xmlData就是xml文件 就可以根据业务逻辑解析 val xmlData = XML.load(new ByteArrayInputStream(xmlin.toByteArray())) //得到fileHeader val xmlFileHeader = xmlData \\ "bulkPmMrDataFile" \ "fileHeader" val reporttime = (xmlFileHeader \ "@reportTime").toString().replace('T', ' ') val starttime = (xmlFileHeader \ "@startTime").toString().replace('T', ' ') val endtime = (xmlFileHeader \ "@endTime").toString().replace('T', ' ') val s = reporttime+"|"+starttime+"|"+endtime datalist = datalist :+ s //当数据量过大时,先写入一部分数据 if (datalist.size >= 3000){ val conf=new Configuration() val sc=FileSystem.get(URI.create(hdfspath),conf) val hdfsOutStream = sc.create(new Path(hdfspath+"/"+"1.txt")) hdfsOutStream.write(datalist.mkString("|").getBytes("UTF-8")) datalist = Nil hdfsOutStream.close() } } //循环完之后写入剩余数据 if (datalist.size >= 0){ val conf=new Configuration() val sc=FileSystem.get(URI.create(hdfspath),conf) val hdfsOutStream = sc.create(new Path(hdfspath+"/"+"1.txt")) hdfsOutStream.write(datalist.mkString("|").getBytes("UTF-8")) datalist = Nil hdfsOutStream.close() } } }
以上代码只是我写的一部分示例代码,并不是项目上使用的!只是演示了从hdfs上读取压缩文件并解析入库的一部分内容!在项目的实施过程中,遇到过很多问题,比如如何保证kafka投递的数据不会倾斜(采用的是投递数据前,获取kafka个分区的lag,将数据投递到lag最小的分区),spark streaming读取kafka数据是批处理的,他需要将这一批次处理完成才消费下一批次数据,测试表明,数据量过大根本解析不过来,造成kafka数据堆积(最后解决借用spark streaming的机制,kafka有多少分区就开启多少线程去消费数据,一个线程一直消费一个分区的数据),这个方案都是根据我们的业务逻辑去调整的,如果有什么疑问可以私信我一起探讨学习!!
原创文章!!!!!!