rdd.cache在触发action时会把计算结果算出来,与此同时会把对应的rdd放到内存里面,如果就触发一次action或者这个 rdd没有复用就没意义
cache会怎么用嫩:通常会吧数据先过滤一下保留有用的字段在rdd.cache
rdd.unpersist(true) 用对应的rdd点unpersist(true)就可以把他从内存中释放掉
参数填true是同步的,是阻塞,要这边释放掉drive端代码才可以往下走
默认是false是异步的,是不阻塞
但是用原本的存储格式是java对象的方式存储数据太大可以序列化一下,甚至压缩
sc.getConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") 默认可写可不写
val rdd = sc.textFile("hdfs://linux01:8020/4.txt") 这是读hdfs上数据
import org.apache.spark.storage.StorageLevel 这是导包
rdd.persist(StorageLevel.MEMORY_ONLY_SER) 调用指定内存是否序列化的方式放在内存中
rdd.count
//参数1代表磁盘 参数2代表内存 参数三代表堆外内存 参数4序列化(false序列化)
//啥也不放,不放内存也不放磁盘
val NONE = new StorageLevel(false, false, false, false)
//只放磁盘
*val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
*val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
persist
1. cache、persist的使用场景:一个application多次触发Action,为了复用前面RDD计算好的数据,
避免反复读取HDFS(数据源)中的数据和重复计算
2.缓存,可以将数据缓存到内存或磁盘【executor所在的磁盘】,第一次触发action才放入到内存或磁盘,
以后再出发Action会读取缓存的RDD的数据再进行操作并且复用缓存的数据
3.一个RDD多次触发Action缓存才有意义
4.如果将数据缓存到内存,内存不够,以分区位单位,只缓存部分分区的数据
5.支持多种StorageLevel,可以将数据序列化,默认放入内存使用的是java对象存储,但是占用空间大,优
点速度快,也可以使用其他的序列化方式
6.cache底层调用的是persist方法,可以指定其他存储级别
7.cache和persist方法,严格来说,不是Transformation,应为没有生成新的RDD,只是标记当前rdd
要cache或persist
8. 原始的数据,经过整理过滤后再进行cache或persist效果更佳,
9.在生产环境建议使用Kryo作为默认序列化器,如果cache或persist,建议使用MEMORY_AND_DISK_SER
checkpoint
其实就是把数据进行持久化保存起来,做检查点,保存到靠谱的分布式文件系统(hdfs),理论上不会丢失
val rdd = sc.textFile("hdfs://linux01:8020/4.txt") 创建RDD从hdfs中读
val filtered = rdd.filter(_.contains("laozhao")) 对数据进行过滤
sc.setCheckpointDir("hdfs://linux01/checkpoint1") 指定保存到hdfs中的目录
filtered.checkpoint chechpoint操作计算结果保存到hdfs中
checkpoint就是为了把中间计算好的结果保存到hdfs中以后再一个Application可以反复使用
注意再调用chechpoint之前一定要指定一个存储目录
1.Checkpoint使用场景:适合复杂的计算(机器学习,迭代计算),为了避免丢失数据重复计算,可以将宝贵的中
间结果保存到hdfs中,中间结果安全
2.再调用的rdd的checkpoint方法之前,一定要 指定checkpoint的目录即sc.setCheckPointDir
3.为保证中间结果安全,将数据保存到hdfs中,分布式文件系统,可以保证数据不丢失
4.第一次触发Action,才做checkpoint,会额外触发一个job,这个job的目的就是将结果保存到hdfs中
5.如果rdd做了checkpoint,这个rdd以前的依赖关系就不再使用了
6.触发多次Action,checkpoint才有意义,多用于迭代计算
7.checkpoint严格说,不是Transformatin,只标记当前rdd要做checkpoint
8.如果chevkpoint前,对rdd进行了cache,可以避免数据重复计算,如果有caahe的数据有限使用cache,
如果cache的数据丢失再使用checkpoint
案例 广播关联
1.通常是为了实现mapside join,可以将Driver端的数据广播到属于该application的Executor,然后
通过Driver广播变量返回的引用,获取实现广播到Executor的数据
2.广播变量是通过BT的方式广播的(TorrentBroadcast),多个Executor可以相互传递数据,可以提供效率
3.sc.broadcast这个方法是阻塞的(同步的)
4.广播变量一但广播出去就不能改变,为了以后可以定期的改变要关联的数据,可以定义一个object[单例
对象],在函数内使用,并且加一个定时器,然后定期更新数据
5.广播到Executor的数据,可以在Driver获取到引用,然后这个引用会伴随着每一个Task发送到Executor,
然后通过这个引用,获取到事先广播好的数据
20090121000132095572000|125.213.100.123|show.51.com|/shoplist.php?phpfile=shoplist2.php&style=1&sex=137|Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Mozilla/4.0(Compatible Mozilla/4.0(Compatible-EmbeddedWB 14.59 http://bsalsa.com/ EmbeddedWB- 14.59 from: http://bsalsa.com/ )|http://show.51.com/main.php|
1.0.1.0|1.0.3.255|16777472|16778239|亚洲|中国|福建|福州||电信|350100|China|CN|119.306239|26.075302
上面是运营商的一条信息,ip地址对应下面的范围数据就是后面的对应的地理位置
这是自己封装的启动SparkContext的方法
import org.apache.spark.{SparkConf, SparkContext}
object SparkUtils {
def createContext(isLocal: Boolean =true): SparkContext = {
val conf = new SparkConf().setAppName(this.getClass.getSimpleName)
if (isLocal){
conf.setMaster("local[*]")
}
val sc =new SparkContext(conf)
sc
}
}
这是自己封装的转换十进制和二分查找的方法
import scala.collection.mutable.ArrayBuffer
object IpUtils {
/**
* 将IP地址转成十进制
*
* @param ip
* @return
*/
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
}
/**
* 二分法查找
*
* @param lines
* @param ip
* @return
*/
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 //没有找到
}
}
这是具体广播关联的代码
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
/*
广播变量通常是为了实现mapSideJoin减少网络传输, 可以将小的数据广播到属于该application的Executor中,
要广播的数据不宜太大, 要根据服务器的内存和分配的executor-memory决定
广播变量是readonly, 一旦广播出去就不能改变了
但是在Driver可以调用unpersist将光标变量释放,如果以后在Executor用到该变量,需要重新广播
*/
object IpDemo {
def main(args: Array[String]): Unit = {
val sc = SparkUtils.createContext(args(0).toBoolean)
val ipLines = sc.textFile(args(1))
//使用spark读取ip规则数据
val ipRulesRDD: RDD[(Long, Long, String, String)] = ipLines.map(i => {
//1.0.8.0|1.0.15.255|16779264|16781311|亚洲|中国|广东|广州||电信|440100|China|CN|113.280637|23.125178
val fields = i.split("[|]")
//ip最大
val starNum = fields(2).toLong
//ip最小
val endNum = fields(3).toLong
//省份
val province = fields(6)
//城市
val city = fields(7)
(starNum, endNum, province, city)
})
// 将整理后的ip规则数据整理到drive端
val ipRulesInDrive: Array[(Long, Long, String, String)] = ipRulesRDD.collect()
//将driver的ip规则数据广播出去 ,该方法时一个(同步)阻塞的方法
val broadcastRef: Broadcast[Array[(Long, Long, String, String)]] = sc.broadcast(ipRulesInDrive)
//读取访问日志数据
val accesslog: RDD[String] = sc.textFile(args(2))
//切分访问数据
val provinceAndOne = accesslog.map(e => {
val fields = e.split("[|]")
//得到运营商的真正ip地址
val ip = fields(1)
//通过封装的方法将ip转换成十进制
val ipNum = IpUtils.ip2Long(ip)
//通过driver返回的广播变量引用,获得事先已经广播到当前Excutor中的数据,用广播变量返回点value相当于得到了真正的广播数据
val ipRulesInExcutor: Array[(Long,Long, String, String)] = broadcastRef.value
//进行二分查找.之前写好的方法,参数写的是返回广播的真实数据和真正IP转的十进制
val index = IpUtils.binarySearch(ipRulesInExcutor, ipNum)
var province = "未知"
if (index >= 0) {
province = ipRulesInExcutor(index)._3
}
(province, 1)
})
val reduced = provinceAndOne.reduceByKey(_ + _)
val res = reduced.collect()
println(res.toBuffer)
}
}