Spark导入各种类型外部数据 Spark cooker 第三章:外部数据源

本文深入探讨了Apache Spark如何从各种数据源加载数据,包括本地文件系统、HDFS、Amazon S3、Apache Cassandra及关系型数据库。介绍了Spark支持的不同文件格式和存储系统,以及如何使用特定的InputFormat。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

翻译
Spark cookbook

简介

spark为大数据提供了一个统一的运行环境。Hadoop分布式文件系统HDFS作为Spark最常用的存储平台,使用普通硬件(廉价机)为非结构化数据或半结构化数据提供了低成本的存储。Spark并不局限于HDFS,可以用于任何Hadoop支持的存储。

Hadoop支持的存储是指可以使用Hadoop InputFormat和OutputFormat接口的存储格式。InputFormat可以从输入数据划分InputSplit,并进一步分解成记录(一行数据即一条记录)。OutputFormat用于写入存储。

我们将先写入本地文件系统,然后从HDFS里加载数据。在从HDFS加载数据诀窍中,我们将包含最常用的文件格式:常规文本。在下一诀窍中,我们将包含如何使用任意的InputFormat接口向Spark导入数据。我们也会探索如何从Amazon S3中加载数据,一个先进的云存储平台。我们还会探索如何从非结构化数据库Apache Cassandra中加载数据。最后我们将探索如何从关系型数据库中加载数据。

从本地文件系统加载数据

本地文件受限于磁盘容量和非分布式的缺陷,并不适合存储大数据,但是技术上你还是可以从本地文件系统加载上传到分布式文件系统上的数据。但是你访问的文件/木兰路必须在每一个节点都可以访问。

请注意如果你打算用这个特点来加载辅助数据(side data),这不是个好注意。加载辅助数据时,Spark有一个广播变量的特征,将在后续的内容讨论。
这一诀窍中,我们将讨论如何从本地文件系统中加载数据到Spark。

怎么做

我们从莎士比亚的"to be or not to be"开始:

  1. 创建words目录
    $ mkdir words
  2. 进入目录
    $ cd words
  3. 创建sh.txt文件并输入"to be or not to be"
    $ echo “to be or not to be” > sh.txt
    启动Spark-shell
  4. $ spark-shell
    以RDD的形式加载words目录
  5. scala> val words = sc.textFile(“file:///home/hduser/words”)
    计数:行数
  6. scala> words.count
    将行分解成多个单词
  7. scala> val wordsFlatMap = words.flatMap(_.split("\W+"))
    将单词转换成 (word,1)—1表示每个单词出现的次数1,word本身作为key
    scala> val wordsMap = wordsFlatMap.map( w => (w,1))
  8. 使用reduceByKey方法将每个单词出现的次数累加,reduceByKey方法每次作用在相邻的两个值a,b上
    scala> val wordCount = wordsMap.reduceByKey( (a,b) => (a+b))
  9. 打印RDD
    scala> wordCount.collect.foreach(println)
  10. 所有步骤集中到一步如下
    scala> sc.textFile(“file:///home/hduser/ words”). flatMap(_.
    split("\W+")).map( w => (w,1)). reduceByKey( (a,b) => (a+b)).
    foreach(println)
    最终结果:
    在这里插入图片描述

从HDFS系统加载数据

HDFS是最常用的大数据存储系统。HDFS被广泛采用的一个原因是读取获得架构。这是指HDFS并不对数据做任何限制,当数据被写入时。任何数据都被接受并可以以原始格式出入。这一特性使得HDFS十分适合村村原始的非结构化的数据和半结构化数据。
当读取数据时,甚至是非结构化数据,需要制定一定的结构来使读取的数据具有意义。Hadoop使用InputFormat决定如何读取数据。Spark完全支持Hadoop的InputFormat,所以任何可以被Hadoop读取的文件也可以被Spark读取。
默认的InputFormat是TextInputFormat。TestInputFormat使用一行文本的字节偏移量作为key,一行文本的内容作为value。Spark使用sc.textFile方法读取TextInputFormat。忽略字节偏移量并创建字符串RDD。

有时候文件名字本身包含有有用的信息,例如时间序列数据。在这种情况下,你也许希望分别读取各个文件。sc.wholeTextFiles方法允许你做这样的操作。sc.wholeTextFiles方法创建RDD并以文件名和路径 (如hdfs://localhost:9000/user/hduser/words )作为key并以整个文件的内容作为value。

Spark同样支持使用DataFrame读出不同方式序列化和支持压缩的格式,比如Avro,Parquet,JSON。这些格式将在后续的内容说明。

这一诀窍我们看看如何Spark shell如何从HDFS系统中加载数据。

怎么做

这次我们从HDFS加载数据,执行单词计数。

  1. 创建目录
    $ mkdir words
  2. 进入目录
    $ cd words
  3. 创建文件并输入"to be or not to be"
    $ echo “to be or not to be” > sh.txt
  4. 启动Spark shell
    $ spark-shell
  5. 以RDD的形式加载word目录
    scala> val words = sc.textFile(“hdfs://localhost:9000/user/hduser/
    words”)
    ** sc.textFile方法也支持传递一个参数作为分区的数目。默认情况下,Spark为每一个InputSplit类创建一个分区,大体上对应一个块。
    你可以要求更多数量的分区。这一点在计算密集型的作业,比如机器学习使用适用。由于一个分区不可能包含多于一个块,少于块数目的分区是不允许的。 **
  6. 计数行数(结果为1)
    scala> words.count
  7. 分解行为多个单词
    scala> val wordsFlatMap = words.flatMap(_.split("\W+"))
  8. 将单词转换成 (word,1)—1表示每个单词出现的次数1,word本身作为key
    scala> val wordsMap = wordsFlatMap.map( w => (w,1))
  9. 使用reduceByKey方法将每个单词出现的次数累加,reduceByKey方法每次作用在相邻的两个值a,b上
    scala> val wordCount = wordsMap.reduceByKey( (a,b) => (a+b))
  10. 打印RDD
    scala> wordCount.collect.foreach(println)
  11. 一步到位
    scala> sc.textFile(“hdfs://localhost:9000/user/hduser/words”).
    flatMap(_.split("\W+")).map( w => (w,1)). reduceByKey( (a,b) =>
    (a+b)).foreach(println)
    结果:
    在这里插入图片描述

更多…

有时我们需要一次性访问整个文件。有时文件名本身包含有用的信息,例如时间序列等。有时你需要将多行数据作为一个记录来处理。sparkContext.wholeTextFiles用于这些情况。我们来看看来自于 ftp://ftp.ncdc.noaa.gov/pub/data/noaa/ 的天气数据。
顶层目录如下:
在这里插入图片描述
进入某个特定年份的目录——比如1901类似于下面的截图。
在这里插入图片描述
数据分割后,每一个文件名都包含有用的信息,USAF-WBAN-year,其中USAF是指US空军站序号,WBAN是指气象局军队海军位置编号。
你也会注意到所有文件都被压缩成gzip,扩展名.gz。压缩是自动处理的,所以你只需要上传数据到HDFS中就可以了。我们在以后的章节中会回头处理这个数据集。
由于整个数据不是很大,可以直接上传到伪分布式模式的HDFS中:

  1. 下载数据
    $ wget -r ftp://ftp.ncdc.noaa.gov/pub/data/noaa/
  2. 加载数据到HDFS
    $ hdfs dfs -put ftp.ncdc.noaa.gov/pub/data/noaa weather/
  3. 启动spark-shell
    $ spark-shell
  4. 加载1901的气象数据到RDD、
    scala> val weatherFileRDD = sc.wholeTextFiles(“hdfs://localhost:9000/user/hduser/weather/1901”)
  5. 缓存数据到RDD中,这样就不必每次访问数据时都重新计算
    scala> val weatherRDD = weatherFileRDD.cache
    在Spark中,有多种存储级别可用于RDD持久化。rdd.cache是rdd.persist(MEMORY_ONLY)存储级别的缩写。
  6. 统计元素数量
    scala> weatherRDD.count
  7. 由于文件的所有内容都被作为一个元素加载,我们需要手动解读数据,所以我们搜仙加载第一个元素
  8. 读取第一个RDD的值
    scala> val firstValue = firstElement._2
    第一个元素中包含(String,String)型元组。元组可以通过两种方式访问:
    ‰ 使用位置函数,以_1
    ‰ 使用producElement方法,比如, tuple.productElement(0)。下标和多数其他方法一样,起始于0。
  9. 以行分割firstValue
    scala> val firstVals = firstValue.split("\n")
  10. 计数firstValue包含的元素的个数
    scala> firstVals.size
  11. 气象数据以文本的位置为分隔符,其架构十分丰富。你可以在国家气象服务网站获取更多有关构架的信息。我们可以获得风速,来自66-69段(单位 米/秒)
    scala> val windSpeed = firstVals.map(line => line.substring(65,69)

使用自定义InputFormat从HDFS加载数据

有时候你需要加载特定格式的数据,TextInputFormat并不适合你的需要。Spark为这种情况提供两种方法:

sparkContext.hadoopFile :支持老板副本MapReduce API
sparkContext.newAPIHadoopFile :支持新版本MapReduce API

这两种方法支持所有Hadoop内置InputFormat接口以及任何自定义InputFormat。

怎么做

我们将要以键值对的方式加载文本数据,并使用KeyValueTextInputFormat加载数据到Spark。

  1. 建立货币目录
    $ mkdir currency
  2. 进入货币目录
    $ cd currency
  3. 创建na.txt文本文件,输入以键值对(key:国家,value:货币)的形式输入货币值,tab作为分割符
    $ vi na.txt
    United States of America US Dollar
    Canada Canadian Dollar
    Mexico Peso
    你可以为每一个部分创建更多的文件。
  4. 上传货币文件夹到HDFS
    $ hdfs dfs -put currency /user/hduser/currency
  5. 启动spark shell
    $ spark-shell
  6. 导入申明
    scala> import org.apache.hadoop.io.Text
    scala> import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat
  7. 以RDD的形式加载货币文件夹
    val currencyFile = sc.newAPIHadoopFile(“hdfs://localhost:9000/user/hduser/currency”,classOf[KeyValueTextInputFormat],classOf[Text],classOf[Text])
  8. 由(Text,Text)元组转换成(String,String)
    val currencyRDD = currencyFile.map( t => (t._1.toString,t._2.toString))
  9. 技术
    scala> currencyRDD.count
  10. 打印值
    scala> currencyRDD.collect.foreach(println)
    在这里插入图片描述
    你可以通过这个方法加载数据到任何Hadoop支持的InputFormat接口中。

从Amazon S3中加载数据

Amazon简易存储服务(S3)为开发人员和IT团队提供了安全的,持久的,弹性的存储平台。Amazon S3最大的优点在于不需要预先(容量)的IT研究,企业只需要点击一下按钮,就可以创建所需要的容量。
尽管Amazon S3可以与任何计算平台一起使用,Amazon S3和Amazon的云服务如Amazon 弹性计算云(EC2),Amazon 弹性块存储(EBS)极好的整合在一起。由于这个原因,使用Amazon Web Service(AWS)的公司往往已经把重要的数据存储在Amazon S3上。
这为从Amazon S3加载到Spark提供了一个好案例,这也是这个诀窍要讨论的。

怎么做

让我们从ASW端口开始:

  1. 登录http://aws.amazon.com
  2. 登录后,导航到Storage & Content Delivery | S3 | Create Bucket
    在这里插入图片描述
  3. 输入bucket(存储空间)名字——比如 com.infoobjects.wordcount。确保你输入独一无二的bucketname(S3存储空间名在全球范围内不能重复)
  4. 选择地区,点击 Create,然后在你创建的存储空间可以看到下面屏幕:在这里插入图片描述
  5. 点击创建文件夹,输入文件名
  6. 在本地文件系统创建sh.txt文本文件
    $ echo “to be or not to be” > sh.txt
  7. 导航至Words | Upload | Add Files,选择在对话框中sh.txt,如下图:
    在这里插入图片描述
  8. 点击上传
  9. 选择sh.txt点击Properties,显示文件的详细信息:
    在这里插入图片描述
  10. 设置 AWS_ACCESS_KEY 和 AWS_SECRET_ACCESS_KEY为环境变量
  11. 启动Spark shell,从s3中加载目录到words RDD
    scala> val words = sc.textFile(“s3n://com.infoobjects.wordcount/words”)
    RDD加载完成,接下来可以对EDD进行transformation和action操作。

**有时候s3:// and s3n://会造成混淆。
s3n://表示常规文件位于S3 bucket但是可以被外部读写。这个文件系统对文件大小有5GB的限制。
s3://表示位于S3 bucket里的HDFS文件。是一个基于块的文件系统。文件系统要求你为这个文件系统提供一个bucket。这个系统下的文件没有大小限制。 **

从Apache Cassandra加载文件(略,麻烦)

从关系型数据库加入数据

许多重要的数据存储在关系型数据库中,Spark需要访问这些数据库。JDBCRDD是一个Spark特性,允许关系型数据表被加载为RDDs。这个诀窍解释了如何使用JDBCRDD。

下一章要讲解的Spark SQL包含了JDBC所需的数据源。对于当前诀窍而言,这一数据应当被视为返回的DataFreames的结果们可以被Spark SQL加工并可以与其他数据源结合使用。

预备备

确保JDBC驱动JAR在客户端节点和所有要运行executor的从节点上都可见。

怎么做

通过以下步骤从关系型数据库导入数据:

  1. 通过以下DDL在MySQL中创建一张名为person的数据表
    CREATE TABLE ‘person’ (
    ‘person_id’ int(11) NOT NULL AUTO_INCREMENT,
    ‘first_name’ varchar(30) DEFAULT NULL,
    ‘last_name’ varchar(30) DEFAULT NULL,
    ‘gender’ char(1) DEFAULT NULL,
    PRIMARY KEY (‘person_id’);
    )
  2. 插入一些数据
    Insert into person values(‘Barack’,‘Obama’,‘M’);
    Insert into person values(‘Bill’,‘Clinton’,‘M’);
    Insert into person values(‘Hillary’,‘Clinton’,‘F’);
  3. 从http://dev.mysql.com/downloads/connector/j/ 下载mysql-connector-java-x.x.xx-bin.jar
  4. 使MySQL驱动可以被Spark shell使用并启动
    $ spark-shell --jars /path-to-mysql-jar/mysql-connector-java-5.1.29-bin.jar
    path-to-mysql-jar替换成实际jar路径
  5. 为用户名,密码,JDBC URL创建变量
    scala> val url=“jdbc:mysql://localhost:3306/hadoopdb”
    scala> val username = “hduser”
    scala> val password = “******”
  6. 导入 JdbcRDD
    scala> import org.apache.spark.rdd.JdbcRDD
  7. 导入JDBC相关类
    scala> import java.sql.{Connection, DriverManager, ResultSet}
  8. 创建一个JDBC驱动的实例
    scala> Class.forName(“com.mysql.jdbc.Driver”).newInstance
  9. 加载JDBCRDD
    scala> val myRDD = new JdbcRDD( sc, () =>
    DriverManager.getConnection(url,username,password) ,
    “select first_name,last_name,gender from person limit ?, ?”,
    1, 5, 2, r => r.getString(“last_name”) + ", " + r.getString(“first_name”))
  10. 现在开始查询结果
    scala> myRDD.count
    scala> myRDD.foreach(println)
  11. 保存RDD到HDFS
    scala> myRDD.saveAsTextFile(“hdfs://localhost:9000/user/hduser/person”)

这是怎么工作的

JDBCRDD是一个通过JDBC连接执行SQL查询,并提取结果的RDD。下面是一个JdbcRDD构造器:
JdbcRDD( SparkContext, getConnection: () => Connection,
sql: String,
lowerBound: Long, upperBound: Long,numPartitions: Int,
mapRow: (ResultSet) => T =
JdbcRDD.resultSetToObjectArray)
两个?是JdbcRDD中准备语句中的绑定变量。第一个?绑定偏移(下界),指的是我们开始计算的行,第二个?是指限制(上届),指的是我们要读多少行。
JdbcRDD是一个在对等模式下从关系型数据库中加载数据到Spark中的方法。如果你希望从关系型数据库整块的加载数据,其他方法会有更好的效果,比如,Apache Sqoop是一个有力工具用于从关系型数据库导入导出到HDFS。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值