package cn._51doit.day06
import org.apache.spark.rdd.RDD
import org.json4s.scalap.scalasig.ScalaSigEntryParsers.index
import utils.{IpUtils, SparkUtils}
object IpLocationCount {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(args(0).toBoolean)
// 使用 spark 读取 IP 规则数据
val ipLines: RDD[String] = sc.textFile(args(1))
val ipRulesRDD: RDD[(Long, Long, String, String)] = ipLines.map(e => {
val fields = e.split("[|]") // 以 | 切割 , 在正则表达式中是特殊符号,所以要拿 []
val startNum = fields(2).toLong // 16777472 切割出 第二个来
val endNum = fields(3).toLong // 16778239 切割出 第三个来
val province = fields(6) // 福建 省份
val city = fields(7) // 福州 城市
(startNum, endNum, province, city)
})
// 将整理后的ip 规则收集到 Drive 端 前提是内存够用,否则它只会传部分数据
val ipRulesInDrive: Array[(Long, Long, String, String)] = ipRulesRDD.collect()
// 将Driver 的 IP 规则数据广播到 Executor 中,该方法是一个阻塞的方法
// 意味着没广播完 Driver 端不会往下走
// broadcastRef 这个引用 可以知道它把数据广播到哪一个节点上了,然后再每一个数据上它的内存地址
val broadcastRef = sc.broadcast(ipRulesInDrive)
// 读取访问日志的数据
val accessLog: RDD[String] = sc.textFile(args(2))
val provinceAndOne = accessLog.map(e => {
val fields = e.split("[|]")
val ip = fields(1)
// 将IP地址转成十进制
val ipNum = IpUtils.ip2Long(ip)
// 通过Driver 返回的广播变量的引用,获取事先已经广播到当前 Executor 中的数据
val ipRulesInExecutor: Array[(Long, Long, String, String)] = broadcastRef.value
// 进行二分查找
val index = IpUtils.binarySearch(ipRulesInExecutor, ipNum)
var province = " 未知"
if (index >= 0) {
province = ipRulesInExecutor(index)._3
}
(province, 1)
})
val reduced = provinceAndOne.reduceByKey(_ + _)
val res = reduced.collect()
println(res.toBuffer)
sc.stop()
}
}
广播变量通常是为了实现 mapSideJoin 减少网络传输 ,可以将小的数据广播到属于 该 application 的 Executor 中 ,要广播的数据不宜太大,要根据 服务器的内存和分配的 executor-memory 决定
广播变量是 readonly,一但广播出去就不能改变了
但是在Driver 可以调用 unpersist 将广播变量释放,如果以后在 Executor 用到该变量,需要重新广播