java、spark操作kudu

5、java 操作kudu

5.1、 构建 maven 工程、导入依赖

<dependencies> 
<dependency>
	<groupId>org.apache.kudu</groupId>
	<artifactId>kudu-client</artifactId>
	<version>1.9.0</version>
</dependency>
<!--导入kudu的客户端工具-->
<dependency>
    <groupId>org.apache.kudu</groupId>
    <artifactId>kudu-client-tools</artifactId>
    <version>${kudu.version}</version>
</dependency>

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
</dependency>
</dependencies>

5.2、 初始化方法

public class TestKudu {
 //声明全局变量 KuduClient 后期通过它来操作 kudu 表
 private KuduClient kuduClient;
 //指定 kuduMaster 地址
 private String kuduMaster;
 //指定表名
 private String tableName;
 @Before
 public void init(){
 //初始化操作
 kuduMaster="node1:7051,node2:7051,node3:7051";
 //指定表名
 tableName="student";
 KuduClient.KuduClientBuilder kuduClientBuilder = new KuduClient.KuduClientBuilder(kuduMaster);
 kuduClientBuilder.defaultSocketReadTimeoutMs(10000);
 kuduClient=kuduClientBuilder.build();
 }

5.3、创建表

 /**
 \* 创建表
 */

 @Test
 public void createTable() throws KuduException {
     
     //1、创建一个client
    //KuduClient client = new KuduClientBuilder(KUDU_MASTER).build();

 //判断表是否存在,不存在就构建
 if(!kuduClient.tableExists(tableName)){
 //构建创建表的 schema 信息-----就是表的字段和类型
 ArrayList<ColumnSchema> columnSchemas = new ArrayList<ColumnSchema>();
 columnSchemas.add(new ColumnSchema.ColumnSchemaBuilder("id", Type.INT32).key(true).nullable(false).build());
 columnSchemas.add(new ColumnSchema.ColumnSchemaBuilder("name", Type.STRING).build());
 columnSchemas.add(new ColumnSchema.ColumnSchemaBuilder("age", Type.INT32).build());
 columnSchemas.add(new ColumnSchema.ColumnSchemaBuilder("sex", Type.INT32).build());
 Schema schema = new Schema(columnSchemas);

 //指定创建表的相关属性
 CreateTableOptions options = new CreateTableOptions();
 ArrayList<String> partitionList = new ArrayList<String>();

 //指定 kudu 表的分区字段是什么
 partitionList.add("id"); 
 // 按照 id.hashcode % 分区数 = 分区号
 options.addHashPartitions(partitionList,6);
 //4、指定分区方式为hash分区、6个分区,一个副本
 options.setNumReplicas(1);
 kuduClient.createTable(tableName,schema,options);
 }
 }

5.4、插入数据

 /**
 * 向表加载数据
 */
 @Test
 public void insertTable() throws KuduException {
 	//向表加载数据需要一个 kuduSession 对象
 	KuduSession kuduSession = kuduClient.newSession();
	kuduSession.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC);
 	//需要使用 kuduTable 来构建 Operation 的子类实例对象
 	KuduTable kuduTable = kuduClient.openTable(tableName);
 
     for(int i=1;i<=10;i++){
 		Insert insert = kuduTable.newInsert();
 		PartialRow row = insert.getRow();
 		row.addInt("id",i);
 		row.addString("name","zhangsan-"+i);
 		row.addInt("age",20+i);
 		row.addInt("sex",i%2);
 	
         kuduSession.apply(insert);//最后实现执行数据的加载操作
 	}
    session.close();
	client.close();
 }

5.5、查询数据

 /* 查询表的数据结果*/
 @Test
 public void queryData() throws KuduException {
 //构建一个查询的扫描器
 	KuduScanner.KuduScannerBuilder kuduScannerBuilder = kuduClient.newScannerBuilder(kuduClient.openTable(tableName));
	ArrayList<String> columnsList = new ArrayList<String>();
 	columnsList.add("id");
 	columnsList.add("name");
 	columnsList.add("age");
 	columnsList.add("sex");
 	kuduScannerBuilder.setProjectedColumnNames(columnsList);
 	//返回结果集
 	KuduScanner kuduScanner = kuduScannerBuilder.build();
 	//遍历
 	while (kuduScanner.hasMoreRows()){
 		RowResultIterator rowResults = kuduScanner.nextRows();
 		while (rowResults.hasNext()){
 			RowResult row = rowResults.next();
 			int id = row.getInt("id");
 			String name = row.getString("name");
 			int age = row.getInt("age");
 			int sex = row.getInt("sex");
 			System.out.println("id="+id+" name="+name+" age="+age+" sex="+sex);
 		}
 	}
    //7、关闭client
    client.close();
 }

5.6、 修改数据

/**
 * 修改表的数据
 */
 @Test
 public void updateData() throws KuduException {
 	//修改表的数据需要一个 kuduSession 对象
 	KuduSession kuduSession = kuduClient.newSession();
 	kuduSession.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC);
     //需要使用 kuduTable 来构建 Operation 的子类实例对象
 	KuduTable kuduTable = kuduClient.openTable(tableName);
 	//Update update = kuduTable.newUpdate();
 	Upsert upsert = kuduTable.newUpsert(); //如果 id 存在就表示修改,不存在就新增
 	PartialRow row = upsert.getRow();
 	row.addInt("id",100);
 	row.addString("name","zhangsan-100");
 	row.addInt("age",100);
 	row.addInt("sex",0);
 	kuduSession.apply(upsert);//最后实现执行数据的修改操作
    kuduSession.flush();
    kuduSession.close();

    client.close();
 }

5.7、 删除数据或表

/* 删除数据
 */
 @Test
 public void deleteData() throws KuduException {
 //删除表的数据需要一个 kuduSession 对象
 	KuduSession kuduSession = kuduClient.newSession();
 	kuduSession.setFlushMode(SessionConfiguration.FlushMode.AUTO_FLUSH_SYNC);
 //需要使用 kuduTable 来构建 Operation 的子类实例对象
 	KuduTable kuduTable = kuduClient.openTable(tableName);
 	Delete delete = kuduTable.newDelete();
 	PartialRow row = delete.getRow();
 	row.addInt("id",100);
     
 	kuduSession.apply(delete);//最后实现执行数据的删除操作
     kuduSession.flush();
     kuduSession.close();
 }
/* 删除表 */
 @Test
 public void dropTable() throws KuduException {
 	if(kuduClient.tableExists(tableName)){
 		kuduClient.deleteTable(tableName);
 	}
 }

5.8、kudu分区操作

/**
 * 测试分区:
 * 多级分区
 * Multilevel Partition
 * 混合使用 hash 分区和 range 分区
 *
 * 哈希分区有利于提高写入数据的吞吐量,而范围分区可以避免
tablet 无限增长问题,
 * hash 分区和 range 分区结合,可以极大的提升 kudu 的性能
 */
 @Test
public void testMultilevelPartition() throws KuduException 
{
 //设置表的 schema
 LinkedList<ColumnSchema> columnSchemas = new LinkedList<ColumnSchema>();
 columnSchemas.add(newColumn("CompanyId", Type.INT32,true));
 columnSchemas.add(newColumn("WorkId", Type.INT32,false));
 columnSchemas.add(newColumn("Name", Type.STRING,false));
 columnSchemas.add(newColumn("Gender", Type.STRING,false));
 columnSchemas.add(newColumn("Photo", Type.STRING,false));
 //创建 schema
 Schema schema = new Schema(columnSchemas);
 //创建表时提供的所有选项
 CreateTableOptions tableOptions = new CreateTableOptions();
 //设置副本数
 tableOptions.setNumReplicas(1);
 //设置范围分区的规则
 LinkedList<String> parcols = new LinkedList<String>();
 parcols.add("CompanyId");
 //hash 分区
 tableOptions.addHashPartitions(parcols,5);
 //range 分区
 int count=0;
 for(int i=0;i<10;i++){
 	PartialRow lower = schema.newPartialRow();
 	lower.addInt("CompanyId",count);
 	count+=10;
 	PartialRow upper = schema.newPartialRow();
 	upper.addInt("CompanyId",count);
 	tableOptions.addRangePartition(lower,upper);
 }
 try {
 	kuduClient.createTable("cat",schema,tableOptions);
 } catch (KuduException e) {
 	e.printStackTrace();
 }
 	kuduClient.close();
}

6、spark 操作kudu

KuduContext。这是可在 Spark 应用程序中广播的主要可序列化对象。此类代表在 Spark 执行程序中与 Kudu Java 客户端进行交互。 KuduContext 提供执行DDL 操作所需的方法,与本机 Kudu RDD 的接口,对数据执行更新/插入/删除,将数据类型从 Kudu 转换为 Spark 等。

6.1、 引入依赖

<repositories>
 <repository>
 <id>cloudera</id>
 <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
 </repository>
</repositories> 
<dependencies>
<dependency>
 	<groupId>org.apache.kudu</groupId>
 	<artifactId>kudu-client-tools</artifactId>
 	<version>1.9.0-cdh6.2.0</version>
 </dependency>
 <dependency>
 <groupId>org.apache.kudu</groupId>
 <artifactId>kudu-client</artifactId>
 <version>1.9.0-cdh6.2.0</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.apache.kudu/kudu-spark2 -->
 <dependency>
 	<groupId>org.apache.kudu</groupId>
 	<artifactId>kudu-spark2_2.11</artifactId>
     <version>1.9.0-cdh6.2.0</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql -->
 <dependency>
 	<groupId>org.apache.spark</groupId>
 	<artifactId>spark-sql_2.11</artifactId>
 	<version>2.1.0</version>
 </dependency>
</dependencies>

6.2、创建表

定义 kudu 的表需要分成 5 个步骤:

1:提供表名

2:提供 schema

3:提供主键

4:定义重要选项;例如:定义分区的 schema

5:调用 create Table api

object SparkKuduTest {
 def main(args: Array[String]): Unit = {
 //构建 sparkConf 对象
 val sparkConf: SparkConf = new SparkConf().setAppName("SparkKuduTest").setMaster("local[2]")
 //构建 SparkSession 对象
 val sparkSession: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
 //获取 sparkContext 对象
 val sc: SparkContext = sparkSession.sparkContext
  sc.setLogLevel("warn")
 
 //构建 KuduContext 对象
 val kuduContext = new KuduContext("node1:7051,node2:7051,node3:7051",sc)
 //1.创建表操作
 createTable(kuduContext)
 
 /**
 * 创建表
 * @param kuduContext
 * @return
 */
 private def createTable(kuduContext: KuduContext) = {
 //1.1 定义表名
 val tableName = "spark_kudu"
 //1.2 定义表的 schema
 val schema = StructType(
 	StructField("userId", StringType, false) ::
 	StructField("name", StringType, false) ::
 	StructField("age", IntegerType, false) ::
 	StructField("sex", StringType, false) :: Nil)
 //1.3 定义表的主键
 val primaryKey = Seq("userId")
 //1.4 定义分区的 schema
 val options = new CreateTableOptions
 //设置分区
 options.setRangePartitionColumns(List("userId").asJava)
 //设置副本
 options.setNumReplicas(1)
 //1.5 创建表
 if(!kuduContext.tableExists(tableName)){
 	kuduContext.createTable(tableName, schema, primaryKey, options)
 }
 } 
}

定义表时要注意的是 Kudu 表选项值。你会注意到在指定组成范围分区列的列名列表时我们调用“asJava”方法。这是因为在这里,我们调用了 Kudu Java 客户端本身,它需要 Java 对象(即 java.util.List)而不是 Scala 的List 对象;(要使“asJava”方法可用,请记住导入 JavaConverters 库。)

创建表后,通过将浏览器指向 http// master 主机名:8051/tables 来查看Kudu 主 UI 可以找到创建的表,通过单击表 ID,能够看到表模式和分区信息。

6.3、dataFrame操作kudu

Kudu 支持许多 DML 类型的操作,其中一些操作包含在 Spark on Kudu 集成.

包括:

INSERT - 将DataFrame的行插入Kudu表。请注意,虽然API完全支持INSERT,但不鼓励在 Spark 中使用它。使用 INSERT 是有风险的,因为 Spark 任务可能需要重新执行,这意味着可能要求再次插入已插入的行。这样做会导致失败,因为如果行已经存在,INSERT 将不允许插入行(导致失败)。

相反,我们鼓励使用下面描述的 INSERT_IGNORE。

INSERT-IGNORE - 将 DataFrame 的行插入 Kudu 表。如果表存在,则忽略插入动作。

DELETE - 从 Kudu 表中删除 DataFrame 中的行

UPSERT - 如果存在,则在 Kudu 表中更新 DataFrame 中的行,否则执行插入操作。

UPDATE - 更新 dataframe 中的行

6.3.1、 插入数据insert 操作

先创建一张表,然后把数据插入到表中。

case class People(id:Int,name:String,age:Int)
object DataFrameKudu {
 def main(args: Array[String]): Unit = {
 	//构建 SparkConf 对象
 	val sparkConf: SparkConf = new SparkConf().setAppName("DataFrameKudu").setMaster("local[2]")
 	//构建 SparkSession 对象
 	val sparkSession: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
 	//获取 SparkContext 对象
 	val sc: SparkContext = sparkSession.sparkContext
 	sc.setLogLevel("warn")
 	//指定 kudu 的 master 地址
 	val kuduMaster="node1:7051,node2:7051,node3:7051"
 	//构建 KuduContext 对象
 	val kuduContext = new KuduContext(kuduMaster,sc)
 
 	//定义表名
 	val tableName="people"
 	//1、创建表
 	createTable(kuduContext, tableName)
 
 	//2、插入数据到表中
 	insertData2table(sparkSession,sc, kuduContext, tableName)
 
 }
 
 /**
 * 创建表
 * @param kuduContext
 * @param tableName
 */
 private def createTable(kuduContext: KuduContext, tableName: String): Unit = {
 //定义表的 schema
 	val schema = StructType(
 		StructField("id", IntegerType, false) ::
 		StructField("name", StringType, false) ::
 		StructField("age", IntegerType, false) :: Nil
 	)
 	//定义表的主键
 	val tablePrimaryKey = List("id")
 	//定义表的选项配置
 	val options = new CreateTableOptions
 	options.setRangePartitionColumns(List("id").asJava)
 	options.setNumReplicas(1)
 	//创建表
 	if (!kuduContext.tableExists(tableName)) {
 		kuduContext.createTable(tableName, schema, tablePrimaryKey, options)
 	}
 }
    
 /**
 * 插入数据到表中
 * @param sparkSession
 * @param sc
 * @param kuduContext
 * @param tableName
 */
 private def insertData2table(sparkSession:SparkSession,sc: SparkContext, kuduContext: KuduContext, tableName: String): Unit = {
 	//准备数据
 	val data = List(People(1, "zhangsan", 20), People(2, "lisi", 30), People(3, "wangwu", 40))
 	val peopleRDD: RDD[People] = sc.parallelize(data)
 	import sparkSession.implicits._
 	val peopleDF: DataFrame = peopleRDD.toDF
 	kuduContext.insertRows(peopleDF, tableName)
 }
 
}
6.3.2. 删除数据 delete 操作
/**
 * 删除表的数据
 * @param sparkSession
 * @param sc
 * @param kuduMaster
 * @param kuduContext
 * @param tableName
 */
 private def deleteData(sparkSession: SparkSession, sc: SparkContext, kuduMaster: String, kuduContext: KuduContext, tableName: String): Unit = {
 //定义一个 map 集合,封装 kudu 的相关信息
     val options = Map(
 		"kudu.master" -> kuduMaster,
 		"kudu.table" -> tableName
 	)
 	import sparkSession.implicits._
 	val data = List(People(1, "zhangsan", 20), People(2, "lisi", 30), People(3, "wangwu", 40))
 	val dataFrame: DataFrame = sc.parallelize(data).toDF
 	dataFrame.createTempView("temp")
 	//获取年龄大于 30 的所有用户 id
 	val result: DataFrame = sparkSession.sql("select id from temp where age >30")
 	//删除对应的数据,这里必须要是主键字段
 	kuduContext.deleteRows(result, tableName)
 }
6.3.3. 更新数据 upsert 操作
/**
 * 更新数据--添加数据
 *
 * @param sc
 * @param kuduMaster
 * @param kuduContext
 * @param tableName
 */
 private def UpsertData(sparkSession: SparkSession,sc: SparkContext, kuduMaster: String, kuduContext: KuduContext, tableName: String): Unit = {
 	//更新表中的数据
 	//定义一个 map 集合,封装 kudu 的相关信息
 val options = Map(
 	"kudu.master" -> kuduMaster,
 	"kudu.table" -> tableName
 )
 import sparkSession.implicits._
 val data = List(People(1, "zhangsan", 50), People(5, "tom", 30))
 val dataFrame: DataFrame = sc.parallelize(data).toDF
 //如果存在就是更新,否则就是插入
 kuduContext.upsertRows(dataFrame, tableName)
}
6.3.4、 更新数据 update 操作
 /**
 * 更新数据
 * @param sparkSession
 * @param sc
 * @param kuduMaster
 * @param kuduContext
 * @param tableName
 */
 private def updateData(sparkSession: SparkSession,sc: SparkContext, kuduMaster: String, kuduContext: KuduContext, tableName: String): Unit = {
 //定义一个 map 集合,封装 kudu 的相关信息
 	val options = Map(
 		"kudu.master" -> kuduMaster,
 		"kudu.table" -> tableName
 	)
 	import sparkSession.implicits._
 	val data = List(People(1, "zhangsan", 60), People(6, "tom", 30))
 	val dataFrame: DataFrame = sc.parallelize(data).toDF
 	//如果存在就是更新,否则就是报错
 	kuduContext.updateRows(dataFrame, tableName)
 }
6.3.5. DataFrame API 读取 kudu 数据

虽然我们可以通过上面显示的 KuduContext 执行大量操作,但我们还可以直接从默认数据源本身调用读/写 API。要设置读取,我们需要为 Kudu 表指定选项,命名我们要读取的表以及为表提供服务的 Kudu 集群的 Kudu 主服务器列表。

 /**
 * 使用 DataFrameApi 读取 kudu 表中的数据
 * @param sparkSession
  * @param kuduMaster
 * @param tableName
 */
 private def getTableData(sparkSession: SparkSession, kuduMaster: String, tableName: String): Unit = {
 	//定义 map 集合,封装 kudu 的 master 地址和要读取的表名
 	val options = Map(
 	"kudu.master" -> kuduMaster,
 	"kudu.table" -> tableName
 	)
 	sparkSession.read.options(options).kudu.show()
 }
6.3.6、DataFrameApi 写数据到 kudu 表

在通过 DataFrame API 编写时,目前只支持一种模式“append”。尚未实现的“覆盖”模式。

/**
 * DataFrame api 写数据到 kudu 表
 * @param sparkSession
 * @param sc
 * @param kuduMaster
 * @param tableName
 */
 private def dataFrame2kudu(sparkSession: SparkSession, sc: SparkContext, kuduMaster: String, tableName: String): Unit = {
//定义 map 集合,封装 kudu 的 master 地址和要读取的表名
 val options = Map(
 "kudu.master" -> kuduMaster,
 "kudu.table" -> tableName
 )
 val data = List(People(7, "jim", 30), People(8, "xiaoming", 40))
 import sparkSession.implicits._
 val dataFrame: DataFrame = sc.parallelize(data).toDF
 //把 dataFrame 结果写入到 kudu 表中 ,目前只支持 append 追加
 dataFrame.write.options(options).mode("append").kudu
 //查看结果
 //导包
 import org.apache.kudu.spark.kudu._
 //加载表的数据,导包调用 kudu 方法,转换为 dataFrame,最后在使用 show 方法显示结果
 sparkSession.read.options(options).kudu.show()
 }
6.3.7、 使用 sparksql 操作 kudu

可以选择使用 Spark SQL 直接使用 INSERT 语句写入 Kudu 表;与’append’类似,INSERT 语句实际上将默认使用 UPSERT 语义处理.

/**
 * 使用 sparksql 操作 kudu 表
 * @param sparkSession
 * @param sc
 * @param kuduMaster
 * @param tableName
 */
 private def SparkSql2Kudu(sparkSession: SparkSession, sc: SparkContext, kuduMaster: String, tableName: String): Unit = {
 //定义 map 集合,封装 kudu 的 master 地址和表名
 val options = Map(
 "kudu.master" -> kuduMaster,
 "kudu.table" -> tableName
 )
 val data = List(People(10, "小张", 30), People(11, "小王", 40))
 import sparkSession.implicits._
 val dataFrame: DataFrame = sc.parallelize(data).toDF
     //把 dataFrame 注册成一张表
 dataFrame.createTempView("temp1")
 //获取 kudu 表中的数据,然后注册成一张表
 sparkSession.read.options(options).kudu.createTempView("temp2")
 //使用 sparkSQL 的 insert 操作插入数据
 sparkSession.sql("insert into table temp2 select * from temp1")
 sparkSession.sql("select * from temp2 where age >30").show()
}

6.4、 kudu native RDD

Spark 与 Kudu 的集成同时提供了 kudu RDD.

//使用 kuduContext 对象调用 kuduRDD 方法,需要 sparkContext 对象,表名,想要的字段名称
 val kuduRDD: RDD[Row] = kuduContext.kuduRDD(sc,tableName,Seq("name","age"))
 //操作该 rdd 打印输出
 val result: RDD[(String, Int)] = kuduRDD.map {
 	case Row(name: String, age: Int) => (name, age)
 }
 result.foreach(println)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值