Spark案例 — 按照ip地址规则与日志数据分析各省份各城市出现次数(使用广播变量)

代码编写使用的是scala

1.首先定义两个方法

  • ip2Long:将ip地址转为十进制的Long
  • binarySearch:二分查找
object Utils {

  /**
   * 将 String 类型的 ip 转为 Long 类型的 十进制ip
   * @param ip
   * @return
   */
  def ip2Long(ip: String): Long = {

    //将数据按照 . 分割开
    //192.168.5.1
    val splited: Array[String] = ip.split("[.]")

    //遍历取出的每一条ip,按照特定算法算出 十进制Long 的值
    var ipNum = 0L
    for (i <- splited.indices) {
      ipNum = splited(i).toLong | ipNum << 8L
    }
    ipNum

  }

  /**
   * 使用二分查找,查询输入的 ip 所对应的地址,并返回该地址对应的Array的index
   * @param ip
   * @return
   */
  def binarySearch(ipArray: Array[(Long,Long,String,String)], ip: Long): Int = {

    //定义一个开始下标
    var startIndex = 0
    //定义一个结束下标
    var endIndex = ipArray.length -1
    //二分查找
    while(startIndex <= endIndex){
      var midIndex = (startIndex + endIndex)/2
      if (ip>=ipArray(midIndex)._1 && ip<=ipArray(midIndex)._2 ){
        return midIndex   //此处一定要写return,这样才会结束该方法,返回midIndex的值
      }else if(ip < ipArray(midIndex)._1){
        endIndex = midIndex - 1
      }else{
        startIndex = midIndex + 1
      }
    }
    -1  //没有找到就返回 -1

  }
}

2.读取数据文件,进行逆地理位置查找

首先要讲解广播变量:

  • 广播变量就是将Driver端的一个数据(这个数据可以从多个Executor拉取来组成,也可以直接从HDFS读取),分片发送给所有的Executor,Driver端有一个 BroadcastManager,由于Worker的反向注册信息,Driver会得到所有的Executor的数据,因此也知道总共有多少个Executor,BroadcastManager根据Executor的个数将数据进行分片,然后将每一片数据分别发送给一个Executor,这样,Driver就只需要给每个Executor发送一次数据,并且Driver端就只发送了一整个文件大小的数据。后续各个Executor之间会进行通信,会将各自拥有的分片数据进行互传,这样所有的Executor就会全部拥有完整的数据内容
  • broadcastManager是一个同步方法,只能等所有数据传输完成后,才会进行Driver下一步的程序操作
  • 广播变量是只读的,且不可变的,广播之后就不可改变了

广播变量的实质:相当于将广播数据提前缓存到了Executor所在的机器中,也就是实现了MapSide Join

为什么要广播数据:为了关联数据,将较小的数据事先进行缓存

注意:当广播变量数据很大时,内存中放不下,此时可将广播数据存放到外部的数据库中,如HBASE、Redis等,来一条数据查询一次外部的数据库

object ipSearchLocation {


  def main(args: Array[String]): Unit = {
    //获取SparkConf,工具类中定义为本地执行
    val conf: SparkConf = Utils.getConf()
    val sc: SparkContext = Utils.getSparkContext(conf)

    //读取ip规则数据,数据路径自行修改
    val ipLines: RDD[String] = sc.textFile("C:\\Users\\logan\\Desktop\\testData\\ip.txt")

    //取出每条数据,解析数据
    val maped: Array[(Long, Long, String, String)] = ipLines.map(line => {
      //按照 | 切分数据
      val splited: Array[String] = line.split("[|]")
      //获取起始ip值与结束ip值
      val start: Long = splited(2).toLong
      val end: Long = splited(3).toLong
      
      //获取省份和城市
      val province: String = splited(6)
      val city: String = splited(7)
    
      //返回tuple4
      (start, end, province, city)

    }).sortBy(_._1)   //按照start进行排序,因为后续要进行二分查找,因此数据必须有序
      .collect()      //使用 collect() 将完整数据收集到Driver端

    //注意:广播 broadcast是 SparkContext的一个方法,调用broadcast方法
    //将上面得到的 maped 数据进行 广播
    val broadcast: Broadcast[Array[(Long, Long, String, String)]] = sc.broadcast(maped)

    //接着读取实际日志数据
    val logLines: RDD[String] = sc.textFile("C:\\Users\\logan\\Desktop\\testData\\ipaccess.log")

    //解析每一条 日志数据
    val result: RDD[((String, String), Int)] = logLines.map(line => {
      //使用 | 切分数据
      val splited: Array[String] = line.split("[|]")
      //获取ip地址
      val ipString: String = splited(1)

      //得到每条日志的 ip地址 ,调用工具类将 String 类型的ip地址 转换为 Long类型的ip地址
      val ipLong: Long = Utils.ip2Long(ipString)

      //查找之前,先要取出广播变量的值。调用其  value 方法
      val ipMatchRule: Array[(Long, Long, String, String)] = broadcast.value

      //得到long 类型的ip地址
      //调用二分查找,开始查找该ip地址的地址下标
      val index: Int = Utils.binarySearch(ipMatchRule, ipLong)
      //index是返回的对应 数据的下标,用于取出某ip地址对应的省份和城市
      //若 index = -1 则代表没有找到对应的值

      //定义province 和 city  的初始值
      var province = "null"
      var city = "null"
      //判断 index 的值
      if (index > -1) {
        //若有值,就将对应Array中的 省份和城市取出来
        province = ipMatchRule(index)._3
        city = ipMatchRule(index)._4
      }

      //保存为k-v形式,方便后续计算各省份各城市的次数
      ((province, city), 1)


    }).reduceByKey(_ + _)  //直接进行聚合计算,局部聚合与全局聚合是相同的逻辑

    //打印结果
    println(result.collect().toBuffer)  

    //关闭SparkContext
    sc.stop()

  }

}

3.结果展示

部分数据如下:

ArrayBuffer(((云南,昆明),126), ((北京,北京),1535), ((河北,石家庄),383), ((null,null),1), ((陕西,西安),1824), ((重庆,重庆),868))

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值