Spark的广播变量及其案列

广播变量:通俗的讲(本人自己的理解,如有错误,请多指教)就是一旦处理数据量大,生成大量的Task.Driver需要将变量传递给每一个Task,这样就显得笨拙,低效率.而广播变量------->就是将Driver端的变量分发给executor一份,广播变量,是对Driver端变量的一个拷贝,这个拷贝是只读的,不能修改。这样executor启动的Task就会共享这个变量.节省了通信的成本和服务器的资源.
如何定义一个广播变量?代码如下:

 sc.broadcast(变量)

注意:变量一旦定义为广播变量,那么这个变量就只能读,不能修改.
如何还原一个广播变量?代码如下:

broadcast.value

注意事项:
1、能不能将一个RDD使用广播变量广播出去?

   不能,因为RDD是不存储数据的。可以将RDD的结果广播出去。

2、 广播变量只能在Driver端定义,不能在Executor端定义。

3、 在Driver端可以修改广播变量的值,在Executor端无法修改广播变量的值。

4、如果executor端用到了Driver的变量,如果不使用广播变量在Executor有多少task就有多少Driver端的变量副本。

5、如果Executor端用到了Driver的变量,如果使用广播变量在每个Executor中只有一份Driver端的变量副本。

案列:
需求:根据访问日志中的Ip地址,访问访客的省份信息.根据Ip


方案一(思路):


①:定义单列对象在静态代码块中加载规则,规则只会加载一次.
②:规则在一个Excutor中的内存中保存一份
③:使用二分查找,提高查找效率
缺点是:每一个Task都需要加载全部的Ip规则,如果Ip规则数据很大,就会很慢.
代码如下:

package day04

import Util.IpUtils
import cn._51doit.spark.day03.IpRulesLoader
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ArrayBuffer

/**
 * 根据日志中的Ip查询位置信息
 */
object IpLocation {
  def main(args: Array[String]): Unit = {
    val isLocal = args(0).toBoolean
    //创建SparkConf 然后创建SparkContext
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName)
    if (isLocal) {
      conf.setMaster("local[*]")
    }
    val sc: SparkContext = new SparkContext(conf)
    //创建RDD  先读取日志数据
    val lines: RDD[String] = sc.textFile(args(1))
    val provinceAndOne: RDD[(String, Int)] = lines.map(line => {
      //IpRulesLoader是在Excutor端被真正调用的
      val allRules: ArrayBuffer[(Long, Long, String, String)] = IpRulesLoader.getAllRules()
      val fields: Array[String] = line.split("[|]")
      //获取IP地址
      val ip: String = fields(1)
      val ipNum: Long = IpUtils.ip2Long(ip)
      //查找全部的Ip规则-->注意 应该在这之前就应该把Ip规则加载到exccutor中
      val Index: Int = IpUtils.binarySearch(allRules, ipNum)
      var province: String = "未知"
      if (Index != -1) {
        province = allRules(Index)._3
      }
      (province, 1)
    })
    //按照省份进行聚合
    val result: RDD[(String, Int)] = provinceAndOne.reduceByKey(_ + _)
    //将计算好的数据保存到MySQL
    println(result.collect().toBuffer)

    sc.stop()


  }
}

加载Ip规则数据代码如下:

package cn._51doit.spark.day03

import java.io.{BufferedReader, InputStreamReader}
import java.net.URI

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FSDataInputStream, FileSystem, Path}

import scala.collection.mutable.ArrayBuffer

object IpRulesLoader {

  //在object中定义的定义的数据是静态的,在一个JVM进程中,只有一份
  var IpRules = new ArrayBuffer[(Long, Long, String, String)]()

  //加载IP规则数据,在Executor的类加载是执行一次
  //静态代码块
  {
    //读取HDFS中的数据
    val fileSystem = FileSystem.get(URI.create("hdfs://qiu01:9000"), new Configuration())
    val inputStream = fileSystem.open(new Path("/ip/ip.txt"))
    val bufferedReader = new BufferedReader(new InputStreamReader(inputStream))

    var line: String = null
    do {
      line = bufferedReader.readLine()
      if (line != null) {
        //处理IP规则数据
        val fields = line.split("[|]")
        val startNum = fields(2).toLong
        val endNum = fields(3).toLong
        val province = fields(6)
        val city = fields(7)
        val t = (startNum, endNum, province, city)
        IpRules += t
      }
    } while (line != null)

  }


  def getAllRules(): ArrayBuffer[(Long, Long, String, String)] = {
    IpRules
  }
}

二分查找,代码如下:

package Util

import scala.collection.mutable.ArrayBuffer

object IpUtils {
  /**
   * 将Ip地址转成十进制
   */
  def ip2Long(ip: String): Long = {
    val fragments = ip.split("[.]")
    var ipNum = 0L
    for (i <- 0 until fragments.length) {
      ipNum = fragments(i).toLong | ipNum << 8L
    }
    ipNum
  }

  /**
   * 二分查找
   */
  def binarySearch(lines: ArrayBuffer[(Long, Long, String, String)], ip: Long): Int = {
    var low = 0 //起始
    var high = lines.length - 1 //结束
    while (low <= high) {
      val middle = (low + high) / 2
      if ((ip >= lines(middle)._1) && (ip <= lines(middle)._2))
        return middle
      if (ip < lines(middle)._1)
        high = middle - 1
      else {
        low = middle + 1
      }
    }
    -1 //没有找到
  }
  def binarySearch(lines: Array[(Long, Long, String, String)], ip: Long): Int = {
    var low = 0 //起始
    var high = lines.length - 1 //结束
    while (low <= high) {
      val middle = (low + high) / 2
      if ((ip >= lines(middle)._1) && (ip <= lines(middle)._2))
        return middle
      if (ip < lines(middle)._1)
        high = middle - 1
      else {
        low = middle + 1
      }
    }
    -1 //没有找到
  }
}


方案二

***:我们可以使用广播变量的方法,把所有的Ip规则加载到Driver端,在切分成N(Excutor的个数,)个,分给每个Excutor,它们之间通过BT(种子)的方式相互传送数据.Excutor越多,传送的越快,提高效率.代码如下:

package day04

import Util.IpUtils
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object IpLocationAdv {
  def main(args: Array[String]): Unit = {
    val isLocal: Boolean = args(0).toBoolean
    //创建SparkConf.然后创建SparkContext
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName)
    if (isLocal) {
      conf.setMaster("local[*]")
    }
    val sc: SparkContext = new SparkContext(conf)
    //读取IP规则数据进行整理
    val ipLines: RDD[String] = sc.textFile(args(1))
    val ipRulesRDD: RDD[(Long, Long, String, String)] = ipLines.map(line => {
      val fields: Array[String] = line.split("[|]")
      val startNum: Long = fields(2).toLong
      val endNum: Long = fields(3).toLong
      val province: String = fields(6)
      val city: String = fields(7)
      (startNum, endNum, province, city)
    }
    )
    //将全部的Ip规则收集到Driver端
    val ipRulesInExecutor: Array[(Long, Long, String, String)] = ipRulesRDD.collect()
    //将Driver端全部的ip规则在广播到Executor
    //广播完成后,将Executor端的广播的数据的引用返回,以后可以通过这个引用获取到事先广播好的数据
    //sc.broadcast是一个阻塞的方法,广播变量没有广播完,不会执行下面的逻辑
    //定义一个广播变量  sc.broadcase(变量)
    val ipRulesRef: Broadcast[Array[(Long, Long, String, String)]] = sc.broadcast(ipRulesInExecutor)
    //处理IP日志数据
    val logDD: RDD[String] = sc.textFile(args(2))

    //处理要计算的数据并关联事先广播到Executor中的规则
    val provinceAndOne: RDD[(String, Int)] = logDD.map(line => {
      val fields: Array[String] = line.split("[|]")
      val ip: String = fields(1)
      val ipNum: Long = IpUtils.ip2Long(ip)
      //获取实现广播到Executor的数据呢?  通过Driver的引用获取Executor广播好的数据
      //调用二分查找
      val index = IpUtils.binarySearch(ipRulesInExecutor, ipNum)
      var province = "未知"
      if (index >= 0) {
        province = ipRulesInExecutor(index)._3
      }
      (province, 1)
    })

    //按照省份进行聚合
    val result: RDD[(String, Int)] = provinceAndOne.reduceByKey(_ + _)
    //建计算结果保存到HDFS中
    // result.saveAsObjectFile(args(3))
    val r: Array[(String, Int)] = result.collect()
    println(r.toBuffer)
    //释放资源
    sc.stop()

    /**
     * 结果为:ArrayBuffer((陕西,1824), (河北,383), (云南,126), (未知,1), (重庆,868), (北京,1535))
     */
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值