标签: SparkStreaming
HDFS
配置文件
MySql
为什么需要动态读取配置文件?
在之前的项目中一直使用的读配置文件的模式是在应用启动阶段一次性读取配置文件并获取到其中的全部配置内容。并且,在程序运行过程中这些配置不能被改变,如果需要改变,则需要重新打包发布应用。
在代码测试阶段这种方式会很麻烦也很费时,所以需要一种能让应用动态更新配置的方法。
一个用户编写的提交给Spark集群执行的application包含两个部分:
那么哪些作为**"驱动代码"在Driver进程中执行,哪些"任务逻辑代码”**被包装到任务中,然后分发到计算节点进行计算?
了解了Spark的执行流程之后,不难发现,尽管Spark的开发者很努力的让Spark编程模式尽可能的靠近普通的顺序执行的编程模式。但是作为一个分布式执行过程,还是跟普通编程模式有很大区别,不注意的话很容易踩到坑。
我也想过了许多解决方案,但是大部分都卡在了这个分布式的坑上边。
比如:
在这些方向都走不通之后,我又重新回到Spark本身的执行流程上思考。Spark的action操作会发布到各个计算节点进行执行,如果把读取配置文件的操作写在action操作里带到各个节点进行执行,应该可以实现让每个节点都读取到配置文件,且可以实时改变。测试的结果也证明了这种方式是可行的。
有了思路接下来就是代码测试。上面的三种失败方案也都进行了代码测试证明是不可行的。
首先是从HDFS中读取配置文件,在这里我写了个工具类:
object HDFSUtil {
val conf: Configuration = new Configuration
var fs: FileSystem = null
var hdfsInStream: FSDataInputStream = null
val prop = new Properties()
//获取文件输入流
def getFSDataInputStream(path: String): FSDataInputStream = {
try {
fs = FileSystem.get(URI.create(path), conf)
hdfsInStream = fs.open(new Path(path))
} catch {
case e: IOException => {
e.printStackTrace
}
}
return hdfsInStream
}
//读取配置文件
def getProperties(path:String,key:String): String = {
prop.load(this.getFSDataInputStream(path))
prop.getProperty(key)
}
然后是SparkStreaming初始化以及处理TCP流数据:
val conf = new SparkConf().setAppName("write data to mysql")
val ssc = new StreamingContext(conf,Seconds(10))
val streamData = ssc.socketTextStream("T002",9999)
val wordCount = streamData.map(line =>(line.split(",")(0),1)).reduceByKey(_+_)
val hottestWord = wordCount.transform(itemRDD => {
val top3 = itemRDD.map(pair => (pair._2, pair._1))
.sortByKey(false).map(pair => (pair._2, pair._1)).take(3)
ssc.sparkContext.makeRDD(top3)
})
hottestWord.foreachRDD( rdd =>{
rdd.foreachPartition(partitionOfRecords =>{
val path = "hdfs:///home/wuyue/property/test.properties"
val MD5Value = HDFSUtil.getHdfsFileMd5(path)
val sql=HDFSUtil.getProperties(path,"sql")
HDFSUtil.close
测试读取的数据是一条sql语句,如果能读取到就可以把数据正确的存入MySql中,如果读取不到配置,程序就会报错。连接数据库的代码如下:
val connect = scalaConnectPool.getConnection
connect.setAutoCommit(false)
val ps = connect.prepareStatement(sql)
partitionOfRecords.foreach(record =>{
val word = record._1
val count = record._2
ps.setString(1,word)
ps.setInt(2,count)
ps.addBatch()
})
ps.executeBatch()
connect.commit()
scalaConnectPool.closeConnection(ps,connect)
我写了一个数据库连接池方便进行数据库连接,因为获取数据库的连接并不是很耗性能,sql语句的执行最耗性能,所以出于性能角度考虑,存入MySql的操作我使用的是批处理模式。
经过测试,数据能够正确的存入数据库中,并且手动更改配置文件之后程序出错停止,说明程序能够读到配置文件中的变化并进行更新。
因为Spark读取数据的操作是分布在各个计算节点执行的,如果使用传统的文件资源管理器就必须在每个节点机器的目录下都存放一份配置文件,并且在改动时要同时进行,这是很不方便的。所以在测试中使用HDFS(分布式文件系统)存储配置文件。
但是HDFS主要用来做大数据量批量读写操作的,对单个文件的随机读写会很慢。
所以我在要读取配置文件之前增加了一个对文件是否改动进行的判断,如果配置文件发生变化则重新读取文件,如果没有变化则不读取。
具体实现方式为获取文件的MD5值,如果MD5值发生变化说明文件有改动,如果不变说明文件没有改动。
但即使是这样,在HDFS上进行随机读写依然很耗性能。因为是测试阶段,主要为了证明这种方式是可行的,在进一步的测试中可以考虑使用MySql或者Redis替换掉HDFS来存储配置文件。
其他相关资料已经同步到我的博客网站,欢迎访问我的个人博客了解更多内容。