spark知识精简版

spark
    UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用的并行计算框架。
    1、Spark Streaming:支持高吞吐量、支持容错的实时流数据处理
    2、Spark SQL, Data frames: 结构化数据查询
    3、MLLib:Spark 生态系统里用来解决大数据机器学习问题的模块
    4、GraphX是构建于Spark上的图计算模型
    
Spark的运行模式
    单机上以本地模式local运行、或以伪分布式模式运行
    分布式的方式运行在Cluster集群中时,底层的资源调度可以使用Mesos 或者Yarn,也可以使用Spark自带的Standalone模式。
    另外一种分法:master有local(调试方便)、yarn-client(普遍)、yarn-cluster、spark、mesos模式
    Spark Standalone:需要部署Spark到相关节点,包括Master和Worker
    Yarn-Cluster:Driver和Executor都运行在Yarn集群中
    Yarn-Client:Driver运行在本地,Executor运行在Yarn集群中

基本概念
(1)    Application:用户编写的Spark应用程序,其中包含Driver功能的代码和分布在集群中多个节点上运行的Executor代码。
(2)    Driver:运行Application的main()函数并且创建SparkContext,创建SparkContext的目的是为了准备Spark应用程序的运行环境。
    SparkContext负责和ClusterManager通信,进行资源的申请、任务的分配和监控等。
    当Executor部分运行完毕后,Driver同时负责将SparkContext关闭。通常用SparkContext代表Driver。
(3)Executor: 某个Application运行在Worker节点上的一个进程,该进程负责运行某些Task,并且负责将数据存在内存或者磁盘上。
    每个Application都有各自独立的一批Executor。
(4)Cluster Manager:指的是在集群上获取资源的外部服务,目前有:
    Standalone:Spark原生的资源管理,由Master负责资源的分配
    Apache Mesos:与Hadoop MapReduce兼容性良好的一种资源调度框架;
    Hadoop Yarn:主要是指的Yarn中的ResourceManager;
(5)Worker:集群中任何可以运行Application代码的节点
    在Standalone模式中指的就是通过slave文件配置的Worker节点,在Spark on Yarn模式中指的就是NodeManager节点。
(6)Job:包含多个Task组成的并行计算,往往由Spark Action触发产生。一个Application中可能会产生多个Job。
(7)Stage:Job被拆分很多组Task,作为一个TaskSet,其名称为Stage。
    Stage的划分和调度由下面的DAGScheduler负责。
    Stage有非最终的Stage即Shuffle Map Stage和最终的Stage即Result Stage两种。
    Stage的边界就是发生Shuffle的地方。
(8)Task:被送到某个Executor上的工作单元,是运行Application的基本单位。
    多个Task组成一个Stage,而Task的调度及管理等由下面的TaskScheduler负责。
    一个Job被拆分成若干个Stage,每个Stage执行一些计算,产生一些中间结果。
    它们的目的是最终生成这个Job的计算结果。
    而每个Stage是一个task set,包含若干个task。Task是Spark中最小的工作单元,在一个executor上完成一个特定的事情。    
(9)RDD:Spark的基本计算单元,可以通过一系列算子进行操作(主要有Transformation 延迟执行 和Action操作)。
    它表示已被分区、被序列化的、不可变的、有容错机制的并且能够被并行操作的数据集合。
    A list of partitions
    A function for computing each split
    A list of dependencies on other RDDs
    其存储级别可以是内存,也可以是磁盘,可通过spark.storage.StorageLevel属性配置。
    1、直接从集合转化 sc.parallelize(List(1,2,3,4,5,6,7,8,9,10))
    2、从各种(分布式)文件系统来 sc.textFile(“README.md”) sc.textFile(“hdfs://xxx”)
    3、从现存的任何Hadoop InputFormat而来 sc.hadoopFile(keyClass, valClass, inputFormat,conf)
(10)共享变量:在Spark Application运行时,可能需要共享一些变量,提供Task或Driver等使用。
    Spark提供了两种共享变量,一种是可以缓存到各个节点的广播变量,一种是只支持加法操作、可以实现求和的累加变量。
(11)宽依赖:ShuffleDependency,依赖需要计算好所有父RDD对应分区的数据,然后在节点之间进行Shuffle。
    指父RDD的每个分区都可能被多个子RDD分区所使用,子RDD分区通常对应所有的父RDD分区(O(n),与数据规模有关)
(12)窄依赖:RDD其分区最多被子RDD中的一个分区依赖。此种情况只有Map任务,是不需要发生Shuffle过程的。窄依赖又分为1:1和N:1两种。
(13)DAGScheduler:根据Job构建基于Stage的DAG,并提交Stage给TaskScheduler。其划分Stage的依据是根据RDD之间的依赖关系。
    1、为每个Job分割stage,同时会决定最佳路径,并且DAG Sheduler会记录哪个RDD或者stage的输出被物化,从而来找到一个最优调度方案。
    2、将TaskSet传给TaskScheduler3、重新提交那些输出lost的stage
(14)TaskScheduler:将Taskset提交给Worker(集群)运行,每个Executor运行什么Task就是在此处分配的。
    1、提交tasks到集群并执行,假如出错就重试。
    2、假如shuffle输出lost就报告fetch failed错误 3、遇到straggle task需要放到别的node上重试

spark的2个优点:
内存计算(Job中间输出和结果可以保存在内存中,从而不再需要读写HDFS)   
DAG

Spark Streaming的核心思想是把流式处理转化为“微批处理”,即以"时间"为单位切分数据流,每个切片内的数据对应一个RDD,
最小抽象单位是Discretized Stream,常用操作foreachRDD
代码例子 object NetworkWordCount {  
    def main(args: Array[String]) {  
        StreamingExamples.setStreamingLogLevels()  
        val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
        val ssc = new StreamingContext(conf, Seconds(1))   
        val lines = ssc.socketTextStream("localhost", 9999)  
        val words = lines.flatMap(_.split(" "))  
        val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)  
        wordCounts.print()  
        ssc.start()  
        ssc.awaitTermination()  

transformation 返回值还是一个RDD
    窄依赖:
    map  List("dog", "salmon", "salmon", "rat", "elephant").map(_.length)
    flatMap List(1, 2, 3), 2).flatMap(x => List(x, x, x))  = Array(1, 1, 1, 2, 2, 2, 3, 3, 3)
    mapPartitions 对rdd中的每个分区的迭代器进行操作 while(x.hasNext){ 操作运算x.next()...
    mapPartitionsWithIndex
    
    filter  (1 to 10).filter(_ % 2 == 0)
    
    union 将两个RDD进行合并,不去重 等价操作符++ (sc.parallelize(datas1) ++ sc.parallelize(datas2)).foreach(println)
    intersection 取交集 sc.parallelize(datas1).intersection(sc.parallelize(datas2)).foreach(println);
    distinct 去重复 datas.distinct().foreach(println)
    
    repartition 就是coalesce函数第二个参数为true的实现
    coalesce 将RDD进行重分区,使用HashPartitioner。且该RDD的分区个数等于numPartitions个数。如果shuffle设置为true,则会进行shuffle。
    
    partitions
    cartesian 笛卡尔积
    
    sample  data.sample(withReplacement = false, 0.5, System.currentTimeMillis)
    groupwith     
    crossProduct
    mapvalues
    sort
    
    宽依赖: 常见于K-V style RDD
    groupByKey  [(3,dog),(4,lion)] [(3,cat)]..... => (4,[lion])(6,[spider])(3,[dog, cat])(5,[tiger, eagle])
    reduceByKey List("dog", "tiger", "lion", "cat", "panther", "eagle").map(x => (x.length, x).educeByKey(_ + _) = Array((4,lion), (3,dogcat), (7,panther), (5,tigereagle))
    cogroup 对多个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并。
    sortByKey List((3, 3), (2, 2), (1, 4), (2, 3))).sortByKey(true)
    aggregateByKey
    sortBy sortBy(_._1) sortBy(_._2)...
    
    其他
    join 可能是宽依赖也可能是窄依赖,其区别是,当要对RDD进行join操作时,如果RDD进行过重分区则为窄依赖,否则为宽依赖。
    (List((1, "苹果"), (2, "梨"), (3, "香蕉"), (4, "石榴"))).join(sc.parallelize(List((1, 7), (2, 3), (3, 8), (4, 3), (5, 9))))
    = (4,(石榴,3)) (1,(苹果,7)) (3,(香蕉,8)) (2,(梨,3))    
    left outer join 左外连接,左边表中的值无论是否在b中存在时,都输出;右边表中的值,只有在左边表中存在时才输出。
    right outer join 和 left outer join 相反。
    举例:rdd={(1,2),(3,4),(3,6)} other={(3,9)}
    join    对两个RDD进行内连接    rdd.join(other)    {(3,(4,9)),(3,(6,9))}
    rightOuterJoin    对两个RDD进行连接操作,右外连接rdd.rightOuterJoin(other)    {(3,(4,9)),(3,(6,9))}
    leftOuterJoin    对两个RDD进行连接操作,左外连接rdd.rightOuterJoin(other)    {(1,(2,None)),(3,(4,9)),(3,(6,9))}

    

action 返回值是一个值,
    reduce  reduce(_ + _) 求和 满足结合律 交换律
    
    count 计数
    countByKey 仅适用于(K, V)类型,对key计数,返回(K, Int)
    
    take 取前若干位
    first 即take(1)
    takeSample
    
    saveasfile
    saveAsTextFile
    saveassequence    
    
    lookup
    
    collect 转数组
    foreach collect是从远程拉取到本地,经过网络传输,foreach是在远程集群上遍历 如果是在本地的话,差别不大
    foreachPartition 这里function的传入参数是一个partition对应数据的iterator
    
    
    
spark工作流程
    Spark的Application在运行的时候,首先在Driver程序中会创建sparkContext作为调度的总入口,
    在其初始化过程中会分别创建DAGScheduler进行Stage调度 TaskScheduler进行Task调度两个模块。
    DAGScheduler模块是基于Stage的调度模块,它为每个Spark Job计算具有依赖关系的多个Stage任务阶段,
    然后将每个Stage划分为具体的一组任务(通常会考虑数据的本地性等)以TaskSet 的形式提交给底层的TaskScheduler模块来具体执行。
    TaskScheduler负责具体启动任务,监控和汇报任务运行情况。而任务运行所需要的资源需要向Cluster Manager申请。
    目前,用spark-submit工具来提交Spark Application,其基本提交格式为:spark-submit [options] <app jar file> [app options] 在其附加参数中可以指定Driver和Executor相关的配置信息。    
    获取rdd的方法    
    1、从共享的文件系统获取,(如:HDFS、HBase) var rdd = sc.textFile("hdfs:///qgzang/1.txt")
    2、通过已存在的RDD转换
    3、将已存在scala集合(只要是Seq对象)并行化 ,通过调用SparkContext的parallelize方法实现    var rdd = sc.parallelize(1 to 10)
    wordcount例子:sc.textFile("README.md").flatMap(_.split(' ')).map((_,1)).reduceByKey(_+_) 其他方法是使用countByValue 和AggregateByKey

spark优化
    1.减少shuffle
    2.内存管理优化
    3.配置项优化
    4.数据序列化,数据结构优化
代码调优
    1、复用相同RDD数据,避免重复创建多份RDD数据。
    2、persist(StorageLevel.MEMORY_AND_DISK_SER RDD持久化操作调用cache()和persist(), cache相当于persist(StorageLevel.MEMORY)
    3、尽量避免使用shuffle类算子(不是不使用) reduceByKey、join、distinct、repartition、groupByKey,cartesian ….
    4、对于非巨型的大变量,使用广播变量Broadcast机制。好处:(1)每个excutor只存放份数据,而不是1个task一份(2)减小task序列化时间
    场景:lookup表, mapside join
    常规做法:
    val rdd_A=…
      val rdd_B=…
    val rdd_C = rdd_A.join(rdd_B)
    优化做法:
    val rdd_A=…
      val rdd_B=…  .collect()
      val rdd_B_Brd = sc.broadcast(rdd_B)
    val rdd_C = rdd_A.map(x=> {
    val key=x._1
             val value1 = x._2
             val value2 = rdd_B_Brd.value.getOrElse  
          (key, (value1, value2) )
    })
    5、使用高性能的RDD算子。
    (1)使用reduceByKey/aggregateByKey替代groupByKey
    (2)使用mapPartitions替代普通map(优化点,但小心OOM)
    (3)使用foreachPartitions替代foreach(尤其是将结果数据输出到数据库时强烈建议使用)
    (4)变化太多可能会产生很多小任务,使用coalesce(TRUE, n)代替repartition
    6、使用Kryo优化序列化性能。spark默认的序列化机制是JAVA的ObjectOutputStream 兼容性好。但又大又慢
    7、数据结构,尽量少使用Java对象。尽量使用字符串代替对象,使用原始类型(Int,long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低GC频率,提升性能。
        只是尽量但也得兼顾代码可读性
    8、计算并发度优化 管理操纵RDD、Partition数控制task计算的并行度 调优shuffle的并发度
    9、其他。(1)无效数据处理能先过滤就先过滤,即filter前置。
        (2)能减小数据处理的数据记录冗余则尽量减小。
        比如: (key1,key2,value1,value2))需要基于key
        ((key1,key2),(key1,key2,value1,value2))
            好的写法:((key1,key2),(value1,value2)
        (3)有用本地运行则不用shuffle
        如:A.map().reduceByKey().map().reduceByKey()
              A.map().reduceByekey()
参数调优
    1.driver-memory (1g-4g)
    --防止spark应用OOM
    2.num-executors
        --控制节点并发
    3.executor-memory (2-6G)
    4.executor-cores (2-4个)
    --决定task执行时长
    5.spark.default.parallelism(默认同RDD分区数同)
    --决定shuffle并发数
    6.spark.yarn.executor.memoryOverhead
    7. spark.kryoserializer.buffer.max (默认64M)

环境调优
    1、优化jar分发性能,防止jar重复上传。 如:
    spark-submit  hdfs://nameservice1/work/wis_2/wis.jar
    spark-submit  wis.jar //运行时会上传jar包到hdfs
    2、数据本地化 HDFS和Mongodb
    3、dir目录配置spark.local.dir(多目录,ssd)


spark生态圈

HDFS
    Hadoop分布式文件系统(Hadoop Distributed File System  HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。
    HDFS是一个高度容错性的系统,适合部署在廉价的机器上。
    三个部分:
    客户端(访问hdfs的入口)
    nameserver(可理解为主控和文件索引类似linux的inode)一般有个secondary备份
    datanode(存放实际数据的存server)
    第一个block副本放在和client所在的node里(如果client不在集群范围内,则这第一个node是随机选取,当然系统会尝试不选择哪些太满或者太忙的node);
    第二个副本放置在与第一个节点不同的机架中的node中(随机选择);
    第三个副本和第二个在同一个机架,随机放在不同的node中。
    优点:    1)处理超大文件     2)流式的访问数据
    缺点:    1)不适合低延迟数据访问     2)无法高效存储大量小文件       3)不支持多用户写入及任意修改文件  
    
    Sc.textFile(path: String, minPartitions: Int = defaultMinPartitions)
    Sc.objectFile[T](path: String, minPartitions: Int = defaultMinPartitions)
    Rdd.saveAsTextFile(path: String): Unit
    Rdd. saveAsObjectFile(path: String): Unit
    
    RDD save到Hdfs后,RDD的每个分区都会对应于一个HDFS文件。如果一个RDD数据小,但分区多,则最好对RDD调整分区后,再保存。
    RDD. Coalesce(N)
    Coalesce用法注意:
    1、如果将1000个分区转换成100个分区,这个过程不会发生shuffle,相反如果10个分区转换成100个分区将会发生shuffle。
    2、也不能将N值设置太小,例如合并成一个分区,在数据量较大时会OOM

Kafka
    Kafka是一个消息系统,原本开发自LinkedIn,用作LinkedIn的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。
    Broker:Kafka集群包含一个或多个服务器,这种服务器被称为broker
    Topic:每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic,被分成多个partition,每个partition是个fifo队列
    Partition:物理上的概念,每个Topic包含一个或多个Partition.
    Producer:负责发布消息到broker,负责确定topic,负责确定partition,异步发送
        producer的ack的3种模式:
        0:这意味着生产者producer不等待来自broker同步完成的确认就继续发送下一条(批)消息。此选项提供最低的延迟但最弱的耐久性保证,因为其没有任何确认机制。
        1:这意味着producer在leader已成功收到数据并得到确认后,再发送下一条消息。此模式leader的确认后就返回,而不管partion的follower是否已经完成。
        -1:这意味着producer在follower副本确认接收到数据后才算一次发送完成。 此选项提供最好的耐久性,保证至少一个同步副本保持存活。
    Consumer:消息消费者,向Kafka broker读取消息的客户端。
    Consumer Group:每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。
    
    spark stream读取kafka数据,2个方式(1)利用接收器(receiver)和kafaka的高层API实现。(2)直接用kafka底层的API来实现。(生产上常用)
    
    

猜你喜欢

转载自blog.csdn.net/b0207191/article/details/88535523