• 大数据-spark


    Spark是用于大规模数据处理的快速通用的计算引擎。

    相较MR快的原因:其任务中间结果存在内存中,在迭代运算中尤为明显,DAG的设置。

    架构说明:

    • Dirver:负责节点通讯,task分发,结果回收

    • Worker:资源管理的从节点

    • Master:资源调度的主节点

    RDD 弹性分布式数据集

    五大特性

    • RDD由一系列partition组成

    • 函数作用在partition上

    • RDD之间存在相互依赖

    • 分区器作用在KV格式的RDD上

    • RDD提供了一系列最佳的计算位置,数据本地化,计算向数据移动

    RDD本身实际不存储数据,为了便于理解暂时认为是存数据的

    属性说明

    • RDD的弹性来源于:partition的大小和数量可变

    • RDD的容错来源于:RDD之间的依赖关系

    • RDD的分布式来源于:partation分布在不同的节点

    • KV格式的数据,RDD中的数据以二元组对象的形式存储

    RDD创建

    scala:通过SparkConf对象设置参数,SparkContext接收SparkConf对象,生成上下文context,context的textFile方法载入数据源,返回第一个RDD,基于算子对RDD进行处理

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("wc")
    val sc = new SparkContext(conf)
    //sc能够设置checkpiont目录,日志打印级别等
    sc.setlogLevel("WARN");
    sc.checkpoint("hdfs://node1:9000/spark/checkpoint");
    val RDD: RDD[String] = sc.textFile("data")

    java:SparkConf类设置参数,JavaSparkContext接收SparkConf对象,生成上下文context,context的textFile方法载入数据源,返回第一个RDD,基于算子对RDD进行处理

    SparkConf sparkConf = new SparkConf();
    sparkConf.setMaster("location").setAppName("wc");
    JavaSparkContext jsc = new JavaSparkContext(sparkConf);
    //能够将jsc对象转为sc对象,执行sc的方法
    SparkContext sc = jsc.sc();
    JavaRDD<String> data = jsc.textFile("data");

    创建RDD的主要方法

    • textFile

      JavaRDD<String> data = jsc.textFile("data");
    • parallelize 将容器转为RDD,能够执行分区数

      JavaRDD<Integer> rdd1 = jsc.parallelize(Arrays.asList(1,2,3),3);
    • parallelizePair

      JavaPairRDD<String, Integer> rdd1 = 
         jsc.parallelizePairs(Arrays.asList(
                 new Tuple2<>("a", 1),
                 new Tuple2<>("a", 2)
        ));

    部署

    Standalone模式

    • 安装

      • tar -zxvf spark-2.3.1-bin-hadoop2.6.tgz

      • mv spark-2.3.1-bin-hadoop2.6.tgz spark-2.3.1 改名

    • 配置

      • cd /opt/sxt/spark-2.3.1/conf 进入目录

      • mv slaves.template slaves 修改slaves文件

        • vim slaves

          • node1 node2 node3

      • mv spark-env.sh.template spark-env.sh

        • vim spark-env.sh

          • JAVA_HOME=/usr/java/jdk1.8.0_11 配置java_home路径

          • SPARK_MASTER_HOST=node1 master的ip

          • SPARK_MASTER_PORT=7077 提交任务的端口,默认是7077

          • SPARK_WORKER_CORES=1 每个worker从节点能够支配的核数

          • SPARK_WORKER_MEMORY=2g 每个worker从节点能够支配的内存

    • 拷贝到其他节点

      cd /opt/sxt
      scp -r spark-2.3.1 node2:`pwd`
      scp -r spark-2.3.1 node3:`pwd`
    • 启动(在主节点上)

      cd /opt/sxt/spark-2.3.1/sbin

      ./start-all.sh (关闭: ./stop-all.sh)

      UI连接:node1:8080 (修改端口:start-master.sh)

    yarn模式

    • 修改spark配置文件spark-env.sh

      添加hadoop配置路径 HADOOP_CONF_DIR=/opt/sxt/hadoop-2.6.5/etc/hadoop

    • 修改hadoop的配置,处理兼容

      vim /opt/sxt/hadoop-2.6.5/etc/hadoop/yarn-site.xml

      <property>
      <name>yarn.nodemanager.vmem-check-enabled</name>
      <value>false</value>
      </property>
    • 启动

      yarn:start-all.sh

      spark:cd /opt/sxt/spark/sbin ./start-all.sh

    Master-HA配置

    • 修改spark-env.sh配置文件

      export SPARK_DAEMON_JAVA_OPTS="
      -Dspark.deploy.recoveryMode=ZOOKEEPER
      -Dspark.deploy.zookeeper.url=node1:2181,node2:2181,node3:2181
      -Dspark.deploy.zookeeper.dir=/sparkmaster0821"
    • 配置到各节点 scp spark-env.sh node2://opt/sxt/spark/conf

    • 配置备用master节点,修改该节点的 spark-env.sh配置中的SPARK_MASTER_HOST

      SPARK_MASTER_HOST=node2
    • HA启动

      • 主节点启动进程:./start-all.sh

      • 备用节点启动master进程:./start-master.sh

    任务执行流程

    Standalone-client模式

    cd /opt/sxt/spark-2.3.1/bin
    #指定任务提交地址,指定提交方式,指定主类,指定jar包位置 ,其他参数
    ./spark-submit
    --master spark://node1:7077
    --deploy-mode client
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.3.1.jar
    100
    • 执行流程

      • 集群启动时,worker向master汇报资源

      • client提交任务,客户端中启动Driver进程

      • Driver向Master申请启动Application的资源

      • Master启动worker节点的excutor进程,excutor进程反向注册到Driver上

      • Driver将task发送到worker的excutor进程中执行

      • Driver监控task,接收worker的执行结果

    • 适用测试环境,多任务执行时Driver网卡压力过大

    Standalone-cluster模式

    cd /opt/sxt/spark-2.3.1/bin
    #指定任务提交地址,指定提交方式,指定主类,指定jar包位置 ,其他参数
    ./spark-submit
    --master spark://node1:7077
    --deploy-mode cluster
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.3.1.jar  
    100
    #需要jar包存放值hdfs中,或者每台节点上
    • 执行过程

      1. 集群启动时,worker向master汇报资源

      2. 客户端将任务提交到集群,向Master请求启动Driver

      3. Master选择随机节点创建Driver

      4. Driver启动后向Master请求application的资源

      5. Master启动worker节点的excutor进程,excutor进程反向注册到Driver上

      6. Driver将task发送到worker的excutor进程中执行

      7. Driver监控task,接收worker的执行结果

    • 适用生产环境,客户端只负责提交任务,Driver均衡分布在集群中降低单节点网卡压力

    yarn-clinet模式

    cd /opt/sxt/spark-2.3.1/bin
    #指定任务提交地址,指定提交方式,指定主类,指定jar包位置 ,其他参数
    ./spark-submit
    --master yarn–client
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.3.1.jar  
    100
    • 执行流程

      • client将任务提交到RM(ResourceManager)中,在客户端内创建Driver进程

      • RM随机选取NM节点(NodeManager),创建AM进程(applicationMaster)

      • AM进程向RM请求applictaion的资源,RM响应一批container资源

      • AM根据资源启动NM节点中的excutor进程

      • excutor反向注册到Driver进程上,接收Driver发出的task并执行

      • Driver监控task,接收worker的执行结果

    • 使用测试环境,客户端过多Driver进程导致网卡压力大

    • 注:AM只执行启动任务,不负责监控

    yarn-cluster模式

    cd /opt/sxt/spark-2.3.1/bin
    #指定任务提交地址,指定提交方式,指定主类,指定jar包位置 ,其他参数
    ./spark-submit
    --master yarn–cluster
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.3.1.jar  
    100
    • 执行流程

      • client向RM节点提交任务

      • RM选择随机NM节点创建AM(ApplicationMaster)

      • AM向RM请求application执行的资源,并接收响应的资源

      • AM根据响应,将请求发送到NM中启动Excutor进程

      • Excutor反向注册到AM所在节点,从AM接收task并执行

      • AM监控task,接收worker的执行结果

    • 适用于生产环境,执行Driver功能的AM随机分布在NM中,单点网卡压力降低

    停止集群任务命令:yarn application -kill applicationID

    宽窄依赖

    形容RDD之间的依赖关系,基于父子RDD之间的partition关联来判断

    • 窄依赖

      • 父RDD中的partition与子RDD中的partition为一对一或一对多

      • 不需要shuffle

    • 宽依赖

      • 父RDD中的partition指向多个子RDD中的partition,呈多对一关系

      • 需要执行shuffle

    stage+管道

    • 一个application包括若干并行的job,一个触发算子对应一个job,每个job会被拆分为多组相互关联的任务组,这些任务组就是stage

    • stage划分流程

      • spark根据RDD之间依赖关系构建DAG有向无环图,并提交给DAGScheduler

      • DAGScheduler将DAG划分若干相互依赖的stage,划分依据是RDD之间的宽窄依赖

      • 逆向切分,沿DAG从后向前,遇到宽依赖划分一个出stage

    • stage之间存在并行和串行两种关联

    • stage内部由一组并行的task构成,stage内部的并行度由最后一个RDD的分区数决定

    • task被送到executor上执行的工作单元

    • RDD的分区数主要在以下情况中可以指定

      • 读取数据时指定

      • 具备宽依赖RDD位置

    • stage内部计算采用管道计算模式

      • 每个task相当于一个管道,同一时间一次处理一条数据

      • task以高阶函数 f4(f3(f2(f1(x)))) 的形式处理stage内多个RDD的代码逻辑

      • 一个stage内部的task可以具备不同逻辑

    • 管道中数据落地的环节

      • 指定持久化的RDD节点

      • 执行shuffle write 的过程中

     

    资源调度与任务调度

    1. spark集群启动,worker节点向Master节点汇报资源,Master掌握了集群的资源。

    2. 客户端向spark提交application,Master根据app的RDD依赖关系构建DAG有向无环图。

    3. Master创建Driver进程,Driver进程中创建DAGScheduler和TaskScheduler调度器。

    4. TaskScheduler创建后向Master节点请求app的资源,Master根据请求在worker节点上启动Executor进程,Executor进程反向注册到Driver进程中。

    5. DAGScheduler根据DAG的宽窄依赖划分stage,将stage封装TaskSet为交给TaskScheduler,TaskSet中封装了stage中并行的task。

    6. TaskScheduler遍历TaskSet,将task分配给Executor执行,即发送到Executor中的线程池ThreadPool中

    7. TaskScheduler监控task执行,Executor的ThreadPool状态会响应给TaskScheduler。

    8. 监控过程

      1. 若task执行失败,TaskScheduler重新发送task到Executor中,默认重试3次

      2. 若task重试失败,则对应stage执行失败,DAGScheduler重新发送stage到TaskScheduler中重新执行,上述重试默认4次。若重试失败,则job失败,app失败

      3. TaskScheduler还否则重试执行缓慢(straggling)的task,TaskScheduler会发送新的task并行执行。关于执行结果采用推测执行机制,两个task以先执行完的结果为准,默认是关闭的,配置属性为spark.speculation。推测执行机制不适应ETL等数据插入的操作(数据冲恢复插入)和数据倾斜的情况

    9. app的资源申请是粗粒度的,application申请的资源,需要等待全部task执行完毕才会释放。优点是:不需要每个task反复请求资源,任务执行效率高;缺点:资源无法充分利用。

      注:MR使用细粒度资源申请方法,task自己申请资源执行任务,每个task执行完毕释放资源,资源充分利用,但task启动变慢。

    内存管理

    静态内存管理

    • 60% spark.storage.memoryFraction 存储内存分区

      • 90%存储+序列化

        • 80% RDD存储+广播变量

        • 20% 解压序列化

      • 10% 预留OOM

    • 20% spark.shuffle.memoryFraction shuffle内存分区

      • 80% shuffle聚合内存

      • 20% OOM预留

    • 剩余 task计算

    统一内存管理

    • 300M JVM

    • 75% spark.memory.storageFraction 存储内存分区

      • RDD存储+广播变量

      • shuffle聚合

        两者动态调用

    • 剩余 执行task

    注:spark1.6以后默认统一内存管理,设置spark.memory.useLegacyMode置为true,修改为静态内存管理

     

     

    任务参数

    提交任务的参数 ./spark-submit ...

    • --master MASTER_URL

      spark://host:port mesos://host:port yarn local (默认)

    • --deploy-mode DEPLOY_MODE client(默认)/cluster

    • --class CLASS_NAME 主类(包+类)

    • --name NAME 任务名

    • --jars JARS 依赖jar包,逗号分隔

    • --files FILES 相关文件

    • --conf PROP=VALUE 配置属性

    • --driver-memory Driver的内存,默认1024M

    • --executor-memory executor的最大内存,默认1G

    • 适用 standalone + cluster

      • --driver-cores driver的核数,默认1

    • 适用 standalone/Mesos + cluster

      • --supervise 失败重启Driver

    • 适用 standalone and Mesos

      • --total-executor-cores executor的总核数

    • 适用 standalone 或 YARN

      • --executor-cores 每个executor的核数,默认1

        通过--total-executor-cores和--executor-cores限定executor的数量

    • 使用yarn

      • --driver-cores driver的核数

      • --queue 资源队列名,默认default

      • --num-executors 指定executors 数量

    SparkShell

    Spark自带的一个快速原型开发工具,支持scala语言交互式编程。

    启动命令 ./spark-shell --master spark://node1:7077 (win/linux)

    执行任务 sc为默认创建的上下文环境

    #指定hdfs的文件进程wordcount ,shsxt是hdfs的集群名
    sc.textFile("hdfs://shsxt:9000/spark/aaa.txt")
    .flatMap(_.split(" "))
    .map((_,1))
    .reduceByKey(_+_)
    .foreach(println)

    SparkUI

    浏览器访问启动spark的主机 node1:8080 范围UI页面

    可以查看当前运行情况和历史运行情况

    history+UI

    对应需要临时保存日志:在shell启动或命令提交时配置如下属性

    • --conf spark.eventLog.enabled=true 保存日志

    • --conf spark.eventLog.dir=hdfs://shsxt/spark/test 指定hdfs存放路径,shsxt是hdfs集群名

    对于需要对所有任务都需要保存日志,需要配置spark-default.conf

    • 配置文件中加入,需要复制到所有节点

      #开启记录事件日志的功能
      spark.eventLog.enabled           true
      #设置事件日志存储的目录,shsxt是hdfs的集群名
      spark.eventLog.dir                 hdfs://shsxt/spark/log
      #日志优化选项,压缩日志
      spark.eventLog.compress         true
      #历史日志在hdfs的目录,shsxt是hdfs的集群名
      spark.history.fs.logDirectory hdfs://shsxt/spark/log
    • 在sbin中启动HistoryServer:./start-history-server.sh

    • UI地址为启动HistoryServer节点+端口: node4:18080

    广播变量与计数器

    广播变量

    • spark代码,RDD以外部分由Driver端执行,RDD以内的部分executor端的各task中执行。

    • 为了避免向每个task重复发送公用的变量,使用广播变量。

    • Driver将广播变量发送到executor端中,executor中的task公用一个变量。

    //scala
    val mybroadcase: Broadcast[String] =sc.broadcast("广播变量")
    sc.textFile("/data").foreach(x=>{
       print( mybroadcase.value )
    })
    //java
    Broadcast<String> xxx = jsc.broadcast("xxx");
    jsc.textFile("/data").foreach(new VoidFunction<String>(){
       @Override
       public void call( String line) throws Exception {
           //取值
           tring value = xxx.value();
      }
    });

    计数器

    • 在广播变量的基础上,不借助count算子,只能实现execute层面的变量计数,无法实现全局的事件计数。

    • 计数器实现:RDD外定义计数器(Driver中),在RDD内进行累加(Executor中),task完成汇总到Driver中实现计数。

    • 注:计数结果必须在Driver端解析,计数器默认从0开始计数,每个Executor独立计数后汇总到Driver累加。

    //scala
    val count = sc.longAccumulator
    sc.textFile("/data").foreach(x=>{
       count.add(1)
    })
    val num: lang.Long =count.value

    //java
    SparkContext sc = jsc.sc();
    LongAccumulator count = sc.longAccumulator();
    jsc.textFile("/data").foreach(new VoidFunction<String>(){
       @Override
       public void call( String line) throws Exception {
           //取值
           count.add(1);
    }});
    Long value = count.value();

    shuffle

    在宽依赖的RDD之间存在shuffle过程,将父RDD的分区的数据shuffle进入子RDD中不同的分区。类似reducebykey算子,将相同ke进入一个分区进行处理。

    • Shuffle Writer:上游stage的map task保证当前分区中相同的key写入一个分区文件中

    • Shuffle Read:下游stage的reduce task在所有机器中获取属于自己分区的分区文件

    Spark2之后使用SortShuffle,1.2之前使用HashShuffle,两者之间并用

    HashShuffle

    • 普通机制

      • 每个map task处理后数据通过hash分区器写入不同的buffer(默认32K)

      • 每个buffer对应一个磁盘小文件,每个buffer或小磁盘文件对应一个reduce task

      • reduce task拉取对应的磁盘小文件的数据

      • 小文件数量:map task数*reduce task数

      • 小文件过多导致:内存创建过多对象,容易OOM;拉取过多,通讯波动和故障易导致拉取失败(shuffle file cannot find) ,这种失败需要DAGScheduler重试stager,容易导致任务失败。

    • 合并机制

      • 一个executor进程中,所有map task公用一组buffer,减少磁盘小文件的数量

      • 小文件数量:executor数*reduce task数

    SortShuffle

    • 普通机制

      • map task 将计算结果写入自己的内存数据结构(默认5M)

      • shuffle设置定时器对内存数据结构的容量进行监控,若监控到大小达到阈值,向内存数据结构分配一倍的容量,直到节点剩余容量不够分配。此时启动内存数据结构溢写

      • 内存数据结构对内部数据排序分区,写出到磁盘小文件,溢写以batch形式去写,一个batch对应1w条数据,batch作为写出缓存

      • maptask完毕后,磁盘小文件合并为:一个数据文件+一个索引文件

      • reduce task通过索引拉取对应部分的数据

      • 生成 2*map task数的文件

    • bypass

      • 取消排序,直接将数据写出都小文件中

      • bypass的触发条件:reduce task数小于 spark.shuffle.sort.bypassMergeThreshold的参数值(默认200),使得小批量数据不排序

    Shuffle寻址

    • MapOutputTracker管理磁盘小文件

      • MapOutputTrackerMaster主,driver进程中

      • MapOutputTrackerWorker从,Executor进程中

    • BlockManager 块管理者

      • 分为主从架构

        • BlockManagerMaster,driver进程中,在使用广播变量和缓存数据时,BlockManagerSlave执行

        • BlockManagerWorker,Executor进程中,作为从节点

      • 通用进程

        • DiskStore 磁盘管理

        • MemoryStore 内存管理

        • ConnectionManager 负责连接其他BlockManagerWorker

        • BlockTransferService 负责数据传输

    • 寻址流程

      • 每个maptask执行完毕将小文件地址封装到MpStatus对象,通过MapOutputTrackerWorker向Driver的MapOutputTrackerMaster汇报

      • 所有maptask执行完毕,reducetask执行前,Excutor的MapOutputTrackerWorker向Driver端的MapOutputTrackerMaster请求磁盘小文件地址数据

      • Excutor的ConnectionManager连接其他节点的ConnectionManager,再借助BlockTransferService进行数据传输

      • BlockTransferService一次启动5个task拉取数据,一个task拉取最多48M数据

    shuffle优化

    reduce的OOM优化

    • 减少每次数据的拉取量,spark.reducer.maxSizeInFlight (64M)

    • 增大shuffle的内存分配

    • 提高executor总内存

    优化方式

    • 代码指定配置信息,优先级最高,硬编码不推荐

    • 任务提交命令中 --conf 后指定参数 (推荐)

    • 在spark-default.conf,适用所有任务,优先级最低,范围太广不推荐

    所有优化属性:

    • spark.shuffle.file.buffer 默认32k

      • 参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。

      • 调优建议:内存资源充足可以适当增加(比如64k),从而减少shuffle write过程中溢写次数,减少磁盘IO次数。性能提升范围1%~5%。

    • spark.reducer.maxSizeInFlight 默认48m

      • 参数说明:设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。

      • 调优建议:内存充足可以适当增加(比如96m),从而减少拉取数据的次数,减少网络IO,进而提升性能。性能提升范围1%~5%。

    • spark.shuffle.io.maxRetries 默认3

      • 参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,失败重试的最大次数。如果3次以后未成功,导致shuffle file not find错误和stage执行失败。

      • 调优建议:对于那些包含了特别耗时的shuffle操作,增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。 shuffle file not find taskScheduler不负责重试task,由DAGScheduler负责重试stage

    spark.shuffle.io.retryWait 默认值:5s 参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。 调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。

    spark.shuffle.memoryFraction 默认值:0.2 参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。 调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。

    spark.shuffle.manager 默认值:sort|hash 参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。 调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。

    spark.shuffle.sort.bypassMergeThreshold 默认值:200 参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。 调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。

    spark.shuffle.consolidateFiles 默认值:false 参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。 调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。

     

     

    案例

    PV-UV

    数据结构:用户ip+地址+时间+电话+会话+域名
    146.1.30.98 河南 2017-10-10 1512012307080  5263761960810313758    www.mi.com View
    

    pv:每个网站的当日访问数

    uv:每个网站的当日独立访客,以会话为准

    //配置不再重复,提供RDD0,值为字符串格式的数据
    val RDD1: RDD[(String, String)] = RDD0.map(x => {
      val y = x.split("\s+")
      //会话+域名
      (y(5), y(4))
    })
    //groupByKey取出相同的网站的数据 set去重,count计数
    val RDD2: RDD[(String, Iterable[String])] = RDD1.groupByKey()
    RDD2.foreach(x => {
      println("网站:"+x._1)
      val it = x._2.iterator
      var count=0
      val set = Set[String]()
      while(it.hasNext){
        count+=1
        set.add(it.next())
      }
      println("PV:"+count+"   UV:"+ set.size )
    })
    

    二次排序

    • 设置数据的封装类,该实现Serializable与Comparable<当前类>

      例子中比较 2个数字组成的数据

      public class SecondSortKey implements Serializable, Comparable<SecondSortKey> {
      	//序列化版本
          private static final long serialVersionUID = 1L;
          private int first;
          private int second;
          //set,get
          public int getFirst() { return first; }
          public void setFirst(int first) { this.first = first; }
          public int getSecond() { return second; }
          public void setSecond(int second) { this.second = second; }
          //构造器
          public SecondSortKey(int first, int second) {
              super(); 
              this.first = first; 
              this.second = second; 
          }
      //重写比较方法,返回+-0数字
          @Override
          public int compareTo(SecondSortKey o1) {
      		//对两组数据分别比较得出比价结果
              if (getFirst() - o1.getFirst() == 0) {
                  return getSecond() - o1.getSecond();
              } else {
                  return getFirst() - o1.getFirst();
              }
      
          }
      }
      
    • 数据载入RDD是,转为KV结构的RDD,key为封装类,value为实际数据

    • 使用sortByKey算子排序,得到所需排序效果

      val result: RDD[(SecondSortKey, String)] =RDD.map(x => {
          val a = x.split(" ")
          val b=new SecondSortKey(a(1).toInt, a(2).toInt)
          new Tuple2(b,x)
      }).sortByKey()
      

    分组取topN

    //获取一组数字中最大的3个值,使用一个3元素的数组,比较并写入数据
    Integer[] top3 = new Integer[3];
    //数据通过迭代器输出,分别填入数组的三个位置,其中处理了最先3个置入的数据和后面添加删除数据的过程
    while (iterator.hasNext()) {
        Integer x = iterator.next();
        for (int i = 0; i < top3.length; i++) {
            if(top3[i] == null){
                top3[i] = x;
                break;
            }else if(score > top3[i]){
                for (int j = 2; j > i; j--) {
                    top3[j] = top3[j-1];
                }
                top3[i] = x;
                break;
            }
        }
    }
    

    源码分析结论

    资源调度

    1. 若设置 --total-executor-core 参数,则使用指定的核数,若未设置则压榨集群性能,使用所有剩余核数

    2. 若没有设置 --executor-core 参数 ,则每个worker节点默认为这个application只开启一个executor进程。若设置了,一个worker可设置多个executor。集群会进一步考虑内存因素

    任务调度

    • DAGScheduler类的getMessingParentStages()方法是切割job划分stage,其中使用了递归的方式实现

    • DAGScheduler将stage封装后发送到TaskScheduler中,TaskScheduler遍历stage中的task并发送到executor中执行

  • 相关阅读:
    启动django报错
    celery简单使用
    git简单使用
    selinux干扰mysql启动
    python操作xml文件时,带有^M符号
    获取服务器内网地址
    WebStorm激活
    linux nohup python 后台运行无输出问题
    安装FTP
    sql server还原数据库代码
  • 原文地址:https://www.cnblogs.com/javaxiaobu/p/11775054.html
Copyright © 2020-2023  润新知