第14课:spark RDD解密学习笔记

来源:互联网 发布:刀塔传奇英雄数据 编辑:程序博客网 时间:2024/06/03 17:17

第14课:spark RDD解密学习笔记

本期内容:

1.RDD:基于工作集的应用抽象

2.RDD内幕解密

3.RDD思考

 

精通了RDD,学习Spark的时间大大缩短。解决问题能力大大提高,

彻底把精力聚集在RDD的理解上,SparkStreaming、SparkSQL、SparkML底层封装的都是RDD。

RDD是spark的基石,

1) RDD提供了通用的 抽象

2)  现在Spark有5个子框架SparkStreaming、SparkSQL、SparkML、GraphX、SparkR,可以根据自己从事的领域如医疗等建模后建立另外的库。

所有顶级spark高手:

1解决bug,性能调优。包括框架的BUG及对框架的修改。

2.拿spark就是做修改的,以配合自己从事的具体业务

 

1. Hadoop的mr是基于数据集的处理

基于工作集&基于数据集的共同特征:位置感知,容错,负载均衡,

基于数据集的处理工作方式是从物理存储设备上加载数据,操作数据,写入物理存储设备。

spark也是mr的一种方式,只是更细致更高效。

其实基于数据集的方式也是一张有向无环图。但与基于工作集不同。

基于数据集的方式每次都从物理存储读取数据操作数据然后写回物理设备。

hadoop的mr的劣势、不适用的场景:

1.含有大量迭代算法。

2.交互式查询。重点是:基于数据流的方式不能复用曾经的结果或中间计算结果。

假设有数千人并发数据仓库,假设100人的查询完全相同,那么每个人都需要重新查询。Spark就可以避免,因为可以复用。

spark会对结果进行重用

假如有一千人查询同一个数据仓库

spark的话,如果第一个人计算过的步骤,其他人都可以复用。

RDD是基于工作集的,除了有共同特点外,还增加了resillient Distributed Dataset

 

RDD弹性:

1:自动的进行内存和磁盘数据存储的切换

2.基于lineage的高效容错

3.task失败会自动进行特定次数的重试

4.stage如果失败会自动进行特定次数的重试而且重试时只会试算失败的分片。

5.checkpoint和persist,是效率和容错的延伸。

6.数据调度弹性:DAG TASK和资源管理无关

7.数据分片的高度弹性

计算过程中有很多数据碎片,那么Partition就会非常小,每个Partition都会由一个线程处理,就会降低处理效率。这时就要考虑把小文件合并成一个大文件。

另外一个方面,如果内存不多,而每个Partition比较大(数据Block大),就要考虑变成更小的分片,Sparke有更多的处理批次但不会出现OOM。

所以说根据数据分片的大小来提高并行度或降低并行度也是Spark高度弹性的表现。同时需要指出的是,不管是提高并行度还是降低并行度,仍具有数据本地性。当然,提高并行度还是降低度行度都是人工通过代码来调整的。

假设有一百万个数据分片,每个数据分片都非常小(1K或10KB),如果要把数据分片调整为一万个,如果使用repartition,就需要Shuffle。

从源码RDD.scala中的repartition方法可以看到内部调用的是coalesce,传入的参数是shuffle并设置为true。源码如下:

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {

    coalesce(numPartitions, shuffle = true)

  }

所以,如果要把很多小的数据分片合并成大的数据分片的话千万不要直接调用repartition,而要调用coalesce,coalesce默认的shufflefalsecoalesce的源码如下:

  /**

   * Return a new RDD that is reduced into `numPartitions` partitions.

   *

   * This results in a narrow dependency, e.g. if you go from 1000 partitions

   * to 100 partitions, there will not be a shuffle, instead each of the 100

   * new partitions will claim 10 of the current partitions.

   *

   * However, if you're doing a drastic coalesce, e.g. to numPartitions = 1,

   * this may result in your computation taking place on fewer nodes than

   * you like (e.g. one node in the case of numPartitions = 1). To avoid this,

   * you can pass shuffle = true. This will add a shuffle step, but means the

   * current upstream partitions will be executed in parallel (per whatever

   * the current partitioning is).

   *

   * Note: With shuffle = true, you can actually coalesce to a larger number

   * of partitions. This is useful if you have a small number of partitions,

   * say 100, potentially with a few partitions being abnormally large. Calling

   * coalesce(1000, shuffle = true) will result in 1000 partitions with the

   * data distributed using a hash partitioner.

   */

  def coalesce(numPartitions: Int, shuffle: Boolean = false)(implicit ord: Ordering[T] = null)

      : RDD[T] = withScope {

    if (shuffle) {

      /** Distributes elements evenly across output partitions, starting from a random partition. */

      val distributePartition = (index: Int, items: Iterator[T]) => {

        var position = (new Random(index)).nextInt(numPartitions)

        items.map { t =>

          // Note that the hash code of the key will just be the key itself. The HashPartitioner

          // will mod it with the number of total partitions.

          position = position + 1

          (position, t)

        }

      } : Iterator[(Int, T)]

 

      // include a shuffle step so that our upstream tasks are still distributed

      new CoalescedRDD(

        new ShuffledRDD[Int, T, T](mapPartitionsWithIndex(distributePartition),

        new HashPartitioner(numPartitions)),

        numPartitions).values

    } else {

      new CoalescedRDD(this, numPartitions)

    }

  }

 

如果要把一万个数据分片变成一百万个的话,可以用shuffle,也可以不用shuffle

 

RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,如果有其他人执行同样的查询的话就可以存入工作集,这就极大地提高了效率。特别是在数据仓库中,如果有一千人做同样的查询,第一个人在查询之后,每二个人查询时就可以直接从缓存中取结果。退一步说,如果一千人执行的查询只有前10个步骤是一样的,如果第一个人计算完成后,后面的人的前10步就不需要再计算了。这就极大地提升了查询速度。

如果是Hadoop的话一千个人执行同样的查询,就需要重复计算一千次。

缓存随时可以清理掉,如果内存或磁盘不足就需要根据优先度将不常使用的缓存内容清理掉。

RDDcache是直接放在内存中的。RDDcache通过checkpoint来清除。但checkpoint是重量级的。

SparkStreaming经常进行Checkpoint,原因是经常要用到以前的内容。假设要统计一段时间的内容,那就需要以前的数据。

如果Spark的一个Stage中有一千个步骤的话,默认只会产生一次结果。如果是HadoopMR就会产生999次中间结果,如果数据量很大的话,内存和磁盘都可能存不下。

Spark本身就是RDD的内容,RDD是只读分区的集合。RDD是数据集合,可以简单理解为ListArray

用一句话概括:RDD是分布式函数式编程的抽象。基于RDD的编程一般都是通过高阶函数的方式,原因是函数里传函数要对当前的Map函数作用的数据集进行记录的明细化操作。

Spark的每一步操作都是对RDD进行操作,而RDD是只读分区的集合。

由于每一次操作都是只读的,而操作会改变数据,那么产生中间结果怎么办?

=>不能立即计算。这就是lazy:不用时不算,用时才计算,所以不会产生中间结果

RDD的核心之一就是它的Lazy,因为这不计算,开始时只是对数据处理进行标记而已。例如WordCount中的map、flatmap其实并不计算数据,只是对数据操作的标记而已。

flatMap的源码如下:

/**

   *  Return a new RDD by first applying a function to all elements of this

   *  RDD, and then flattening the results.

   */

  def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] = withScope {

    val cleanF = sc.clean(f)

    new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.flatMap(cleanF))

  }

可以看出在flatMap中创建了一个MapPartitionsRDD,但第一个参数是this,这个this是指它依赖的父RDD。每次创建新的RDD都会把父RDD作为第一个参数传入。而这只是对数据处理的标记而已。这让我们联想到一种操作:

f(x)=x+2

x=y+1

y=z+3

执行时只是函数展开。

所以RDD每次构建对象都依赖于父RDD,最后就是函数展开。所以如果一个Stage中有一千个步骤的话,不会产生999次中间结果。

以前有人说spark不适合大规模计算,当时确实有其道理,主要有两点原因:

1)Spark一直基于内存迭代会消耗大量内存。例如如一千个步骤虽然不产生中间结果,但如果要复用别人的结果时就需要手动persist或cache,这确实非常消耗内存。

2)主要是因为shuffle机制。但现在Shuffle支持很多种机制,如hashShuffle、sortBasedShuffle、钨丝计划等,而且现在Shuffle只是一个接口,一个插件,可以自定义,所以可以应对任意规模的数据处理。

Spark1.2以前确实有规模的限制,是由其Shuffle机制导致的,但现在没有了。现在生产环境spark最低要1.3版本,因为Spark1.3引入了dataframe,这是里程碑式的。推荐使用Spark1.6

由于RDD是只读的,为了应对计算模型,RDD又是lazy级别的。每次操作都会产生RDD,每次构建新的RDD都是把父RDD作为第一个参数传入,这就构成了一个链条。在最后Action时才触发,这就构成了一个从后往前回溯的过程,其实就是函数展开的过程。

由于这种从后往前的回溯机制,Spark的容错的开销会非常低。

常规容错方式:

1)数据检查点checkpoint

2)记录数据的更新

数据检查点非常致命。数据检查点的工作方式是通过数据中心的网络连接不同的机器,每次操作时都要复制整个数据集,就相当于每次都有一个拷贝,拷贝是要通过网络的,而网络带宽就是分布式系统的瓶颈。同时因为要拷贝,就需要重组资源,这也是对性能的非常大的消耗。

记录数据的更新:每次数据变化时作记录,不需要拷贝,但1)比较复杂,而且每次更新数据需要权限,容易失控。2)耗性能。

spark的RDD就是记录数据更新的方式,但为何高效?

1)RDD是不可变的而且是lazy的。由于RDD是不可变的,所以每次操作时就要产生新的RDD,新的RDD将父RDD作为第一个参数传入,所以不存在全局修改的问题,控制难度就有极大的下降。计算时每次都是从后往前回溯,不会产生中间结果。在此基础上还有计算链条,出错可以从中间开始恢复。

恢复点要么是checkpoint要么是前一个stage的结果(因为Stage结束时会自动写磁盘)

2)如果每次对数据进行很小的修都要记录,那代价很大。RDD是粗粒度的操作:原因是为了效率为了简化。粗粒度就是每次操作时作用的都是所有的数据集合。细粒度代价太大。

对RDD的具体的数据的改变操作都是粗粒度的。--RDD的写操作是粗粒度的。但RDD的读操作既可以是粗粒度的又可以是细粒度的。

RDD的粗粒度的写操作限制了RDD的应用场景。例如网络爬虫就不适合sparkRDD。但现实中大数据处理场景大部分都是粗粒度的。特别是支持数据并行批处理的应用,例如机器学习,图计算,数据挖掘,都是在很多记录上进行相应操作,都是粗粒度的表现。

RDD不适合做细粒度和异步更新的应用。

如果想让Spark直接操作MYSQL的数据或者操作HBASE数据就需要复写RDD。

RDD的数据分片上运行的计算逻辑都是一样的。对于每个计算逻辑都有计算函数compute

def compute(split: Partition, context: TaskContext): Iterator[T]

所有的RDD操作返回的都是一个迭代器。如Map/flatMap等。这样的好处:假设用SparkSQL提取到了数据,产生了新的RDD,机器学习去访问这个RDD,但根本不需要知道这是来自于SparkSQL。这就可以让所有的框架无缝集成。结果就是机器学习可以直接调用SparkSQL,流处理也可以用机器学习进行训练。因为无论是什么操作返回的都是Iterator。所以就可以用hasNext来看看有没有下一个元素,然后通过Next读取下一个元素。Next具体怎么读取下一个元素和具体RDD实现有关。

Iterator的部分源码(scala.collection.Iterator):

trait Iterator[+Aextends TraversableOnce[A] {
  self =>

  def seq: Iterator[A] = this

  /** Tests whether this iterator can provide another element.
   *
   *  @return  `true` if a subsequent call to `next` will yield an element,
   *           `false` otherwise.
   *  @note    Reuse: $preservesIterator
   */
  def hasNext: Boolean

  /** Produces the next element of this iterator.
   *
   *  @return  the next element of this iterator, if `hasNext` is `true`,
   *           undefined behavior otherwise.
   *  @note    Reuse: $preservesIterator
   */
  def next(): A

  /** Tests whether this iterator is empty.
   *
   *  @return   `true` if hasNext is false, `false` otherwise.
   *  @note     Reuse: $preservesIterator
   */
  def isEmpty: Boolean = !hasNext

无论是什么操作,返回的结果都是Iterator接口,面向接口编程时能不能操作SparkSQL/RDD的子类的方法?

=>Spark可以,java不可。原因是有this.type

从Java的角度讲,面向接口编程不能调用子类的方法,但如果是this.type有运行时的支持,this.type会指向具体的子类,这样就可以调用子类的方法。SparkStreaming可以调用ML的子类进行训练,RDD本身是个abstract class,与机器学习等的算法无关。但由于有了this.type可以通过runtime把实例赋值给RDD,这样就可以操作了。

   如果开发了一个自己领域的子框架,例如金融领域,这个子框架就可以直接在代码中调用机器学习,调用图计算进行风险预测、个性化分析、行为模式分析等,也可以调用SparkSQL用于数据挖掘。同时机器学习也可以调用金融框架。

又例如开发一个电商框架,那么用户支付时又可以直接调用金融框架。

就是说每增加一个功能就会让所有的功能都增强。每提出一个新的框架都可以使用其他所有的功能。这是核聚变级别的。

Spark的所有子框架都是基于RDD的,只不过是子类而已。

 

下面再看一下RDD.scala类中的preferredLocation的源码:

final def preferredLocations(split: Partition): Seq[String] = {
  checkpointRDD.map(_.getPreferredLocations(split)).getOrElse {
    getPreferredLocations(split)
  }
}

分布式大数据计算时优先考虑数据不动代码动,由于有了preferredLocation,可以说spark不仅可以处理大数据,Spark可以处理一切数据。可以处理数据的数据,可以处理普通文件系统上的数据,可以在Linux上运行,也可以在Windows上运行,可以运行一切文件格式。

由于preferredLocation,所以每次计算都符合完美的数据本地性。Spark要做一体化多元化的数据通用处理框架,兼容一切文件系统一切操作系统一切文件格式。

Spark计算更快,算子更丰富,使用更简单,一统数据处理天下。

IBM在2016年6月16日宣布承诺大力推进Apache Spark项目,并称该项目为:在以数据为主导的,未来十年最为重要的新的开源项目。这一承诺的核心是将Spark嵌入IBM业内领先的分析和商务平台,并将Spark作为一项服务,在IBM Bluemix平台上提供给客户。IBM还将投入超过3500名研究和开发人员在全球十余个实验室开展与Spark相关的项目,并将为Spark开源生态系统无偿提供突破性的机器学习技术——IBM SystemML,同时,IBM还将培养超过100万名Spark数据科学家和数据工程师。

因为Spark是运行在JVM上的,一切能运行在JVM上的数据Spark都能处理。

只有一点:spark替代不了实时事务处理。如银行转帐等,因为Spark反应还不够快,而且实时事务性处理控制难度比较大。

Spark完全可以做实时处理。SparkStreaming可以达到1ms内的响应速度(官方200ms)。

spark要统一数据计算领域除了实时事务性处理。

 

下面再看一下RDD.scala中的dependencies的源码:

/**
 * Get the list of dependencies of this RDD, taking into account whether the
 * RDD is checkpointed or not.
 */
final def dependencies: Seq[Dependency[_]] = {
  checkpointRDD.map(r => List(new OneToOneDependency(r))).getOrElse {
    if (dependencies_ == null) {
      dependencies_ = getDependencies
    }
    dependencies_
  }
}

后面的RDD对前面的RDD都有依赖,所以容错性非常好。

下面再看一下RDD.scla中的partitions的源码:

/**
 * Get the array of partitions of this RDD, taking into account whether the
 * RDD is checkpointed or not.
 */
final def partitions: Array[Partition] = {
  checkpointRDD.map(_.partitions).getOrElse {
    if (partitions_ == null) {
      partitions_ = getPartitions
    }
    partitions_
  }
}

进行下一步操作时可以改变并行度。并行度是弹性的一部分。

 

RDD的缺陷:

不支持细粒度的更新操作和增量迭代计算(如网络爬虫)

增量迭代时每次可能只迭代一部分数据,但RDD是粗粒度的,无法考虑是不是只是一部分数据。

Jstorm支持增量迭代计算,是用Java的方式重构的Storm(由阿里开发)

 

真相给你自由,人的一切痛苦都源于不了解真相。所以必须了解Spark的真相,在工作时才能自由。

 

以上内容是王家林老师DT大数据梦工厂《 IMF传奇行动》第14课的学习笔记。
王家林老师是Spark、Flink、Docker、Android技术中国区布道师。Spark亚太研究院院长和首席专家,DT大数据梦工厂创始人,Android软硬整合源码级专家,英语发音魔术师,健身狂热爱好者。

微信公众账号:DT_Spark

联系邮箱18610086859@126.com 

电话:18610086859

QQ:1740415547

微信号:18610086859  

新浪微博:ilovepains

 

 

百度百科关于Java事务处理的参考资料:

java事务处理

 编辑

Java的事务处理,如果对数据库进行多次操作,每一次的执行或步骤都是一个事务.如果数据库操作在某一步没有执行或出现异常而导致事务失败,这样有的事务被执行有的就没有被执行,从而就有了事务的回滚,取消先前的操作.....

中文名

JAVA事件处理

外文名

Java Transaction Processing

简介

编辑

Java的事务处理,如果对数据库进行多次操作,每一次的执行或步骤都是一个事务.如果数据库操作在某一步没有执行或出现异常而导致事务失败,这样有的事务被执行有的就没有被执行,从而就有了事务的回滚,取消先前的操作.....

详细说明

编辑

Java中使用事务处理,首先要求数据库支持事务。如使用MySQL的事务功能,就要求MySQL的表类型为Innodb才支持事务。否则,在Java程序中做了commitrollback,但在数据库中根本不能生效。

JavaBean中使用JDBC方式进行事务处理

public int delete(int sID) {

dbc = new DataBaseConnection();

Connection con = dbc.getConnection();

try {

con.setAutoCommit(false);// 更改JDBC事务的默认提交方式

dbc.executeUpdate("delete from xiao where ID=" + sID);

dbc.executeUpdate("delete from xiao_content where ID=" + sID);

dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);

con.commit();//提交JDBC事务

con.setAutoCommit(true);// 恢复JDBC事务的默认提交方式

dbc.close();

return 1;

}

catch (Exception exc) {

con.rollBack();//回滚JDBC事务

exc.printStackTrace();

dbc.close();

return -1;

}

}

在数据库操作中,一项事务是指由一条或多条对数据库更新的sql语句所组成的一个不可分割的工作单元。只有当事务中的所有操作都正常完成了,整个事务才能被提交到数据库,如果有一项操作没有完成,就必须撤消整个事务。

例如在银行的转帐事务中,假定张三从自己的帐号上把1000元转到李四的帐号上,相关的sql语句如下:

update account set monery=monery-1000 where name='zhangsan'

update account set monery=monery+1000 where name='lisi'

这个两条语句必须作为一个完成的事务来处理。只有当两条都成功执行了,才能提交这个事务。如果有一句失败,整个事务必须撤消。

connection类中提供了3个控制事务的方法:

1) setAutoCommit(Boolean autoCommit):设置是否自动提交事务;

2) commit();提交事务;

3) rollback();撤消事务;

jdbc api中,默认的情况为自动提交事务,也就是说,每一条对数据库的更新的sql语句代表一项事务,操作成功后,系统自动调用commit()来提交,否则将调用rollback()来撤消事务。

jdbc api中,可以通过调用setAutoCommit(false) 来禁止自动提交事务。然后就可以把多条更新数据库的sql语句做为一个事务,在所有操作完成之后,调用commit()来进行整体提交。倘若其中一项 sql操作失败,就不会执行commit()方法,而是产生相应的sqlexception,此时就可以捕获异常代码块中调用rollback()方法撤消事务。

事务处理是企业应用需要解决的最主要的问题之一。J2EE通过JTA提供了完整的事务管理能力,包括多个事务性资源的管理能力。但是大部分应用都是运行在单一的事务性资源之上(一个数据库),他们并不需要全局性的事务服务。本地事务服务已然足够(比如JDBC事务管理)。

事务的特性

编辑

Atomic原子性、Consistency一致性、Isolation隔离性和Durability持久性。

原子性:指整个事务是不可以分割的工作单元。只有事务中所有的操作执行成功,才算整个事务成功,事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该回到执行事务前的状态。

一致性:指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对于银行转账事务,不管事务成功还是失败,应该保证事务结束后两个转账账户的存款总额是与转账前一致的。

隔离性:指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。

持久性:指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

本文并不讨论应该采用何种事务处理方式,主要目的是讨论如何更为优雅地设计事务服务。仅以JDBC事务处理为例。涉及到的DAOFactory,Proxy,Decorator等模式概念,请阅读相关资料

 

另有一篇不错的关于事务处理的文章:http://www.cnblogs.com/bicabo/archive/2011/11/14/2248044.html

 

 

 

 

0 0
原创粉丝点击