1.数据来源于搜狗实验室
数据为2006年8月份的日志数据
2.大致查看下包内文件 tar -ztf SogouQ.tar.gz
root@host1:/usr/wh# tar -ztf SogouQ.tar.gz
SogouQ/
SogouQ/access_log.20060801.decode.filter
SogouQ/access_log.20060802.decode.filter
SogouQ/access_log.20060803.decode.filter
SogouQ/access_log.20060804.decode.filter
SogouQ/access_log.20060805.decode.filter
SogouQ/access_log.20060806.decode.filter
SogouQ/access_log.20060807.decode.filter
SogouQ/access_log.20060808.decode.filter
SogouQ/access_log.20060809.decode.filter
SogouQ/access_log.20060810.decode.filter
3.解压上传至hdfs
- tar -zxf SogouQ.tar.gz
- sudo -uhdfs hdfs dfs -mkdir -p /user/test/test_data/sougou
- 上传数据
sudo -uhdfs hdfs dfs -put /tmp/tmp2018511/access_log.20060801.decode.filter /user/test/test_data/sougou
报错:有一台datanode的50010拒绝访问【0010是datanode用于数据交换的服务端口】
java.io.IOException: Got error, status message , ack with firstBadLink as 172.**.***.**:50010
注意,这里仅一台机器报错,不代表文件上传失败,只能说拒绝连接的那台datanode上没有副本- 查看了已添加的Iptables规则iptables -L -n -v,50010并未开放
- 添加规则,并指定可以链接的IP地址:
iptables -A INPUT -p tcp --dport 50010 -s 172.**.***.** -j ACCEPT
/etc/rc.d/init.d/iptables save
service iptables restart
尝试只开放INPUT,失败,加上OUTPUT,仍然失败 - 删除刚才添加的规则:
iptables -L -n --line-numbers
【将所有iptables以序号标记显示】
iptables -D INPUT 7【删除INPUT中的7】
- 尝试使用-p all
iptables -A INPUT -p all -s 172.**.***.** -j ACCEPT
- 再次上传仍然失败,TODO【暂时关闭防火墙,上传完毕再打开】
-A | -I | -p | –dport | -s | -j |
---|---|---|---|---|---|
于规则链末尾追加规则 | 指定序号添加规则,默认为1,也就是规则链的最前面 | 代表协议类型,比如tcp,udp,icmp等等,忽略的话,则允许所有协议。也可以使用-p all | 端口号 | 指定source address | 表示匹配时如何做,是DROP则拒绝访问,ACCEPT允许链接 |
INPUT 表示”别人”对“我”的访问,OUTPUT表示“我”的输出
可惜,数据和官方说明的格式对不上,但是搜狗新闻数据又是一堆小文件。先不管了,先用着
# 这是日志数据
6383203565086312 [bt????] 8 1 www.lovetu.com/
07822362349231865 [??????] 3 1 ldjiamu.blog.sohu.com/10491955.html
23528656921072266 [http://onlyasianmovies.net] 1 1 onlyasianmovies.net/
4.SparkSession拿到sparkContext对象,读取hdfs文件,显示指定创建5个partitions
val sparkSession:SparkSession = SparkSession.builder.appName("SougouQ").master("spark://host1.com:7077").getOrCreate
val sc = sparkSession.sparkContext
val sougouSource:RDD[String] = sc.textFile("hdfs:/user/test/test_data/sougou/access_log.20060801.decode.filter",5)
还可以这样同时读取多个文件:
val rdd = sparkContext.wholeTextFile(“hdfs://a-hdfs-path”)
more:sequenceFile、newApiHadoopFile等等
详见SparkContext源码
5.简单看下SparkSession的源码实现
- 最外层SparkSession类的构造器
@InterfaceStability.Stable
class SparkSession private(
@transient val sparkContext: SparkContext,
@transient private val existingSharedState: Option[SharedState],
@transient private val parentSessionState: Option[SessionState],
@transient private[sql] val extensions: SparkSessionExtensions)
extends Serializable with Closeable with Logging {
// Closeable是Java接口;Serializable/Logging是特质(trait)接口,Serializable底层就是Serializable的Java接口
// Option[SharedState]和Option[SessionState]代表这两个参数是可选的,有可能为空
// Option有Some,None两个子类;
// Option[T] 是一个类型为 T 的可选值的容器: 如果值存在, Option[T] 就是一个 Some[T] ,如果不存在, Option[T] 就是对象 None
// @InterfaceStability.Unstable是说,接口不稳定
builder()函数的实现
只有一行
def builder(): Builder = new Builder
Builder是SparkSession的伴生对象object SparkSession的一个内部类class Builder extends Logging
【该内部类负责:参数设置,启用Hive支持,创建SparkSession】
【伴生对象是在第一次访问的时候初始化,不可以new,不能带参数】SparkSession中参数的设置
private[this] val options = new scala.collection.mutable.HashMap[String, String]
所有的参数,都以key-value的方式存储在HashMap中,比如options += key -> value.toString
也可以直接传递一个SparkConfigdef config(conf: SparkConf): Builder
【使用此方法设置的参数会自动传播到SparkConf和SparkSession自己的配置中。】- 所以可以使用builder.config(“key”, value)配置参数
- 也可以创建SparkSession之后可以设置运行参数,代码如下:
// 设置一些运行时参数
spark.conf.set(“spark.sql.shuffle.partitions”, 6)
getOrCreate方法【SparkContext也有一个getOrCreate方法】
/**
* Gets an existing [[SparkSession]] or, if there is no existing one, creates a new
* one based on the options set in this builder.
*
* 进行两次检测,
* 首先检查是否有一个有效的线程本地SparkSession,如果是,返回这一个.
* 然后它检查是否有一个有效的全局默认SparkSession,如果是,返回这一个.
* 如果没有有效的全局缺省SparkSession,该方法将创建一个新的SparkSession,并将新创建的SparkSession分配为全局默认值.
*
* 如果返回现有的SparkSession,则该构建器中指定的配置选项将应用于当下SparkSession。
* This method first checks whether there is a valid thread-local SparkSession,
* and if yes, return that one. It then checks whether there is a valid global
* default SparkSession, and if yes, return that one. If no valid global default
* SparkSession exists, the method creates a new SparkSession and assigns the
* newly created SparkSession as the global default.
*
* In case an existing SparkSession is returned, the config options specified in
* this builder will be applied to the existing SparkSession.
*
* @since 2.0.0
*/
def getOrCreate(): SparkSession = synchronized {
// 如果有,从当前线程活跃的 session 中获取SparkSession
// 使用Java中的线程实现,get()内部使用Entry实现
var session = activeThreadSession.get()
if ((session ne null) && !session.sparkContext.isStopped) {
// 为session设置参数
options.foreach { case (k, v) => session.sessionState.conf.setConfString(k, v) }
if (options.nonEmpty) {
// 如果使用已经存在的SparkSession,则我们自己builder时传递的参数可能不会起作用
logWarning("Using an existing SparkSession; some configuration may not take effect.")
}
return session
}
// Global synchronization so we will only set the default session once.
SparkSession.synchronized {
// If the current thread does not have an active session, get it from the global session.
// 如果当前线程不存在一个活动的SparkSession,那么就尝试从全局获取,为第二次检测
session = defaultSession.get()
if ((session ne null) && !session.sparkContext.isStopped) {
options.foreach { case (k, v) => session.sessionState.conf.setConfString(k, v) }
if (options.nonEmpty) {
logWarning("Using an existing SparkSession; some configuration may not take effect.")
}
return session
}
// No active nor global default session. Create a new one
// 没有活动的或者全局默认的SparkSession,就新建一个
val sparkContext = userSuppliedContext.getOrElse {
// set app name if not given,没设置appName,则使用UUID生成唯一标志变量(时间+机器识别号等)自动配置
val randomAppName = java.util.UUID.randomUUID().toString
val sparkConf = new SparkConf()
options.foreach { case (k, v) => sparkConf.set(k, v) }
if (!sparkConf.contains("spark.app.name")) {
sparkConf.setAppName(randomAppName)
}
// SparkContext也有一个getOrCreate方法.
/** 直接调用 SparkContext的伴生类创建sc */
val sc = SparkContext.getOrCreate(sparkConf)
// maybe this is an existing SparkContext, update its SparkConf which maybe used
// by SparkSession
options.foreach { case (k, v) => sc.conf.set(k, v) }
if (!sc.conf.contains("spark.app.name")) {
sc.conf.setAppName(randomAppName)
}
sc
}
// Initialize extensions if the user has defined a configurator class.
val extensionConfOption = sparkContext.conf.get(StaticSQLConf.SPARK_SESSION_EXTENSIONS)
if (extensionConfOption.isDefined) {
object SparkSession中还有很多管理session的方法。
- 启用Hive支持
/**
* Enables Hive support, including connectivity to a persistent Hive metastore, support for
* Hive serdes, and Hive user-defined functions.
*
* 启用hive支持,包括连接到一个持久的hive元数据,支持hive serdes,以及hive的定义函数
* @since 2.0.0
*/
def enableHiveSupport(): Builder = synchronized {
// 检查是否有Hive相关类以及相关配置类.
if (hiveClassesArePresent) {
config(CATALOG_IMPLEMENTATION.key, "hive")
} else {
throw new IllegalArgumentException(
"Unable to instantiate SparkSession with Hive support because " +
"Hive classes are not found.")
}
}
6.查看分区数sougouSource.partitions.size
关于分区的原则和理由见这篇文章
还可以将数据重新分区。通过对RDD调用.repartition(numPartitions)
7.查看宽窄依赖
- MapPartitionsRDD
var fileds = sougouSource.flatMap(_.split("\t")).map((_,1))
flatMap和map主要在数组,集合这些数据结构中体现区别,前者 fileds.dependencies.foreach{ dep => println($"这个是窄依赖=====${dep.getClass}")}
这个是窄依赖=====class org.apache.spark.OneToOneDependency
ShuflleRDD
val reduce = fileds.reduceByKey(_+_)
这个是宽依赖=====class org.apache.spark.ShuffleDependency
8.RDD分区函数
分区划分对Shuffle极其重要,Spark Partitioner中有哈希分区器HashPartitioner和范围分区器RangePartitioner
reduce.partitioner
Partitioner只存在于(K,V)RDD 中,非(K,V)RDD为None【option类型】
Option[org.apache.spark.Partitioner] = Some(org.apache.spark.HashPartitioner@5)
9.打印RDD元素:
reduce.collect
程序运行出问题了:
18088端口,Spark的history UI
- yarn8088端口显示任务Finished,但是spark history server18088端口显示任务还有一stage RUNNING
- 无论kill spark相关kid,还是 yarn application -kill application_1525953647678_0008结果不变,但是yarn显示Application application_1525953647678_0008 has already finished
- 甚至重启Spark服务依旧RUNNING状态。。。
常见的习惯用法是尝试使用rdd.foreach(println)或rdd.map(println)打印RDD的元素。在单台机器上,这将产生预期的输出并打印所有RDD的元素。但是,在集群模式下,由执行程序的标准输出输出现在写入执行程序的stdout,而不是驱动程序的标准输出,因此驱动程序的stdout不会显示这些!要打印驱动程序中的所有元素,可以使用collect()方法首先将RDD带到驱动程序节点:rdd.collect().foreach(println)。但是,这可能会导致驱动程序内存不足,因为collect()会将整个RDD提取到单台计算机;如果您只需要打印RDD的几个元素,则更安全的方法是使用take():rdd.take(100).foreach(println)。
- 数据仅仅66MB,机器内存48G,standalong模式,感觉不太可能是内存不足的原因
18088中查看executors,显示还有活跃的Task,查看日志,有心跳Warning
WARN netty.NettyRpcEndpointRef: Error sending message [message = Heartbeat(5,[Lscala.Tuple2;@9fdf1da,BlockManagerId(5, ip13, 36338))] in 2 attempts
但是yarn中的history job很完整,spark history server中仅仅部分job,spark history server不太完善?
- 整个集群重启,仍然RUNNING状态,我感觉history server在骗我。。。
- TODO
10.验证cache缓存
flatmap.cache()
// 第一次是从HDFS读取数据
flatmap.count()
flatmap.count()
// 从缓存中读取数据
可以看到,第一次计算耗时10秒,第二次仅耗时0.1秒
cache()相当于StorageLevel为MEMORY_ONLY时的persist()
StorageLevel详见StorageLevel的单例伴生对象StorageLevel
11.mapValues【针对value进行map】
val map_values = fileds.mapValues(_ + "@")
在每个value后加上字符@
12.设置checkPoint
sc.setCheckpointDir("hdfs:/user/test/test_data/checkpoint")
map_values.checkpoint()
行动操作
集合标量行动操作
first()、take(num:Int)、count()、collect()、reduce()、top(num:Int)、takeOrdered(num:Int)、aggreate()、fold()、lookup()、countByKey()、foreach() 、foreachPartition()、sortBy()
1.top、takeOrdered
var rdd = sc.makeRDD(Seq(1 to 100))
top降序,takeOrdered相反
var rdd = sc.makeRDD(Seq(3,5,8.8,2,1,7))
rdd.top()
2.lookup
var reverse = flatmap.map(x => (x._2,x._1))
reverse.lookup(1)
TODO
1.——1.3中的防火墙问题
2.——8中分区器的具体学习
3.——9中collet引发的RUNNING不结束,kill不了的问题