Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/xiaohei-info/spark_db_connector
Use Scala API to read/write data from different databases,HBase,MySQL,etc.
https://github.com/xiaohei-info/spark_db_connector
connector database hbase mysql scala spark-database
Last synced: 4 months ago
JSON representation
Use Scala API to read/write data from different databases,HBase,MySQL,etc.
- Host: GitHub
- URL: https://github.com/xiaohei-info/spark_db_connector
- Owner: xiaohei-info
- Created: 2017-03-23T04:23:13.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2018-02-28T07:02:37.000Z (almost 7 years ago)
- Last Synced: 2024-10-11T06:05:20.407Z (4 months ago)
- Topics: connector, database, hbase, mysql, scala, spark-database
- Language: Scala
- Homepage:
- Size: 208 KB
- Stars: 24
- Watchers: 4
- Forks: 8
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Spark Database Connector
## New Feature
- List写入HBase支持Kerberos认证
- 升级HBase Client API为1.2.0版本隐藏处理各种数据库的连接细节,使用Scala API在Spark中简易地处理数据库连接的读写操作。
相关测试环境信息:
- Scala 2.11.8/2.10.5
- Spark 1.6.0
- HBase 0.98.4
- Jdbc Driver 5.1.35目前支持的有:
- HBase
- MySQL添加Maven引用:
```xml
info.xiaohei.www
spark-database-connector_2.11
1.0.0```
Scala 2.10版本使用:
```xml
info.xiaohei.www
spark-database-connector_2.10
1.0.0```
## HBase
### 设置HBase host
通过以下三种任意方式设置HBase host地址
**1、在spark-submit中设置命令:**
```shell
spark-submit --conf spark.hbase.host=your-hbase-host
```**2、在Scala代码中设置:**
```scala
val sparkConf = new SparkConf()
sparkConf.set("spark.hbase.host", "your-hbase-host")
val sc = new SparkContext(sparkConf)
```**3、在JVM参数中设置:**
```shell
java -Dspark.hbase.host=your-hbase-host -jar ....
```**设置hbase-site.xml文件读取路径(可选)**
如果有读取hbase-site.xml文件的需求时,可以通过设置下面的选项进行指定:
```shell
spark.hbase.config=your-hbase-config-path
```设置该选项的方式同上
注意:需要将hbase-site.xml文件添加到当前项目可识别的resource路径中,否则将无法读取,使用默认配置### 向HBase写入数据
**导入隐式转换:**
```scala
import info.xiaohei.spark.connector.hbase._
```#### Spark RDD写入HBase
任何Spark RDD对象都能直接操作写入HBase,例如:
```scala
val rdd = sc.parallelize(1 to 100)
.map(i => (s"rowkey-${i.toString}", s"column1-${i.toString}", "column2"))
```这个RDD包含了100个三元组类型的数据,写入HBase时,第一个元素为rowkey,剩下的元素依次为各个列的值:
```scala
rdd.toHBase("mytable")
.insert("col1", "col2")
.inColumnFamily("columnFamily")
.save()
```(1)使用RDD的toHBase函数传入要写入的表名
(2)insert函数传入要插入的各个列名
(3)inColumnFamily函数传入这些列所在的列族名
(4)最后save函数将该RDD保存在HBase中如果col2和col1的列族不一样,可以在insert传入列名时单独指定:
```scala
rdd.toHBase("mytable")
.insert("col1", "otherColumnFamily:col2")
.inColumnFamily("defaultColumnFamily")
.save()
```列族名和列名之间要用冒号(:)隔开,其他列需要指定列名时使用的方式一致
#### Scala集合/序列写入HBase
```scala
val dataList = Seq[(String, String)](
("00001475304346643896037", "kgJkm0euSbe"),
("00001475376619355219953", "kiaR40qzI8o"),
("00001475458728618943637", "kgCoW0hgzXO"),
("00001475838363931738019", "kqiHu0WNJC0"))
//创建隐式变量
implicit val hbaseConf = HBaseConf.createConf("hbase-host")
//如果实在spark程序操作可以通过以下的方式
implicit val hbaseConf = HBaseConf.createFromSpark(sc)dataList.toHBase("mytable")
.insert("col1", "col2")
.inColumnFamily("columnFamily")
.save()
```使用方式和RDD写入HBase的操作类似,**注意,隐式变量不能在spark的foreachPartition等算子中定义**
以上的方式将使用HTable的put list批量将集合中的数据一次全部put到HBase中,如果写入HBase时想使用缓存区的方式,需要另外添加几个参数:
```scala
dataList.toHBase("mytable"
//该参数指定写入时的autoFlush为false
, Some(false, false)
//该参数指定写入缓冲区的大小
, Some(5 * 1024 * 1024))
.insert("col1", "col2")
.inColumnFamily("columnFamily")
.save()
```使用该方式时,集合中的每个数据都会被put一次,但是关闭了自动刷写,所以只有当缓冲区满了之后才会批量向HBase写入
#### 写入时为Rowkey添加salt前缀
```scala
rdd.toHBase("mytable")
.insert("col1", "otherColumnFamily:col2")
.inColumnFamily("defaultColumnFamily")
//添加salt
.withSalt(saltArray)
.save()
```saltArray是一个字符串数组,简单的例如0-9的字符串表示,由使用者自己定义
使用withSalt函数之后,在写入HBase时会为rowkey添加一个saltArray中的随机串,**注意:为了更好的支持HBase部分键扫描(rowkey左对齐),数组中的所有元素长度都应该相等**
取随机串的方式有两种:
* 1.计算当前的rowkey的hashCode的16进制表示并对saltArray的长度取余数,得到saltArray中的一个随机串作为salt前缀添加到rowkey
* 2.使用随机数生成器获得不超过saltArray长度的数字作为下标取数组中的值当前使用的是第一种方式
### 读取HBase数据
**导入隐式转换:**
```scala
import info.xiaohei.spark.connector.hbase._
```读取HBase的数据操作需要通过sc来进行:
```scala
val hbaseRdd = sc.fromHBase[(String, String, String)]("mytable")
.select("col1", "col2")
.inColumnFamily("columnFamily")
.withStartRow("startRow")
.withEndRow("endRow")
//当rowkey中有随机的salt前缀时,将salt数组传入即可自动解析
//得到的rowkey将会是原始的,不带salt前缀的
.withSalt(saltArray)
```(1)使用sc的fromHBase函数传入要读取数据的表名,该函数需要指定读取数据的类型信息
(2)select函数传入要读取的各个列名
(3)inColumnFamily函数传入这些列所在的列族名
(4)withStartRow和withEndRow将设置rowkey的扫描范围,可选操作
(5)之后就可以在hbaseRdd上执行Spark RDD的各种算子操作上面的例子中,fromHBase的泛型类型为三元组,但是select中只读取了两列值,因此,该三元组中第一个元素将是rowkey的值,其他元素按照列的顺序依次类推
当你不需要读取rowkey的值时,只需要将fromHBase的泛型类型改为二元组
即读取的列数为n,泛型类型为n元组时,列名和元组中的各个元素相对应
读取的列数为n,泛型类型为n+1元组时,元组的第一个元素为rowkey当各个列位于不同列族时,设置列族的方式同写入HBase一致
### SQL On HBase
借助SQLContext的DataFrame接口,在组件中可以轻易实现SQL On HBase的功能。
上例中的hbaseRdd是从HBase中读取出来的数据,在此RDD的基础上进行转换操作:
```scala
//创建org.apache.spark.sql.Row类型的RDD
val rowRdd = hbaseRdd.map(r => Row(r._1, r._2, r._3))
val sqlContext = new SQLContext(sc)
val df = sqlContext.createDataFrame(
rowRdd,
StructType(Array(StructField("col1", StringType), StructField("col2", StringType), StructField("col3", StringType)))
)
df.show()df.registerTempTable("mytable")
sqlContext.sql("select col1 from mytable").show()
```### 使用case class查询/读取HBase的数据
使用内置的隐式转换可以处理基本数据类型和元组数据,当有使用case class的需求时,需要额外做一些准备工作
定义如下的case class:
```scala
case class MyClass(name: String, age: Int)
```如果想达到以下的效果:
```scala
val classRdd = sc.fromHBase[MyClass]("tableName")
.select("name","age")
.inColumnFamily("info")classRdd.map{
c =>
(c.name,c.age)
}
```或者以下的效果:
```scala
//classRdd的类型为RDD[MyClass]
classRdd.toHBase("tableName")
.insert("name","age")
.inColumnFamily("info")
.save()
```需要另外实现能够解析自定义case class的隐式方法:
```scala
implicit def myReaderConversion: DataReader[MyClass] = new CustomDataReader[(String, Int), MyClass] {
override def convert(data: (String, Int)): MyClass = MyClass(data._1, data._2)
}implicit def myWriterConversion: DataWriter[MyClass] = new CustomDataWriter[MyClass, (String, Int)] {
override def convert(data: MyClass): (String, Int) = (data.name, data.age)
}
```该隐式方法返回一个DataReader/DataWriter 重写CustomDataReader/CustomDataWriter中的convert方法
将case class转换为一个元组或者将元组转化为case class即可## 带有Kerberos认证的HBase
除了上述过程中写HBase需要的配置外,还需要指定以下三个配置:
- spark.hbase.krb.principal:认证的principal用户名
- spark.hbase.krb.keytab:keytab文件路径(各个节点都存在且路径保持一致)
- spark.hbase.config:hbase-site.xml文件路径写入HBase时将会使用提供给的krb信息进行认证
当前仅支持无缝读取启用了Kerberos认证的HBase
写入时有一定限制,如要使用RDD的foreachPartition入库:```scala
rdd.foreachPartition{
data =>
data.toList.toHBase("table").insert("columns")//...
}
```**注意,foreachPartition中的toList操作将会把分区中的所有数据加载到内存中,如果数据量过大可能会造成OOM,增加Executor的内存即可**
TODO:RDD的读写接口目前还未实现Kerberos认证
## MySQL
除了可以将RDD/集合写入HBase之外,还可以在普通的程序中进行MySQL的相关操作
### 在conf中设置相关信息
**1、Spark程序中操作**
在SparkConf中设置以下的信息:
```scala
sparkConf
.set("spark.mysql.host", "your-host")
.set("spark.mysql.username", "your-username")
.set("spark.mysql.password", "your-passwd")
.set("spark.mysql.port", "db-port")
.set("spark.mysql.db", "database-name")//创建MySqlConf的隐式变量
implicit val mysqlConf = MysqlConf.createFromSpark(sc)
```关于这个隐式变量的说明:在RDD的foreachPartition或者mapPartitions等操作时,因为涉及到序列化的问题,默认的对MySqlConf的隐式转化操作会出现异常问题,所以需要显示的声明一下这个变量,其他不涉及网络序列化传输的操作可以省略这步
HBase小节中的设置属性的方法在这里也适用
**2、普通程序中操作**
创建MysqlConf,并设置相关属性:
```scala
//创建MySqlConf的隐式变量
implicit val mysqlConf = MysqlConf.createConf(
"your-host",
"username",
"password",
"port",
"db-name"
)```
在普通程序中操作时一定要显示声明MysqlConf这个隐式变量
### 写入MySQL
导入隐式转换:
```scala
import info.xiaohei.spark.connector.mysql._
```之后任何Iterable类型的数据都可以直接写入MySQL中:
```scala
list.toMysql("table-name")
//插入的列名
.insert("columns")
//where条件,如age=1
.where("where-conditions")
.save()
```### 在Spark程序中从MySQL读取数据
```scala
val res = sc.fromMysql[(Int,String,Int)]("table-name")
.select("id","name","age")
.where("where-conditions")
.get
```### 在普通程序中从MySQL读取数据
```scala
//普通程序读取关系型数据库入口
val dbEntry = new RelationalDbEntryval res = dbEntry.fromMysql[(Int,String,Int)]("table-name")
.select("id","name","age")
.where("where-conditions")
.get
```创建数据库入口之后的操作和spark中的流程一致
### case class解析
如果需要使用自定义的case class解析/写入MySQL,例如:
```scala
case class Model(id: Int, name: String, age: Int)
```基本流程和hbase小节中差不多,定义隐式转换:
```scala
implicit def myExecutorConversion: DataExecutor[Model] = new CustomDataExecutor[Model, (Int, String, Int)]() {
override def convert(data: Model): (Int, String, Int) = (data.id, data.name, data.age)
}implicit def myMapperConversion: DataMapper[Model] = new CustomDataMapper[(Int, String, Int), Model]() {
override def convert(data: (Int, String, Int)): Model = Model(data._1, data._2, data._3)
}
```之后可以直接使用:
```scala
val entry = new RelationalDbEntry
val res = entry.fromMysql[Model]("test")
.select("id", "name", "age")
.get
res.foreach(x => println(s"id:${x.id},name:${x.name},age:${x.age}"))
```