Spark的数据读取即数据保存可以从两个维度来做区分:文件格式以及文件系统。
文件格式分为:Text文件,Json文件,Csv文件,Sequence文件以及Object文件;
文件系统分为:本地文件系统,HDFS,HBase以及数据库。
1. 文件类数据读取与保存
1.1 Text文件
-
数据读取:textFile(String)
var hdfsFile = sc.textFile("hdfs://hadoop01:8020/fruit.txt")
-
数据保存:saveAsTextFile(String)
hdfsFile.saveAsTextFile("/fruitOut")
1.2 Json文件
如果JSON文件中每一行就是一个JSON记录,那么可以通过将JSON文件当做文本文件来读取,然后利用相关的JSON库对每一条数据进行JSON解析。
注意:使用RDD读取JSON文件处理很复杂,同时SparkSql集成了很好的处理JSON文件的方式,所以应用中多采用SparkSql处理JSON文件。
object JsonTest {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 读取文件
val lineRdd = sc.textFile("D://sparkData/students.json")
// 解析json数据
val result = lineRdd.map(JSON.parseFull).collect()
result.foreach(println(_))
sc.stop()
}
}
1.3 Sequence文件
SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat File)。
Spark有专门用来读取SequenceFile的接口,在SparkContext中,可以调用sequenceFile[keyClass, valueClass] (path)。
注意:SequenceFile文件只针对PairRDD
object SequenceTest {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 读取文件
val lineRdd = sc.textFile("D://sparkData/students.json")
// 将RDD保存为Sequence文件
lineRdd.map((_, 1)).saveAsSequenceFile("D://sparkData/seq")
// 读取Sequence文件
val seq = sc.sequenceFile[String, Int]("D://sparkData/seq")
// 打印读取后的Sequence文件
seq.collect().foreach(println(_))
sc.stop()
}
}
1.4 对象文件
对象文件是将对象序列化后保存的文件,采用Java的序列化机制。
可以通过objectFile[k, v] (path)函数接收一个路径,读取对象文件,返回对应的RDD,也可以通过调用saveAsObjectFile()实现对对象文件的输出。
因为是序列化所以要指定类型。
object ObjectTest {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 读取文件
val lineRdd = sc.textFile("D://sparkData/students.json")
// 将文件保存为Object文件
lineRdd.saveAsObjectFile("D://sparkData/obj")
// 读取Object文件
val result = sc.objectFile[String]("D://sparkData/obj")
result.collect().foreach(println(_))
sc.stop()
}
}
2. 文件系统类数据读取与保存
2.1 HDFS
Spark的整个生态系统与Hadoop是完全兼容的,所以对于Hadoop所支持的文件类型或者数据库类型,Spark也同样支持。另外,由于Hadoop的API有新旧两个版本,所以Spark为了能够兼容Hadoop所有的版本,也提供了两套创建操作接口。对于外部存储创建操作而言,hadoopRDD和newHadoopRDD是最为抽象的两个函数接口,主要包含以下四个参数。
- 输入格式(InputFormat):制定数据输入的类型,如TextInputFormat等,新旧两个版本所引用的版本分别是org.apache.hadoop.mapred.InputFormat和
org.apache.hadoop.mapreduce.InputFormat(NewInputFormat) - 键类型:指定[K, V]键值对中K的类型
- 值类型:指定[K, V]键值对中V的类型
- 分区值:指定由外部存储生成的RDD的partition数量的最小值,如果没有指定,系统会使用默认值 defaultMinSplits
注意:其他创建操作的API接口都是为了方便最终的Spark程序开发者而设置的,是这两个接口的高效实现版本。例如,对于textFile而言,只有path这个指定文件路径的参数,其他参数在系统内部指定了默认值。
- 在Hadoop中以压缩形式存储的数据,不需要指定解压方式就能够进行读取,因为Hadoop本身有一个解压器会根据压缩文件的后缀推断解压算法进行解压。
- 如果用Spark从Hadoop中读取某种类型的数据不知道怎么读取的时候,上网查找一个使用map-reduce的时候是怎么读取这种这种数据的,然后再将对应的读取方式改写成上面的hadoopRDD和newAPIHadoopRDD两个类就行了。
2.2 MySQL数据库连接
支持通过Java JDBC访问关系型数据库。需要通过JdbcRDD进行,示例如下:
添加依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
MySql读取:
import java.sql.DriverManager
import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkConf, SparkContext}
object MysqlSelect {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 定义连接mysql的参数
val driver = "com.mysql.cj.jdbc.Driver"
val url = "jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=GMT%2B8"
val userName = "root"
val passWd = "123456"
// 创建JdbcRDD
val rdd = new JdbcRDD(sc, () => {
Class.forName(driver)
DriverManager.getConnection(url, userName, passWd)
},
"select * from dynasty where id >= ? and id <= ?;",
1,
10,
1,
r => (r.getInt(1), r.getString(2))
).cache()
// 打印最后结果
println(rdd.count())
println("-----------------")
rdd.foreach(print(_))
sc.stop()
}
}
MySql写入:
import java.sql.DriverManager
import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkConf, SparkContext}
object MysqlInsert {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 创建要插入的数据
val data = sc.parallelize(List("Female", "Male", "Female"))
// 往mysql中添加数据,调用foreachPartition针对每一个分区进行操作
data.foreachPartition(insertData)
def insertData(iterator: Iterator[String]): Unit = {
// 定义连接mysql的参数
val driver = "com.mysql.cj.jdbc.Driver"
val url = "jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=GMT%2B8"
val userName = "root"
val passWd = "123456"
// 注册数据库驱动
Class.forName(driver).newInstance()
// 获取数据库连接
val conn = DriverManager.getConnection(url, userName, passWd)
iterator.foreach(data => {
val sql = "insert into emp(name) values (?);"
// 将数据存入到mysql
val ps = conn.prepareStatement(sql)
// 封装数据
ps.setString(1, data)
// 执行
ps.execute()
})
conn.close()
}
}
}
2.3 HBase数据库
由于org.apache.hadoop.hbase.mapreduce.TableInputFormat类的实现,Spark可以通过Hadoop输入格式访问HBase。这个输入格式会返回键值对数据,其中键的类型为org.apache.hadoop.hbase.io.ImmutableBytesWritable,而值的类型为org.apache.hadoop.hbase.client.Result。
添加依赖:
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-mapreduce</artifactId>
<version>2.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>2.2.5</version>
</dependency>
从HBase读取数据:
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.{Put, Result}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object HbaseSelect {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 建立HBase的连接
val config:Configuration = HBaseConfiguration.create()
config.set("hbase.zookeeper.quorum", "node7-1:2181,node7-2:2181,node7-3:2181")
// 设置查询的表明mydata:psn_1
config.set(TableInputFormat.INPUT_TABLE, "mydata:psn_1")
// 通过SparkContext将mydata:psn_1表中数据创建一个RDD
val hbaseRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
config,
classOf[TableInputFormat],
classOf[ImmutableBytesWritable],
classOf[Result]
)
// 计算数据条数
val count: Long = hbaseRDD.count()
println(count)
// 遍历输出
// 当建立RDD的时候,前面全部是参数信息,后面的resul才是保存数据的数据集
hbaseRDD.foreach {
case (_, result) =>
// 通过result.getRow来获取行键
val key: String = Bytes.toString(result.getRow)
// 通过result.getValue("列族", "列名")来获取值
// 注意这里需要使用getBytes将字符流转化成字节流
val name: String = Bytes.toString(result.getValue("cf".getBytes, "name".getBytes))
val age: String = Bytes.toString(result.getValue("cf".getBytes, "age".getBytes))
// 打印结果
println("RowKey:" + key + ",Name:" + name + ",Age:" + age)
}
// 关闭连接
sc.stop()
}
}
往HBase写入数据:
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.{Put, Result}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapred.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.mapred.JobConf
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object HbaseInsert {
def main(args: Array[String]): Unit = {
//控制日志输出
Logger.getLogger("org").setLevel(Level.ERROR)
// 创建spark配置信息
val conf: SparkConf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]")
// 创建SparkContext
val sc = new SparkContext(conf)
// 连接hbase
val conf = HBaseConfiguration.create()
conf.set("hbase.zookeeper.quorum", "node7-1:2181,node7-2:2181,node7-3:2181")
// 读取文件(文件中是要写入Hbase中的数据)
val textFile: RDD[String] = sc.textFile("D://sparkData/info.txt")
val put: RDD[(ImmutableBytesWritable, Put)] = textFile.map {
case line => {
// 将文件中的数据按,切分
val datas = line.split(",")
// 行键的值
val rowkey = Bytes.toBytes(datas(0))
val put = new Put(rowkey)
// // 依次给列族cf的列添加值
put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("name"), Bytes.toBytes(datas(1)))
put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("price"), Bytes.toBytes(datas(2)))
// // 必须有这两个返回值,put为要传入的数据
(new ImmutableBytesWritable(rowkey), put)
}
}
val jobConf = new JobConf(conf)
jobConf.setOutputFormat(classOf[TableOutputFormat])
// 与HBase的fruit_spark表建立连接
jobConf.set(TableOutputFormat.OUTPUT_TABLE, "fruit_spark")
put.saveAsHadoopDataset(jobConf)
println("spark向hbase写入数据成功")
}
}