需求描述
數據源是一批網絡日誌數據,每條數據都有兩個字段srcip和dstip,字段之間以逗號分隔,問題的需求是給定一個srcip和dstip,在給定的搜索深度下檢索這兩個ip之間所有的通聯路徑。這個問題是網絡日誌處理中的一個實際需求,之前在單機的程序中實現過,但是需要將所有的ip對加載到內存中。考慮到如果數據量太大的情況,可能單節點的內存無法支撐這樣的操作,但是如果不將ip對全加載內存中,使用深度優先遍歷的方法,搜索過程又會很慢。最近在學習spark框架,剛接觸RDD,就是這用RDD來解決這個問題。以下是scala代碼
package com.pxu.spark.core
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
/**
* pxu
* 2021-01-29 16:57
*/
object FindIpRel {
def main(args: Array[String]): Unit = {
val srcIp = args(0) // 源ip
val dstIp = args(1) // 目標ip
val depth = args(2).toInt //搜索深度
val resPath = args(3) //搜索結果的輸出位置
val conf = new SparkConf().setAppName("findIpRel")
val sc = new SparkContext(conf)
/**
* 從數據源中構建原始rdd,每一行的數據形式爲a,b
*/
val ori = sc.textFile("hdfs://master:9000/submitTest/input/ipconn/srcdst.csv")
/**
* 對原始Rdd進行元組形式轉化,現在每一行的數據形式爲(a,b)
* 除此之外還對數據進行了去重處理,並顯示使用hash分區器對RDD中的數據進行分區
* 爲後面的join操作,做一些優化
*/
val base = ori.map(a => {
val tmpArr = a.split(",")
(tmpArr(0), tmpArr(1))
}).distinct().partitionBy(new HashPartitioner(10))
/**
* 這是一個用於保存結果的RDD,其中每一行的形式爲(dstIp,List(ip on path))
* 在查找過程中,發現了搜索結果後,就會將其併入到res中
*/
var res = sc.makeRDD[(String,List[String])](List())
/**
* 這是一個用於迭代的RDD,其初始化的內容是,首先從baseRdd中過濾出元組第一個元素a是參數SrcIp的,
* 然後將其轉化成(b,List(a))的格式,其中b總是代表當前搜索路徑上的尾ip,list中的其他內容代表搜索
* 路徑上其他的ip
*/
var iteration = base.filter(_._1.equals(srcIp)).map(a => (a._2,List(a._1)))
for(i <- 2 to depth){
/**
* 1.首先iteration和base按照key進行join,這個操作的意義就是更深一層的搜索,結果RDD的格式是(b,(List(ip on path),c))
* 2.對數據進行一次過濾,過去掉那些路徑已經形成環的元素,成環的判據就是List(ip on path)中的數據已經包含c了
* 3.進行map操作,b併入到List(ip on path),將c作爲新的key,因此此時更深一層的搜索,導致c成爲了當前搜索路徑中的尾節點,
* 此時RDD中的每一個元素的格式應該是(c,(List(ip on path))
*/
val tmp = iteration.join(base).filter(a => !a._2._1.contains(a._2._2)).map(a => (a._2._2,a._2._1:+a._1))
/**
* 將tmp中已經成功搜索的路徑篩選出來,成功搜索的判據是(c,(List(ip on path)),c與dstIp相等
*/
val success = tmp.filter(a => a._1.equals(dstIp))
/**
* 將成功搜索的數據合併到res中
*/
res = res.union(success)
/**
* 更新iteration
*/
iteration = tmp.subtract(success)
}
/**
* 將成功搜索的路徑併入到res中
*/
res.union(iteration.filter(a => a._1.equals(dstIp)))
/**
* 執行一次轉換操作,將res中的元素從(c,(List(ip on path))格式轉換成List(all ip on path)
*/
val finalResult = res.map(a => a._2 :+ a._1)
finalResult.saveAsTextFile(resPath)
}
}