目录
需求:如何在spark中匹配规则,来删选单词。
方案一:直接在map创建规则实例
缺点:如果有许多数据那么处理一条就需要进入map,new 一个实例,它每个打印出来的toString地址都不一样,它用完这个实例就不在使用了,被垃圾回收了
在map的函数中,创建一个rules实例(太浪费资源)
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => { //函数的执行是在Executor执行的(Task中执行的)
val rules = new Rules //rules的实际是在Executor中使用的
val hostname = InetAddress.getLocalHost.getHostName //当前Task执行的主机名
val threadName = Thread.currentThread().getName //拿到当前线程的名字
(hostname, threadName, Rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
class Rules {
val rulesMap = Map("hadoop" -> 1, "spark" -> 2)
}
方案二:闭包
闭包:变量定义在外面,引用在函数里面
这个程序报错:Task not Serializable,原因是Task引用了一个没法被序列化的实例
object SerTest {
def main(args: Array[String]): Unit = {
val rules = new Rules //在Driver端被实例化,rules的实际是在Executor中使用的
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => { //函数的执行是在Executor执行的(Task中执行的)
val hostname = InetAddress.getLocalHost.getHostName //当前Task执行的主机名
val threadName = Thread.currentThread().getName //拿到当前线程的名字
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
class Rules {
val rulesMap = Map("hadoop" -> 1, "spark" -> 2)
}
从图中可以看出Rules在Driver端生成,并放在每一个RDD里面,传送给Executor,这个传送石Rpc传送,所以需要把Rules给序列化了,
解决方案:
object SerTest {
def main(args: Array[String]): Unit = {
val rules = new Rules //在Driver端被实例化,rules的实际是在Executor中使用的
println("@@@@@@@@@@@@" + rules.toString + "@@@@@@@@@@@@")
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => { //函数的执行是在Executor执行的(Task中执行的)
val hostname = InetAddress.getLocalHost.getHostName //当前Task执行的主机名
val threadName = Thread.currentThread().getName //拿到当前线程的名字
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
class Rules extends Serializable {
val rulesMap = Map("hadoop" -> 1, "spark" -> 2)
val hostname = InetAddress.getLocalHost.getHostName
println(hostname + "@@@@@@@")
}
序列化后的Rules传送到Executor端,在反序列化,两边的Rules地址是不一样的,在map引用rules时,到Executor的task执行时,每个task里面的rules都是不一样的。
方案三:静态对象
目标:使每一个Executor里面都有一份单独的rules,而不是每一个Task里面都不相同
解决方案:广播,静态对象(单例对象,伴生对象)
object SerTest {
def main(args: Array[String]): Unit = {
var rules = Rules //初始化object(在Driver端)
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => { //函数的执行是在Executor执行的(Task中执行的)
val hostname = InetAddress.getLocalHost.getHostName //当前Task执行的主机名
val threadName = Thread.currentThread().getName //拿到当前线程的名字
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
object Rules extends Serializable {
val rulesMap = Map("hadoop" -> 1, "spark" -> 2)
}
效果:
方案四:在Executor中实例化一个object
被使用时才会初始化,并且只会初始化一次,静态方法,类名.方法名
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => { //函数的执行是在Executor执行的(Task中执行的)
val hostname = InetAddress.getLocalHost.getHostName //当前Task执行的主机名
val threadName = Thread.currentThread().getName //拿到当前线程的名字
(hostname, threadName, Rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
object Rules { // 希望Rules在EXecutor中被初始化(不走网络了,就不必实现序列化接口)
val rulesMap = Map("hadoop" -> 2.7, "spark" -> 2.2)
val hostname = InetAddress.getLocalHost.getHostName
println(hostname + "@@@@@@@@@@@@@@@@!!!!")
}
在一个Executor中多个Task会出现多线程问题,如果核数足够多,多个Task同时修改一个文件,就有可能出现线程不安全问题,解决方案是加锁,或者他的共享变量用线程安全的