Iceberg(二)对接Spark

1、配置参数和jar包

        1、将构建好的Iceberg的spark模块jar包,复制到spark jars下

cp /opt/module/iceberg-apache-iceberg-0.11.1/spark3-extensions/build/libs/* /opt/module/spark-3.0.1-bin-hadoop2.7/jars/
cp /opt/module/iceberg-apache-iceberg-0.11.1/spark3-runtime/build/libs/* /opt/module/spark-3.0.1-bin-hadoop2.7/jars/ 

        2、配置spark参数,配置Spark Sql Catlog,可以用两种方式,基于hive和基于hadoop,这里先选择基于hadoop。修改spark conf目录下的spark-default.conf

spark.sql.catalog.hive_prod = org.apache.iceberg.spark.SparkCatalog 
spark.sql.catalog.hive_prod.type = hive 
spark.sql.catalog.hive_prod.uri = thrift://hadoop1:9083

spark.sql.catalog.hadoop_prod = org.apache.iceberg.spark.SparkCatalog 
spark.sql.catalog.hadoop_prod.type = hadoop
spark.sql.catalog.hadoop_prod.warehouse = hdfs://mycluster/spark/warehouse

spark.sql.catalog.catalog-name.type = hadoop 
spark.sql.catalog.catalog-name.default-namespace = db 
spark.sql.catalog.catalog-name.uri = thrift://hadoop1:9083
spark.sql.catalog.catalog-name.warehouse= hdfs://mycluster/spark/warehouse

2、Spark sql操作

2.1、建表

        使用spark sql创建iceberg表,配置完毕后,会多出一个hadoop_prod.db数据库,但是注意这个数据库通过show tables是看不到的。可以直接通过use hadoop_prod;没有报错则表示配置成功。然后可以create database db;并通过以下命令建表:

use hadoop_prod.db; 
create table testA(
    id bigint, 
    name string, 
    age int,
    dt string) 
USING iceberg
PARTITIONED by(dt);

        当向表中执行插入语句,可以直接通过select查询数据

insert into testA values(1,'张三',18,'2021-06-21');

select * from hadoop_prod.db.testA;

2.2、overwrite操作

        当向表中执行overwrite覆盖操作是,与 hive 一样,会将原始数据重新刷新。注意覆盖的不是整张表的数据,只是这个分区的数据。被覆盖的数据不会被物理删除,还会存在HDFS上,除非是对表进行drop操作。

2.3、动态覆盖

        1、Spark的默认覆盖模式是静态的,但在写入iceberg时建议使用动态覆盖模式(执行insert overwrite不用指定分区列。会将A表的数据全部覆盖B表的数据)。静态覆盖模式需要制定分区列,动态覆盖模式不需要。
        2、设置动态覆盖模式,修改 spark-default.conf,添加对应参数

spark.sql.sources.partitionOverwriteMode=dynamic

2.4、静态覆盖

        类似于每天的跑批,跟 hive 插入时手动指定分区一致,需要手动指定分区列的值。会将A表中的所有数据复制到B表的指定分区中。

insert overwrite testB Partition(dt='2021-06-26') select id,name,age from testA;

2.5、删除数据

        iceberg并不会物理删除数据,当执行删除操作,提示删除成功,再次查询数据。发现表中已无数据,但是存在hdfs上的物理并没有实际删除。

2.6、历史快照

        每张表都拥有一张历史表,历史表的表名为当前表加上后缀.history,注意:查询历史表的时候必须是表的全称,不可用先切到 hadoop.db 库中再查 testB。

select * from hadoop_prod.db.testB.history;

        可以查看到每次操作后的对应的快照记录,也可以查询对应快照表,快照表的表名在原表基础上加上.snapshots,也是一样必须是表的全称不能简写。

select * from hadoop_prod.db.testB.snapshots;

        可以在看到commit的时间,snapshot快照的id,parent_id父节点,operation操作类型,已经summary概要,summary概要字段中可以看到数据量大小,总条数,路径等信息。两张表也可以根据snapshot_id快照id进行join关联查询。

select	*from hadoop_prod.db.testB.history a join hadoop_prod.db.testB.snapshots b on a.snapshot_id=b.snapshot_id ;

        知道了快照表和快照信息后,可以根据快照 id 来查询具体的历史信息,发进行检测是否误操作,如果是误操作则可通过 spark 重新刷新数据。查询方式如下

spark.read.option("snapshot-id","5549650043576786799").format("iceberg").load("/hive/w arehouse/db/testB").show

2.7、隐藏分区(有 bug 时区不对)

        隐藏分区支持的函数有 years,months,days,hours,bucket,truncate。比如接下来创建一张 testC 表,表中有id,name 和 ts时间戳。

create table hadoop_prod.db.testC(
	id bigint, 
	name string, 
	ts timestamp
) 
using iceberg
partitioned by (days(ts));

insert into hadoop_prod.db.testC values(1,'张三',cast(1624773600 as timestamp)),(2,'李四',cast(1624860000 as timestamp));

        发现时区不对(比方说13点的数据,会放在5点的分区),修改 /opt/module/spark-3.0.1-bin-hadoop2.7/conf/spark-defaults.conf中的spark.sql.session.timeZone=GMT+8无效。

2.8、bucket 函数(有 bug)

        创建testD表使用bucket函数。分桶hash算法采用Murmur3 hash,官网介绍https://iceberg.apache.org/spec/#partition-transforms。

create table hadoop_prod.db.testD(
	id bigint, 
	name string, 
	ts timestamp
) using iceberg
partitioned by (bucket(16,id));

        当向同一分区中写入多条数据,会报错,并发写的问题。

2.9、truncate函数

        截取长度作为分区。(待续)

create table hadoop_prod.db.testE(
	id bigint, 
	name string, 
	ts timestamp) 
using iceberg
partitioned by (truncate(4,id));

3、DataFrame操作

        引入依赖:

<properties>
    <spark.version>3.0.1</spark.version>
    <scala.version>2.12.10</scala.version>
    <log4j.version>1.2.17</log4j.version>
    <slf4j.version>1.7.22</slf4j.version>
    <iceberg.version>0.11.1</iceberg.version>
</properties>

<dependency>
    <groupId>org.apache.iceberg</groupId>
    <artifactId>iceberg-spark3-runtime</artifactId>
    <version>${iceberg.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.iceberg</groupId>
    <artifactId>iceberg-spark3-extensions</artifactId>
    <version>${iceberg.version}</version>
</dependency>

3.1、Spark DF读取iceberg的三种方式

 def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf()
      .set("spark.sql.catalog.hadoop_prod", "org.apache.iceberg.spark.SparkCatalog")
      .set("spark.sql.catalog.hadoop_prod.type", "hadoop")
      .set("spark.sql.catalog.hadoop_prod.warehouse", "hdfs://mycluster/hive/warehouse")
      .set("spark.sql.catalog.catalog-name.type", "hadoop")
      .set("spark.sql.catalog.catalog-name.default-namespace", "default")
      .set("spark.sql.sources.partitionOverwriteMode", "dynamic")
      .set("spark.sql.session.timeZone", "GMT+8")
      .setMaster("local[*]").setAppName("table_operations")
    val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
    readTale(sparkSession)

  }
  /**
   *	读取iceberg 的表
   *	@param sparkSession
   */
  def readTale(sparkSession: SparkSession) = {
    //三种方式
    sparkSession.table("hadoop_prod.db.testA").show()
    sparkSession.read.format("iceberg").load("hadoop_prod.db.testA").show()
    sparkSession.read.format("iceberg").load("/hive/warehouse/db/testA").show()// 路径到表就行,不要到具体文件
  }

3.2、Spark DF读取Iceberg快照表的两种方式

def readSnapShots(sparkSession: SparkSession) = {
  //根据查询 hadoop_prod.db.testA.snapshots 快照表可以知道快照时间和快照id
  //根据时间戳读取,必须是时间戳 不能使用格式化后的时间
  sparkSession.read
    .option("as-of-timestamp", "1624961454000") //毫秒时间戳,查询比该值时间更早的快照
    .format("iceberg")
    .load("hadoop_prod.db.testA").show()

  //根据快照 id 查询
  sparkSession.read
    .option("snapshot-id", "9054909815461789342")
    .format("iceberg")
    .load("hadoop_prod.db.testA").show()
}

3.3、写入数据并自动创建表

def writeAndCreateTable(sparkSession: SparkSession) = {
  import sparkSession.implicits._
  import org.apache.spark.sql.functions._
  val data = sparkSession.createDataset[Student](Array(Student(1001, " 张 三 ", 18, "2021-06-28"),
    Student(1002, "李四", 19, "2021-06-29"), Student(1003, "王五", 20, "2021-06-29")))
  data.writeTo("hadoop_prod.db.test1").partitionedBy(col("dt")) //指定dt为分区列
    .create()
}

case class Student(id: Int, name: String, age: Int, dt: String)

3.4、写数据

3.4.1、Append

def AppendTable(sparkSession: SparkSession) = {
  //两种方式
  import sparkSession.implicits._
  val data = sparkSession.createDataset(Array(Student(1003, "王五", 11, "2021-06-29"), Student(1004, "赵六", 10, "2021-06-30")))
  data.writeTo("hadoop_prod.db.test1").append()  // 使用DataFrameWriterV2 API  spark3.0
  data.write.format("iceberg").mode("append").save("hadoop_prod.db.test1")	//使用DataFrameWriterV1 API spark2.4
}

3.4.2、Overwrite

        1、动态覆盖,只会刷新所属分区数据

// 动态覆盖,只会刷新所属分区数据
def OverWriteTable(sparkSession: SparkSession) = {
  import sparkSession.implicits._
  val data = sparkSession.createDataset(Array(Student(1003, " 王五", 11, "2021-06-29"),
    Student(1004, "赵六", 10, "2021-06-30")))
  data.writeTo("hadoop_prod.db.test1").overwritePartitions()
}

        2、静态覆盖,手动指定分区

def OverWriteTable2(sparkSession: SparkSession) = {
  import sparkSession.implicits._
  val data = sparkSession.createDataset(Array(Student(1, "s1", 1, "111"), Student(2, "s2", 2, "111")))
  data.writeTo("hadoop_prod.db.test1").overwrite($"dt" === "2021-06-30")
}

        注意:输出表不能存在多个分区。不然会报错

4、存在的问题和缺点

4.1、问题

  1. 时区无法设置,见2.7。
  2. Spark Sql客户端打开,缓存无法更新,修改表数据后,得需要关了黑窗口再重新打开,查询才是更新后的数据。
  3. 表分区如果指定多个分区或分桶,那么插入批量数据时,如果这一批数据有多条数据在统一分区内,会报错

4.2、缺点

  1. 与hudi相比,没有解决小文件问题
  2. 与hudi相比,缺少行级更新,只能对标的数据按照分区进行overwrite全量覆盖

        

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值