Doris-05-集成Spark、Flink、Datax,以及数据湖分析(JDBC、ODBC、ES、Hive、多源数据目录Catalog)

集成其他系统

准备表和数据:

CREATE TABLE table1
(
    siteid INT DEFAULT '10',
    citycode SMALLINT,
    username VARCHAR(32) DEFAULT '',
    pv BIGINT SUM DEFAULT '0'
)
AGGREGATE KEY(siteid, citycode, username)
DISTRIBUTED BY HASH(siteid) BUCKETS 10
PROPERTIES("replication_num" = "1");
insert into table1 values
(1,1,'jim',2),
(2,1,'grace',2),
(3,2,'tom',2),
(4,3,'bush',3),
(5,3,'helen',3);

Spark 读写 Doris

Spark Doris Connector 可以支持通过 Spark 读取 Doris 中存储的数据,也支持通过Spark写入数据到Doris。

代码库地址:https://github.com/apache/incubator-doris-spark-connector

  • 支持从Doris中读取数据
  • 支持Spark DataFrame批量/流式 写入Doris
  • 可以将Doris表映射为DataFrame或者RDD,推荐使用DataFrame
  • 支持在Doris端完成数据过滤,减少数据传输量。
Connector Spark Doris Java Scala
2.3.4-2.11.xx 2.x 0.12+ 8 2.11
3.1.2-2.12.xx 3.x 0.12.+ 8 2.12
3.2.0-2.12.xx 3.2.x 0.12.+ 8 2.12

准备 Spark 环境

创建 maven 工程,编写 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atguigu.doris</groupId>
    <artifactId>spark-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <scala.binary.version>2.12</scala.binary.version>
        <spark.version>3.0.0</spark.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- Spark 的依赖引入 -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_${scala.binary.version}</artifactId>
            <scope>provided</scope>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_${scala.binary.version}</artifactId>
            <scope>provided</scope>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-hive_${scala.binary.version}</artifactId>
            <scope>provided</scope>
            <version>${spark.version}</version>
        </dependency>
        <!-- 引入 Scala -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.12.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!--spark-doris-connector-->
        <dependency>
            <groupId>org.apache.doris</groupId>
            <artifactId>spark-doris-connector-3.1_2.12</artifactId>
            <!--<artifactId>spark-doris-connector-
2.3_2.11</artifactId>-->
            <version>1.0.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--编译 scala 所需插件-->
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.1</version>
                <executions>
                    <execution>
                        <id>compile-scala</id>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile-scala</id>
                        <goals>
                            <goal>add-source</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <!-- 声明绑定到 maven 的 compile 阶段 -->
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- assembly 打包插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <archive>
                        <manifest>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.6.1</version>
 &lt;!&ndash; 所有的编译都依照 JDK1.8 &ndash;&gt;
 <configuration>
 <source>1.8</source>
 <target>1.8</target>
 </configuration>
 </plugin>-->
        </plugins>
    </build>
</project>

使用 Spark Doris Connector

Spark Doris Connector 可以支持通过 Spark 读取 Doris 中存储的数据,也支持通过Spark 写入数据到 Doris。

(1)SQL 方式读写数据

import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
/**
 * TODO
 *
 * @version 1.0
 * @author cjp
 */
object SQLDemo {
    
    
    def main( args: Array[String] ): Unit = {
    
    
        val sparkConf = new SparkConf().setAppName("SQLDemo")
        .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉
        val sparkSession = 
        SparkSession.builder().config(sparkConf).getOrCreate()
        sparkSession.sql(
            """
            |CREATE TEMPORARY VIEW spark_doris
            |USING doris
            |OPTIONS(
                | "table.identifier"="test_db.table1",
                | "fenodes"="hadoop1:8030",
                | "user"="test",
                | "password"="test"
                |);
            """.stripMargin)
        //读取数据
        // sparkSession.sql("select * from spark_doris").show()
        //写入数据
        sparkSession.sql("insert into spark_doris 
                             values(99,99,'haha',5)")
    } 
}

(2)DataFrame 方式读写数据(batch)

import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
/**
 * TODO
 *
 * @version 1.0
 * @author cjp
 */
object DataFrameDemo {
    
    
    def main( args: Array[String] ): Unit = {
    
    
        val sparkConf = new SparkConf().setAppName("DataFrameDemo")
        .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉
        val sparkSession = 
        SparkSession.builder().config(sparkConf).getOrCreate()
        // 读取数据
        // val dorisSparkDF = sparkSession.read.format("doris")
        // .option("doris.table.identifier", "test_db.table1")
        // .option("doris.fenodes", "hadoop1:8030")
        // .option("user", "test")
        // .option("password", "test")
        // .load()
        // dorisSparkDF.show()
        // 写入数据
        import sparkSession.implicits._
        val mockDataDF = List(
            (11,23, "haha", 8),
            (11, 3, "hehe", 9),
            (11, 3, "heihei", 10)
        ).toDF("siteid", "citycode", "username","pv")
        mockDataDF.show(5)
        mockDataDF.write.format("doris")
        .option("doris.table.identifier", "test_db.table1")
        .option("doris.fenodes", "hadoop1:8030")
        .option("user", "test")
        .option("password", "test")
        //指定你要写入的字段
        // .option("doris.write.fields", "user")
        .save()
    } 
}

(3)RDD 方式读取数据

import org.apache.spark.{
    
    SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
/**
 * TODO
 *
 * @version 1.0
 * @author cjp
 */
object RDDDemo {
    
    
    def main( args: Array[String] ): Unit = {
    
    
        val sparkConf = new SparkConf().setAppName("RDDDemo")
        .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉
        val sc = new SparkContext(sparkConf)
        import org.apache.doris.spark._
        val dorisSparkRDD = sc.dorisRDD(
            tableIdentifier = Some("test_db.table1"),
            cfg = Some(Map(
                "doris.fenodes" -> "hadoop1:8030",
                "doris.request.auth.user" -> "test",
                "doris.request.auth.password" -> "test"
            ))
        )
        dorisSparkRDD.collect().foreach(println)
    } 
}

(4)配置和字段类型映射

  • 通用配置项

    Key Default Value Comment
    doris.fenodes Doris FE http 地址,支持多个地址,使用逗号分隔
    doris.table.identifier Doris 表名,如:db1.tbl1
    doris.request.retries 3 向Doris发送请求的重试次数
    doris.request.connect.timeout.ms 30000 向Doris发送请求的连接超时时间
    doris.request.read.timeout.ms 30000 向Doris发送请求的读取超时时间
    doris.request.query.timeout.s 3600 查询doris的超时时间,默认值为1小时,-1表示无超时限制
    doris.request.tablet.size Integer.MAX_VALUE 一个RDD Partition对应的Doris Tablet个数。 此数值设置越小,则会生成越多的Partition。从而提升Spark侧的并行度,但同时会对Doris造成更大的压力。
    doris.batch.size 1024 一次从BE读取数据的最大行数。增大此数值可减少Spark与Doris之间建立连接的次数。 从而减轻网络延迟所带来的额外时间开销。
    doris.exec.mem.limit 2147483648 单个查询的内存限制。默认为 2GB,单位为字节
    doris.deserialize.arrow.async false 是否支持异步转换Arrow格式到spark-doris-connector迭代所需的RowBatch
    doris.deserialize.queue.size 64 异步转换Arrow格式的内部处理队列,当doris.deserialize.arrow.async为true时生效
    doris.write.fields 指定写入Doris表的字段或者字段顺序,多列之间使用逗号分隔。 默认写入时要按照Doris表字段顺序写入全部字段。
    sink.batch.size 10000 单次写BE的最大行数
    sink.max-retries 1 写BE失败之后的重试次数
  • SQL 和 Dataframe 专有配置

    Key Default Value Comment
    user 访问Doris的用户名
    password 访问Doris的密码
    doris.filter.query.in.max.count 100 谓词下推中,in表达式value列表元素最大数量。超过此数量,则in表达式条件过滤在Spark侧处理。
  • RDD 专有配置

    Key Default Value Comment
    doris.request.auth.user 访问Doris的用户名
    doris.request.auth.password 访问Doris的密码
    doris.read.field 读取Doris表的列名列表,多列之间使用逗号分隔
    doris.filter.query 过滤读取数据的表达式,此表达式透传给Doris。Doris使用此表达式完成源端数据过滤。
  • Doris 和 Spark 列类型映射关系:

    Doris Type Spark Type
    NULL_TYPE DataTypes.NullType
    BOOLEAN DataTypes.BooleanType
    TINYINT DataTypes.ByteType
    SMALLINT DataTypes.ShortType
    INT DataTypes.IntegerType
    BIGINT DataTypes.LongType
    FLOAT DataTypes.FloatType
    DOUBLE DataTypes.DoubleType
    DATE DataTypes.StringType1
    DATETIME DataTypes.StringType1
    BINARY DataTypes.BinaryType
    DECIMAL DecimalType
    CHAR DataTypes.StringType
    LARGEINT DataTypes.StringType
    VARCHAR DataTypes.StringType
    DECIMALV2 DecimalType
    TIME DataTypes.DoubleType
    HLL Unsupported datatype

注:Connector中,将DATEDATETIME映射为String。由于Doris底层存储引擎处理逻辑,直接使用时间类型时,覆盖的时间范围无法满足需求。所以使用 String 类型直接返回对应的时间可读文本。

Flink Doris Connector

Flink Doris Connector 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。

Flink Doris Connector Sink 的内部实现是通过 Stream load 服务向 Doris 写入数据, 同时也支持 Stream load 请求参数的配置设定。

版本兼容如下:

Connector Flink Doris Java Scala
1.14_2.11-1.1.0 1.14.x 1.0+ 8 2.11
1.14_2.12-1.1.0 1.14.x 1.0+ 8 2.12

准备Flink环境

创建 maven 工程,编写 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atguigu.doris</groupId>
    <artifactId>flink-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <flink.version>1.13.1</flink.version>
        <java.version>1.8</java.version>
        <scala.binary.version>2.12</scala.binary.version>
        <slf4j.version>1.7.30</slf4j.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope> <!--不会打包到依赖中,只参与编译,不
参与运行 -->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streamingjava_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flinkclients_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-plannerblink_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <!---->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-runtimeweb_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.14.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-statebackendrocksdb_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-sequence-file</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>com.ververica</groupId>
            <artifactId>flink-connector-mysql-cdc</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--flink-doris-connector-->
        <dependency>
            <groupId>org.apache.doris</groupId>
            <!--<artifactId>flink-doris-connector-
1.14_2.12</artifactId>-->
            <artifactId>flink-doris-connector-1.13_2.12</artifactId>
            <!--<artifactId>flink-doris-connector-
1.12_2.12</artifactId>-->
            <!--<artifactId>flink-doris-connector-
1.11_2.12</artifactId>-->
            <version>1.0.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <excludes>

                                    <exclude>com.google.code.findbugs:jsr305</exclude>
                                    <exclude>org.slf4j:*</exclude>
                                    <exclude>log4j:*</exclude>

                                    <exclude>org.apache.hadoop:*</exclude>
                                </excludes>
                            </artifactSet>
                            <filters>
                                <filter>
                                    <!-- Do not copy the signatures in 
the META-INF folder.
 Otherwise, this might cause 
SecurityExceptions when using the JAR. -->
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers combine.children="append">
                                <transformer 
                                             implementation="org.apache.maven.plugins.shade.resource.ServicesR
                                                             esourceTransformer">
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

使用Flink Doris Connector

Flink Doris Connector 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。

(1)SQL方式读写

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
/**
* TODO
*
* @author cjp
* @version 1.0
*/
public class SQLDemo {
    
    
    public static void main(String[] args) {
    
    
        StreamExecutionEnvironment env = 
        StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        StreamTableEnvironment tableEnv =
        StreamTableEnvironment.create(env);
        tableEnv.executeSql("CREATE TABLE flink_doris (\n" +
                            " siteid INT,\n" +
                            " citycode SMALLINT,\n" +
                            " username STRING,\n" +
                            " pv BIGINT\n" +
                            " ) \n" +
                            " WITH (\n" +
                            " 'connector' = 'doris',\n" +
                            " 'fenodes' = 'hadoop1:8030',\n" +
                            " 'table.identifier' = 'test_db.table1',\n" +
                            " 'username' = 'test',\n" +
                            " 'password' = 'test'\n" +
                            ")\n");
        // 读取数据
        // tableEnv.executeSql("select * from flink_doris").print();
        // 写入数据
        tableEnv.executeSql("insert into 
                     flink_doris(siteid,username,pv) values(22,'wuyanzu',3)");
    } 
}

(2)DataStream 读写

  • source

    import org.apache.doris.flink.cfg.DorisStreamOptions;
    import org.apache.doris.flink.datastream.DorisSourceFunction;
    import org.apache.doris.flink.deserialization.SimpleListDeserializationSchema;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
    import java.util.Properties;
    /**
    * TODO
    *
    * @author cjp
    * @version 1.0
    */
    public class DataStreamSourceDemo {
          
          
        public static void main(String[] args) throws Exception {
          
          
            StreamExecutionEnvironment env = 
            StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            Properties properties = new Properties();
            properties.put("fenodes","hadoop1:8030");
            properties.put("username","test");
            properties.put("password","test");
            properties.put("table.identifier","test_db.table1");
            env.addSource(new DorisSourceFunction(
                new DorisStreamOptions(properties),
                new SimpleListDeserializationSchema()
            )
                         ).print();
            env.execute();
        } 
    }
    
  • Sink

    Json 数据流:

    import org.apache.doris.flink.cfg.*;
    import org.apache.doris.flink.datastream.DorisSourceFunction;
    import 
    org.apache.doris.flink.deserialization.SimpleListDeserializationS
    chema;
    import 
    org.apache.flink.streaming.api.environment.StreamExecutionEnviron
    ment;
    import java.util.Properties;
    /**
    * TODO
    *
    * @author cjp
    * @version 1.0
    */
    public class DataStreamJsonSinkDemo {
          
          
        public static void main(String[] args) throws Exception {
          
          
            StreamExecutionEnvironment env = 
            StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            Properties pro = new Properties();
            pro.setProperty("format", "json");
            pro.setProperty("strip_outer_array", "true");
            env.fromElements(
                "{
          
          \"longitude\": \"116.405419\", \"city\": \"
    北京\", \"latitude\": \"39.916927\"}"
            )
            .addSink(
                DorisSink.sink(
                    DorisReadOptions.builder().build(),
                    DorisExecutionOptions.builder()
                    .setBatchSize(3)
                    .setBatchIntervalMs(0L)
                    .setMaxRetries(3)
                    .setStreamLoadProp(pro).build(),
                    DorisOptions.builder()
                    .setFenodes("FE_IP:8030")
                    .setTableIdentifier("db.table")
                    .setUsername("root")
                    .setPassword("").build()
                ));
            // .addSink(
            // DorisSink.sink(
            // DorisOptions.builder()
            // .setFenodes("FE_IP:8030")
            // .setTableIdentifier("db.table")
            // .setUsername("root")
            // .setPassword("").build()
            // ));
            env.execute();
        } 
    }
    

    RowData 数据流:

    import org.apache.doris.flink.cfg.DorisExecutionOptions;
    import org.apache.doris.flink.cfg.DorisOptions;
    import org.apache.doris.flink.cfg.DorisReadOptions;
    import org.apache.doris.flink.cfg.DorisSink;
    import org.apache.flink.api.common.functions.MapFunction;
    import org.apache.flink.streaming.api.datastream.DataStream;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.table.data.GenericRowData;
    import org.apache.flink.table.data.RowData;
    import org.apache.flink.table.data.StringData;
    import org.apache.flink.table.types.logical.*;
    /**
    * TODO
    * @author cjp
    * @version 1.0
    */
    public class DataStreamRowDataSinkDemo {
          
          
        public static void main(String[] args) throws Exception {
          
          
            StreamExecutionEnvironment env = 
            StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            DataStream<RowData> source = env.fromElements("")
            .map(new MapFunction<String, RowData>() {
          
          
                @Override
                public RowData map(String value) throws Exception 
                {
          
          
                    GenericRowData genericRowData = new 
                    GenericRowData(4);
                    genericRowData.setField(0, 33);
                    genericRowData.setField(1, new Short("3"));
                    genericRowData.setField(2, 
                                            StringData.fromString("flink-stream"));
                    genericRowData.setField(3, 3L);
                    return genericRowData;
                }
            });
            LogicalType[] types = {
          
          new IntType(), new SmallIntType(), 
                                   new VarCharType(32), new BigIntType()};
            String[] fields = {
          
          "siteid", "citycode", "username", "pv"};
            source.addSink(
                DorisSink.sink(
                    fields,
                    types,
                    DorisReadOptions.builder().build(),
                    DorisExecutionOptions.builder()
                    .setBatchSize(3)
                    .setBatchIntervalMs(0L)
                    .setMaxRetries(3)
                    .build(),
                    DorisOptions.builder()
                    .setFenodes("hadoop1:8030")
                    .setTableIdentifier("test_db.table1")
                    .setUsername("test")
                    .setPassword("test").build()
                ));
            env.execute();
        } 
    }
    

(3)通用配置项和字段类型映射

  • 通用配置项:

    Key Default Value Required Comment
    fenodes Y Doris FE http 地址
    table.identifier Y Doris 表名,如:db.tbl
    username Y 访问 Doris 的用户名
    password Y 访问 Doris 的密码
    doris.request.retries 3 N 向 Doris 发送请求的重试次数
    doris.request.connect.timeout.ms 30000 N 向 Doris 发送请求的连接超时时间
    doris.request.read.timeout.ms 30000 N 向 Doris 发送请求的读取超时时间
    doris.request.query.timeout.s 3600 N 查询 Doris 的超时时间,默认值为1小时,-1表示无超时限制
    doris.request.tablet.size Integer. MAX_VALUE N 一个 Partition 对应的 Doris Tablet 个数。 此数值设置越小,则会生成越多的 Partition。从而提升 Flink 侧的并行度,但同时会对 Doris 造成更大的压力。
    doris.batch.size 1024 N 一次从 BE 读取数据的最大行数。增大此数值可减少 Flink 与 Doris 之间建立连接的次数。 从而减轻网络延迟所带来的额外时间开销。
    doris.exec.mem.limit 2147483648 N 单个查询的内存限制。默认为 2GB,单位为字节
    doris.deserialize.arrow.async FALSE N 是否支持异步转换 Arrow 格式到 flink-doris-connector 迭代所需的 RowBatch
    doris.deserialize.queue.size 64 N 异步转换 Arrow 格式的内部处理队列,当 doris.deserialize.arrow.async 为 true 时生效
    doris.read.field N 读取 Doris 表的列名列表,多列之间使用逗号分隔
    doris.filter.query N 过滤读取数据的表达式,此表达式透传给 Doris。Doris 使用此表达式完成源端数据过滤。
    sink.label-prefix Y Stream load导入使用的label前缀。2pc场景下要求全局唯一 ,用来保证Flink的EOS语义。
    sink.properties.* N Stream Load 的导入参数。 例如: ‘sink.properties.column_separator’ = ‘, ’ 定义列分隔符, ‘sink.properties.escape_delimiters’ = ‘true’ 特殊字符作为分隔符,’\x01’会被转换为二进制的0x01 JSON格式导入 ‘sink.properties.format’ = ‘json’ ‘sink.properties.read_json_by_line’ = ‘true’
    sink.enable-delete TRUE N 是否启用删除。此选项需要 Doris 表开启批量删除功能(Doris0.15+版本默认开启),只支持 Unique 模型。
    sink.enable-2pc TRUE N 是否开启两阶段提交(2pc),默认为true,保证Exactly-Once语义。关于两阶段提交可参考这里
  • Doris 和 Flink 列类型映射关系:

    Doris Type Flink Type
    NULL_TYPE NULL
    BOOLEAN BOOLEAN
    TINYINT TINYINT
    SMALLINT SMALLINT
    INT INT
    BIGINT BIGINT
    FLOAT FLOAT
    DOUBLE DOUBLE
    DATE DATE
    DATETIME TIMESTAMP
    DECIMAL DECIMAL
    CHAR STRING
    LARGEINT STRING
    VARCHAR STRING
    DECIMALV2 DECIMAL
    TIME DOUBLE
    HLL Unsupported datatype

DataX doriswriter

DorisWriter 支持将大批量数据写入 Doris 中。DorisWriter 通过 Doris 原生支持 Stream load 方式导入数据,

DorisWriter 会将 reader 读取的数据进行缓存在内存中,拼接成 Json 文本,然后批量导入至 Doris。

DorisWriter需要进行编译后得到插件添加到Datax中,才能进行使用。可以自己编译,也可以直接使用编译好的包:

(1)进入之前的容器环境

docker run -it \ -v /opt/software/.m2:/root/.m2 \ -v /opt/software/apache-doris-0.15.0-incubating-src/:/root/apache-doris-0.15.0-incubating-src/ \
apache/incubator-doris:build-env-for-0.15.0

或者直接下载GitHub - apache/doris: Apache Doris is an easy-to-use, high performance and unified analytics database.

(2)运行 init-env.sh

cd /root/apache-doris-0.15.0-incubating-src/extension/DataX
sh init-env.sh

主要做了下面几件事,减少了繁杂的操作:

  • 将 DataX 代码库 clone 到本地。

  • 将 doriswriter/ 目录软链到 DataX/doriswriter 目录。

    这个目录是 doriswriter 插件的代码目录。这个目录中的所有代码,都托管在 Apache Doris 的代码库中。

  • 在 DataX/pom.xml 文件中添加 doriswriter 模块。

  • 将 DataX/core/pom.xml 文件中的 httpclient 版本从 4.5 改为 4.5.13(因为有bug)

(3)手动上传依赖alibaba-datax-maven-m2-20210928.tar.gz

在编译的时候如果没有这个依赖,可能汇报错:

Could not find artifact com.alibaba.datax:datax-all:pom:0.0.1-SNAPSHOT ...

可尝试以下方式解决:

  • 下载 alibaba-datax-maven-m2-20210928.tar.gz,并上传;

  • 解压:

    tar -zxvf alibaba-datax-maven-m2-20210928.tar.gz -C /opt/software
    
  • 拷贝解压后的文件到 maven 仓库:

    sudo cp -r /opt/software/alibaba/datax/ /opt/software/.m2/repository/com/alibaba/
    

(4)编译 doriswriter

  • 单独编译 doriswriter 插件:

    cd /root/apache-doris-0.15.0-incubating-src/extension/DataX/DataX
    mvn clean install -pl plugin-rdbms-util,doriswriter -DskipTests
    
  • 编译整个 DataX 项目:

    cd /root/apache-doris-0.15.0-incubating-src/extension/DataX/DataX
    mvn package assembly:assembly -Dmaven.test.skip=true
    

产出在 target/datax/datax/.

hdfsreader, hdfswriter and oscarwriter 这三个插件需要额外的 jar 包。如果你并不需要这些插件,可以在 DataX/pom.xml 中删除这些插件的模块。

(5)拷贝编译好的插件到 DataX

Sudo cp -r /opt/software/apache-doris-0.15.0-incubating-src/extension/DataX/doriswriter/target/datax/plugin/writer/dorisw
riter /opt/module/datax/plugin/writer

使用:

# MySQL 建表、插入测试数据
CREATE TABLE `sensor` (
 `id` varchar(255) NOT NULL,
 `ts` bigint(255) DEFAULT NULL,
 `vc` int(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
)
insert into sensor values('s_2',3,3),('s_9',9,9);
# Doris 建表
CREATE TABLE `sensor` (
 `id` varchar(255) NOT NULL,
 `ts` bigint(255) DEFAULT NULL,
 `vc` int(255) DEFAULT NULL
)
DISTRIBUTED BY HASH(`id`) BUCKETS 10;
vim mysql2doris.json
{
    
    
    "job": {
    
    
        "setting": {
    
    
            "speed": {
    
    
                "channel": 1
            },
            "errorLimit": {
    
    
                "record": 0,
                "percentage": 0
            }
        },
        "content": [
            {
    
    
                "reader": {
    
    
                    "name": "mysqlreader", 
                    "parameter": {
    
    
                        "column": [
                            "id",
                            "ts",
                            "vc"
                        ],
                        "connection": [
                            {
    
    
                                "jdbcUrl": [
                                    "jdbc:mysql://hadoop1:3306/test"
                                ], 
                                "table": [
                                    "sensor"
                                ]
                            }
                        ], 
                        "username": "root", 
                        "password": "000000"
                    }
                }, 
                "writer": {
    
    
                    "name": "doriswriter",
                    "parameter": {
    
    
                        "feLoadUrl": ["hadoop1:8030", "hadoop2:8030", 
                                      "hadoop3:8030"],
                        "beLoadUrl": ["hadoop1:8040", "hadoop2:8040", 
                                      "hadoop3:8040"],
                        "jdbcUrl": "jdbc:mysql://hadoop1:9030/",
                        "database": "test_db",
                        "table": "sensor",
                        "column": ["id", "ts", "vc"],
                        "username": "test",
                        "password": "test",
                        "postSql": [],
                        "preSql": [],
                        "loadProps": {
    
    
                        },
                        "maxBatchRows" : 500000,
                        "maxBatchByteSize" : 104857600,
                        "labelPrefix": "my_prefix",
                        "lineDelimiter": "\n"
                    }
                }
            }
        ]
    } }

参数说明:

  • jdbcUrl

    描述:Doris 的 JDBC 连接串,用户执行 preSql 或 postSQL。

    必选:是

    默认值:无

  • feLoadUrl

    描述:和 beLoadUrl 二选一。作为 Stream Load 的连接目标。格式为 “ip:port”。其中IP 是 FE 节点 IP,port 是 FE 节点的 http_port。可以填写多个,doriswriter 将以轮询的方式访问。

    必选:否

    默认值:无

  • beLoadUrl

    描述:和 feLoadUrl 二选一。作为 Stream Load 的连接目标。格式为 “ip:port”。其中 IP 是 BE 节点 IP,port 是 BE 节点的 webserver_port。可以填写多个,doriswriter 将以轮询的方式访问。

    必选:否

    默认值:无

  • username

    描述:访问 Doris 数据库的用户名

    必选:是

    默认值:无

  • password

    描述:访问 Doris 数据库的密码

    必选:否

    默认值:空

  • database

    描述:需要写入的 Doris 数据库名称。

    必选:是

    默认值:无

  • table

    描述:需要写入的 Doris 表名称。

    必选:是

    默认值:无

  • column

    描述:目的表需要写入数据的字段,这些字段将作为生成的 Json 数据的字段名。字段之间用英文逗号分隔。例如: “column”: [“id”,“name”,“age”]。

    必选:是

    默认值:否

  • preSql

    描述:写入数据到目的表前,会先执行这里的标准语句。

    必选:否

    默认值:无

  • postSql

    描述:写入数据到目的表后,会执行这里的标准语句。

    必选:否

    默认值:无

  • maxBatchRows

    描述:每批次导入数据的最大行数。和 maxBatchByteSize 共同控制每批次的导入数量。每批次数据达到两个阈值之一,即开始导入这一批次的数据。

    必选:否

    默认值:500000

  • maxBatchByteSize

    描述:每批次导入数据的最大数据量。和 maxBatchRows 共同控制每批次的导入数量。每批次数据达到两个阈值之一,即开始导入这一批次的数据。

    必选:否

    默认值:104857600

  • labelPrefix

    描述:每批次导入任务的 label 前缀。最终的 label 将有 labelPrefix + UUID + 序号 组 成

    必选:否

    默认值:datax_doris_writer_

  • lineDelimiter

    描述:每批次数据包含多行,每行为 Json 格式,每行的的分隔符即为 lineDelimiter。支持多个字节, 例如’\x02\x03’。

    必选:否

    默认值:\n

  • loadProps

    描述:StreamLoad 的请求参数,详情参照 StreamLoad 介绍页面。

    必选:否

    默认值:无

  • connectTimeout

    描述:StreamLoad 单次请求的超时时间, 单位毫秒(ms)。

    必选:否

    默认值:-1

数据湖分析

JDBC和ODBC

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,是一个标准,一个协议,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。简言之,JDBC就是Java用于执行SQL语句实现数据库操作的API。

当JDBC提出标准以后,由对应的数据库厂商来进行相应的实现,而这些实现JDBC接口的驱动程序才是真正操作数据库的东西。所以基于这种设计,我们只需要面向JDBC这一个统一的接口进行开发,就可以实现对不同的数据库进行操作了。

ODBC是早期的数据库规范,是开放式数据库连接。与JDBC一样,ODBC也是一个API,充当客户端应用程序和服务器端数据库之间的接口。ODBC是最广泛使用的,并且可以理解许多不同的编程语言。但它的代码很复杂,难以理解。

JDBC和ODBC的区别:

  • JDBC代表java数据库连接,是面向对象的。而ODBC代表开放式数据库连接,是程序性的。
  • JDBC只能将其用于Java语言开发的程序中,可以在任何平台上使用;ODBC可以将其用于任何语言,如C,C ++等本地语言开发的ODBC驱动程序,仅可以选择在Windows平台上使用。
  • 对于Java应用程序,不建议使用ODBC,因为内部转换会导致性能下降,应用程序将变为平台相关;强烈建议使用JDBC,因为我们没有性能和平台相关的问题。
  • ODBC的代码很复杂,很难学习。但是,JDBC的代码更简单,更容易运行。

ODBC 外部表

ODBC External Table Of Doris 提供了 Doris 通过数据库访问的标准接口(ODBC)来访问外部表,外部表省去了繁琐的数据导入工作,让 Doris 可以具有了访问各式数据库的能力,并借助 Doris 本身的 OLAP 的能力来解决外部表的数据分析问题:

  • 支持各种数据源接入 Doris
  • 支持 Doris 与各种数据源中的表联合查询,进行更加复杂的分析操作
  • 通过 insert into 将 Doris 执行的查询结果写入外部的数据源

使用方式

(1)ODBC Driver 的安装和配置

各大主流数据库都会提供 ODBC 的访问 Driver,用户可以执行参照各数据库官方推荐的方式安装对应的 ODBC Driver LiB 库。

安装完成之后,查找对应的数据库的 Driver Lib 库的路径,并且修改 be/conf/odbcinst.ini的配置:

[MySQL Driver]
Description = ODBC for MySQL
Driver = /usr/lib64/libmyodbc8w.so
FileUsage = 1
  • 上述配置[]里的对应的是 Driver 名,在建立外部表时需要保持外部表的 Driver 名和配置文件之中的一致。
  • Driver= 这个要根据实际 BE 安装 Driver 的路径来填写,本质上就是一个动态库的路径,这里需要保证该动态库的前置依赖都被满足。

切记,这里要求所有的 BE 节点都安装上相同的 Driver,并且安装路径相同,同时有相同的 be/conf/odbcinst.ini 的配置。

(2)Doris 中创建 ODBC 的外表

  • 方式一:不使用 Resource 创建 ODBC 的外表

    CREATE EXTERNAL TABLE `baseall_oracle` (
        `k1` decimal(9, 3) NOT NULL COMMENT "",
        `k2` char(10) NOT NULL COMMENT "",
        `k3` datetime NOT NULL COMMENT "",
        `k5` varchar(20) NOT NULL COMMENT "",
        `k6` double NOT NULL COMMENT ""
    ) ENGINE=ODBC
    COMMENT "ODBC"
    PROPERTIES (
        "host" = "192.168.0.1",
        "port" = "8086",
        "user" = "test",
        "password" = "test",
        "database" = "test",
        "table" = "baseall",
        "driver" = "Oracle 19 ODBC driver",
        "odbc_type" = "oracle"
    );
    
  • 方式二:通过 ODBC_Resource 来创建 ODBC 外表(推荐使用的方式)。

    CREATE EXTERNAL RESOURCE `oracle_odbc`
    PROPERTIES (
        "type" = "odbc_catalog",
        "host" = "192.168.0.1",
        "port" = "8086",
        "user" = "test",
        "password" = "test",
        "database" = "test",
        "odbc_type" = "oracle",
        "driver" = "Oracle 19 ODBC driver"
    );
    
    CREATE EXTERNAL TABLE `baseall_oracle` (
        `k1` decimal(9, 3) NOT NULL COMMENT "",
        `k2` char(10) NOT NULL COMMENT "",
        `k3` datetime NOT NULL COMMENT "",
        `k5` varchar(20) NOT NULL COMMENT "",
        `k6` double NOT NULL COMMENT ""
    ) ENGINE=ODBC
    COMMENT "ODBC"
    PROPERTIES (
        "odbc_catalog_resource" = "oracle_odbc",
        "database" = "test",
        "table" = "baseall"
    );
    

    参数说明:

    • hosts :外表数据库的 IP 地址
    • driver :ODBC 外表 Driver 名,需要和 be/conf/odbcinst.ini 中的 Driver 名一致。
    • odbc_type :外表数据库的类型,当前支持 oracle, mysql, postgresql
    • user :外表数据库的用户名
    • password :对应用户的密码信息

(3)查询用法

完成在Doris中建立ODBC外表后,除了无法使用Doris中的数据模型(rollup、预聚合、物化视图等)外,与普通的Doris表并无区别:

select * from oracle_table where k1 > 1000 and k3 ='term' or k4 like '%doris';

(4)数据写入

在Doris中建立ODBC外表后,可以通过insert into语句直接写入数据,也可以将Doris执行完查询之后的结果写入ODBC外表,或者是从一个ODBC外表将数据导入另一个ODBC外表。

insert into oracle_table values(1, "doris");
insert into oracle_table select * from postgre_table;

(5)事务

Doris的数据是由一组batch的方式写入外部表的,如果中途导入中断,之前写入数据可能需要回滚。所以ODBC外表支持数据写入时的事务,事务的支持需要通过session variable:enable_odbc_transcation 设置。

set enable_odbc_transcation = true; 

事务保证了ODBC外表数据写入的原子性,但是一定程度上会降低数据写入的性能,可以考虑酌情开启该功能。

使用 ODBC 的 MySQL 外表

CentOS 数据库 ODBC 版本对应关系:

Mysql版本 Mysql ODBC版本
8.0.27 8.0.27,8.026
5.7.36 5.3.11,5.3.13
5.6.51 5.3.11,5.3.13
5.5.62 5.3.11,5.3.13

MySQL 与 Doris 的数据类型匹配:

MySQL Doris 替换方案
BOOLEAN BOOLEAN
CHAR CHAR 当前仅支持UTF8编码
VARCHAR VARCHAR 当前仅支持UTF8编码
DATE DATE
FLOAT FLOAT
TINYINT TINYINT
SMALLINT SMALLINT
INT INT
BIGINT BIGINT
DOUBLE DOUBLE
DATETIME DATETIME
DECIMAL DECIMAL

(1)安装 unixODBC(可选)

安装
yum install -y unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel
查看是否安装成功
odbcinst -j

(2)安装 MySQL 对应版本的 ODBC(每个 BE 节点都要)

下载
wget https://downloads.mysql.com/archives/get/p/10/file/mysql-connector-odbc-5.3.11-1.el7.x86_64.rpm
安装
yum install -y mysql-connector-odbc-5.3.11-1.el7.x86_64.rpm
查看是否安装成功
myodbc-installer -d -l

(3)配置 unixODBC,验证通过 ODBC 访问 Mysql

编辑 ODBC 配置文件
vim /etc/odbc.ini
[mysql]
Description = Data source MySQL
Driver = MySQL ODBC 5.3 Unicode Driver
Server = hadoop1
Host = hadoop1
Database = test
Port = 3306
User = root
Password = 000000
测试链接
isql -v mysql

(4)准备 MySQL 表

CREATE TABLE `test_cdc` (
    `id` int NOT NULL AUTO_INCREMENT,
    `name` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=91234 DEFAULT CHARSET=utf8mb4;
INSERT INTO `test_cdc` VALUES (123, 'this is a update');
INSERT INTO `test_cdc` VALUES (1212, '测试 flink CDC');
INSERT INTO `test_cdc` VALUES (1234, '这是测试');
INSERT INTO `test_cdc` VALUES (11233, 'zhangfeng_1');
INSERT INTO `test_cdc` VALUES (21233, 'zhangfeng_2');
INSERT INTO `test_cdc` VALUES (31233, 'zhangfeng_3');
INSERT INTO `test_cdc` VALUES (41233, 'zhangfeng_4');
INSERT INTO `test_cdc` VALUES (51233, 'zhangfeng_5');
INSERT INTO `test_cdc` VALUES (61233, 'zhangfeng_6');
INSERT INTO `test_cdc` VALUES (71233, 'zhangfeng_7');
INSERT INTO `test_cdc` VALUES (81233, 'zhangfeng_8');
INSERT INTO `test_cdc` VALUES (91233, 'zhangfeng_9');

(5)修改 Doris 的配置文件(每个 BE 节点都要,不用重启 BE)

在 BE 节点的 conf/odbcinst.ini,添加我们的刚才注册的的 ODBC 驱动([MySQL ODBC 5.3.11]这部分)。

# Driver from the postgresql-odbc package
# Setup from the unixODBC package
[PostgreSQL]
Description = ODBC for PostgreSQL
Driver = /usr/lib/psqlodbc.so
Setup = /usr/lib/libodbcpsqlS.so
FileUsage = 1
# Driver from the mysql-connector-odbc package
# Setup from the unixODBC package
[MySQL ODBC 5.3.11]
Description = ODBC for MySQL
Driver= /usr/lib64/libmyodbc5w.so
FileUsage = 1
# Driver from the oracle-connector-odbc package
# Setup from the unixODBC package
[Oracle 19 ODBC driver]
Description=Oracle ODBC driver for Oracle 19
Driver=/usr/lib/libsqora.so.19.1

(6)Doris 建 Resource

通过 ODBC_Resource 来创建 ODBC 外表,这是推荐的方式,这样 resource 可以复用。

CREATE EXTERNAL RESOURCE `mysql_5_3_11`
PROPERTIES (
    "host" = "hadoop1",
    "port" = "3306",
    "user" = "root",
    "password" = "000000",
    "database" = "test",
    "table" = "test_cdc",
    "driver" = "MySQL ODBC 5.3.11", --名称要和上面[]里的名称一致
    "odbc_type" = "mysql",
    "type" = "odbc_catalog")

(7)基于 Resource 创建 Doris 外表

CREATE EXTERNAL TABLE `test_odbc_5_3_11` (
    `id` int NOT NULL ,
    `name` varchar(255) null
) ENGINE=ODBC
COMMENT "ODBC"
PROPERTIES (
    "odbc_catalog_resource" = "mysql_5_3_11", --名称就是 resource 的名称
    "database" = "test",
    "table" = "test_cdc"
);

(8)查询 Doris 外表

select * from `test_odbc_5_3_11`;

使用 ODBC 的 Oracle 外表

CentOS 数据库 ODBC 版本对应关系:

Oracle版本 Oracle ODBC版本
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production oracle-instantclient19.13-odbc-19.13.0.0.0
Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production oracle-instantclient19.13-odbc-19.13.0.0.0
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production oracle-instantclient19.13-odbc-19.13.0.0.0
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production oracle-instantclient19.13-odbc-19.13.0.0.0
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production oracle-instantclient19.13-odbc-19.13.0.0.0

与 Doris 的数据类型匹配:

Oracle Doris 替换方案
不支持 BOOLEAN Oracle可用number(1) 替换boolean
CHAR CHAR
VARCHAR VARCHAR
DATE DATE
FLOAT FLOAT
TINYINT Oracle可由NUMMBER替换
SMALLINT SMALLINT
INT INT
BIGINT Oracle可由NUMMBER替换
DOUBLE Oracle可由NUMMBER替换
DATETIME DATETIME
NUMBER DECIMAL

(1)安装 Oracle 对应版本的 ODBC(每个 BE 节点都要):

下载 4 个安装包
wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-sqlplus-19.13.0.0.0-2.x86_64.rpm
wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-devel-19.13.0.0.0-2.x86_64.rpm
wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-odbc-19.13.0.0.0-2.x86_64.rpm
wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-basic-19.13.0.0.0-2.x86_64.rpm
安装 4 个安装包
rpm -ivh oracle-instantclient19.13-basic-19.13.0.0.0-2.x86_64.rpm
rpm -ivh oracle-instantclient19.13-devel-19.13.0.0.0-2.x86_64.rpm
rpm -ivh oracle-instantclient19.13-odbc-19.13.0.0.0-2.x86_64.rpm
rpm -ivh oracle-instantclient19.13-sqlplus-19.13.0.0.0-2.x86_64.rpm

(2)修改 Doris 的配置(每个 BE 节点都要,不用重启)

修改 BE 节点 conf/odbcinst.ini 文件,加入刚才/etc/odbcinst.ini 添加的一样内容,并删除原先的 Oracle 配置:

[Oracle 19 ODBC driver]
Description = Oracle ODBC driver for Oracle 19
Driver = /usr/lib/oracle/19.13/client64/lib/libsqora.so.19.1

(3)创建 Resource

CREATE EXTERNAL RESOURCE `oracle_19`
PROPERTIES (
    "host" = "hadoop2",
    "port" = "1521",
    "user" = "atguigu",
    "password" = "000000",
    "database" = "orcl", --数据库示例名称,也就是 ORACLE_SID
    "driver" = "Oracle 19 ODBC driver", --名称一定和 be odbcinst.ini里的 oracle 部分的[]里的内容一样
    "odbc_type" = "oracle",
    "type" = "odbc_catalog"
);

(4)基于 Resource 创建 Doris 外表

CREATE EXTERNAL TABLE `oracle_odbc` (
    id int,
    name VARCHAR(20) NOT NULL
) ENGINE=ODBC
COMMENT "ODBC"
PROPERTIES (
    "odbc_catalog_resource" = "oracle_19", 
    "database" = "orcl",
    "table" = "student"
);

ES外表

Doris-On-ES 将 Doris 的分布式查询规划能力和 ES(Elasticsearch)的全文检索能力相结合,提供更完善的 OLAP 分析场景解决方案:

  • ES 中的多 index 分布式 Join 查询
  • Doris 和 ES 中的表联合查询,更复杂的全文检索过滤

原理

  • 创建 ES 外表后,FE 会请求建表指定的主机,获取所有节点的 HTTP 端口信息以及 index 的 shard 分布信息等,如果请求失败会顺序遍历 host 列表直至成功或完全失败
  • 查询时会根据 FE 得到的一些节点信息和 index 的元数据信息,生成查询计划并发给对应的 BE 节点
  • BE 节点会根据就近原则即优先请求本地部署的 ES 节点,BE 通过 HTTP Scroll 方式流式的从 ES index 的每个分片中并发的从_source 或 docvalue 中获取数据
  • Doris 计算完结果后,返回给用户

使用方式

(1)Doris 中创建 ES 外表

  • 创建 ES 索引

    PUT test
    {
          
          
        "settings": {
          
          
            "index": {
          
          
                "number_of_shards": "1",
                "number_of_replicas": "0"
            }
        },
        "mappings": {
          
          
            "doc": {
          
           // ES 7.x 版本之后创建索引时不需要指定 type,会有一个默认且唯
                一的`_doc` type
                "properties": {
          
          
                "k1": {
          
          
                "type": "long"
            },
            "k2": {
          
          
                "type": "date"
            },
            "k3": {
          
          
                "type": "keyword"
            },
            "k4": {
          
          
                "type": "text",
                "analyzer": "standard"
            },
            "k5": {
          
          
                "type": "float"
            }
        }
    }
    } 
    }
    
  • ES 索引导入数据

    POST /_bulk
    {
          
          "index":{
          
          "_index":"test","_type":"doc"}}
    {
          
           "k1" : 100, "k2": "2020-01-01", "k3": "Trying out Elasticsearch", 
    "k4": "Trying out Elasticsearch", "k5": 10.0}
    {
          
          "index":{
          
          "_index":"test","_type":"doc"}}
    {
          
           "k1" : 100, "k2": "2020-01-01", "k3": "Trying out Doris", "k4": 
    "Trying out Doris", "k5": 10.0}
    {
          
          "index":{
          
          "_index":"test","_type":"doc"}}
    {
          
           "k1" : 100, "k2": "2020-01-01", "k3": "Doris On ES", "k4": "Doris 
    On ES", "k5": 10.0}
    {
          
          "index":{
          
          "_index":"test","_type":"doc"}}
    {
          
           "k1" : 100, "k2": "2020-01-01", "k3": "Doris", "k4": "Doris", 
    "k5": 10.0}
    {
          
          "index":{
          
          "_index":"test","_type":"doc"}}
    {
          
           "k1" : 100, "k2": "2020-01-01", "k3": "ES", "k4": "ES", "k5": 
    10.0}
    
  • Doris 中创建 ES 外表

    CREATE EXTERNAL TABLE `es_test` (
        `k1` bigint(20) COMMENT "",
        `k2` datetime COMMENT "",
        `k3` varchar(20) COMMENT "",
        `k4` varchar(100) COMMENT "",
        `k5` float COMMENT ""
    ) ENGINE=ELASTICSEARCH // ENGINE 必须是 Elasticsearch
    PROPERTIES (
        "hosts" = "http://hadoop1:9200,http://hadoop2:9200,http://hadoop3:9200",
        "index" = "test",
        "type" = "doc",
        "user" = "",
        "password" = ""
    );
    

    参数说明:

    参数 说明
    hosts ES集群地址,可以是一个或多个,也可以是ES前端的负载均衡地址
    index 对应的ES的index名字,支持alias,如果使用doc_value,需要使用真实的名称
    type index的type,ES 7.x及以后的版本不传此参数
    user ES集群用户名
    password 对应用户的密码信息
    • ES 7.x之前的集群请注意在建表的时候选择正确的索引类型type
    • 认证方式目前仅支持Http Basic认证,并且需要确保该用户有访问: /_cluster/state/、_nodes/http等路径和index的读权限; 集群未开启安全认证,用户名和密码不需要设置
    • Doris表中的列名需要和ES中的字段名完全匹配,字段类型应该保持一致
    • ENGINE必须是 Elasticsearch

Doris On ES 一个重要的功能就是过滤条件的下推: 过滤条件下推给 ES,这样只有真正满足条件的数据才会被返回,能够显著的提高查询性能和降低 Doris 和 Elasticsearch 的 CPU、memory、IO 使用量。

下面的操作符(Operators)会被优化成如下 ES Query:

SQL syntax ES 5.x+ syntax
= term query
in terms query
> , < , >= , ⇐ range query
and bool.filter
or bool.should
not bool.must_not
not in bool.must_not + terms query
is_not_null exists query
is_null bool.must_not + exists query
esquery ES原生json形式的QueryDSL

数据类型映射:

Doris\ES byte short integer long float double keyword text date
tinyint
smallint
int
bigint
float
double
char
varchar
date
datetime

参数配置

(1)启用列式扫描优化查询速度

"enable_docvalue_scan" = "true"
  • 参数说明

    是否开启通过 ES/Lucene 列式存储获取查询字段的值,默认为 false。开启后 Doris 从 ES中获取数据会遵循以下两个原则:

    ①尽力而为: 自动探测要读取的字段是否开启列式存储(doc_value: true),如果获取的字段全部有列存,Doris 会从列式存储中获取所有字段的值

    ②自动降级: 如果要获取的字段只要有一个字段没有列存,所有字段的值都会从行存_source 中解析获取

  • 优势:

    默认情况下,Doris On ES 会从行存也就是_source 中获取所需的所有列,_source 的存储采用的行式+json 的形式存储,在批量读取性能上要劣于列式存储,尤其在只需要少数列的情况下尤为明显,只获取少数列的情况下,docvalue 的性能大约是_source 性能的十几倍。

  • 注意

    text 类型的字段在 ES 中是没有列式存储,因此如果要获取的字段值有 text 类型字段会自动降级为从_source 中获取;

    在获取的字段数量过多的情况下(>= 25),从 docvalue中获取字段值的性能会和从_source中获取字段值基本一样。

(2)探测 keyword 类型字段

"enable_keyword_sniff" = "true"

参数说明:

  • 是否对 ES 中字符串类型分词类型(text) fields 进行探测,获取额外的未分词(keyword)字段名(multi-fields 机制)

  • 在 ES 中可以不建立 index 直接进行数据导入,这时候 ES 会自动创建一个新的索引,针对字符串类型的字段 ES 会创建一个既有 text 类型的字段又有 keyword 类型的字段,这就是 ES 的 multi fields 特性,mapping 如下:

    "k4": {
          
          
        "type": "text",
        "fields": {
          
          
            "keyword": {
          
           
                "type": "keyword",
                "ignore_above": 256
            }
        } 
    }
    

    对 k4 进行条件过滤时比如=,Doris On ES 会将查询转换为 ES 的 TermQuery。

    SQL 过滤条件:
    k4 = "Doris On ES"
    转换成 ES 的 query DSL 为:
    "term" : {
          
          
     "k4": "Doris On ES"
    }
    

    因为 k4 的第一字段类型为 text,在数据导入的时候就会根据 k4 设置的分词器(如果没有设置,就是 standard 分词器)进行分词处理得到 doris、on、es 三个 Term,如下 ES analyze API 分析:

    POST /_analyze
    {
          
          
        "analyzer": "standard",
        "text": "Doris On ES"
    }
    

    分词的结果是:

    {
          
          
        "tokens": [
            {
          
          
                "token": "doris",
                "start_offset": 0,
                "end_offset": 5,
                "type": "<ALPHANUM>",
                "position": 0
            },
            {
          
          
                "token": "on",
                "start_offset": 6,
                "end_offset": 8,
                "type": "<ALPHANUM>",
                "position": 1
            },
            {
          
          
                "token": "es",
                "start_offset": 9,
                "end_offset": 11,
                "type": "<ALPHANUM>",
                "position": 2
            }
        ] 
    }
    

    查询时使用的是:

    "term" : {
          
          
        "k4": "Doris On ES"
    }
    

    Doris On ES 这个 term 匹配不到词典中的任何 term,不会返回任何结果,而启用enable_keyword_sniff: true 会自动将 k4 = "Doris On ES"转换成 k4.keyword = "Doris On ES"来完全匹配 SQL 语义,转换后的 ES query DSL 为:

    "term" : {
          
          
        "k4.keyword": "Doris On ES"
    }
    

    k4.keyword 的类型是 keyword,数据写入 ES 中是一个完整的 term,所以可以匹配。

(3)开启节点自动发现

"nodes_discovery" = "true"

参数说明:

  • 是否开启 es 节点发现,默认为 true。
  • 当配置为 true 时,Doris 将从 ES 找到所有可用的相关数据节点(在上面分配的分片)。如果 ES 数据节点的地址没有被 Doris BE 访问,则设置为 false。ES 集群部署在与公共 Internet隔离的内网,用户通过代理访问。

(4)配置 https 访问模式

"http_ssl_enabled" = "true"

参数说明:

  • ES 集群是否开启 https 访问模式。
  • 目前 fe/be 实现方式为信任所有,这是临时解决方案,后续会使用真实的用户配置证书。

查询用法

完成在 Doris 中建立 ES 外表后,除了无法使用 Doris 中的数据模型(rollup、预聚合、物化视图等)外并无区别。

(1)基本查询

select * from es_table where k1 > 1000 and k3 ='term' or k4 like 'fu*z_'

(2)扩展的 esquery(field, QueryDSL)

通过 esquery(field, QueryDSL)函数将一些无法用 sql 表述的 query 如 match_phrase、geoshape 等下推给 ES 进行过滤处理,esquery 的第一个列名参数用于关联 index,第二个参数是 ES 的基本 Query DSL 的 json 表述,使用花括号{}包含,json 的 root key 有且只能有一个,如 match_phrase、geo_shape、bool 等。

  • match_phrase 查询:

    select * from es_table where esquery(k4, '{"match_phrase": {"k4": "doris on es"}}');
    
  • geo 相关查询:

    select * from es_table where esquery(k4, '{
          
          
      "geo_shape": {
          
          
        "location": {
          
          
            "shape": {
          
          
                "type": "envelope",
                "coordinates": [
                    [
                        13,
                        53
                    ],
                    [
                        14,
                        52
                    ]
                ]
            },
            "relation": "within"
        }
    }                              
    }');
    
  • bool 查询:

    select * from es_table where esquery(k4, ' {
        "bool": {
            "must": [
                {
                    "terms": {
                        "k1": [
                            11,
                            12
                        ]
                    }
                },
                {
                    "terms": {
                        "k2": [
                            100
                        ]
                    }
                }
            ]
        }
    }');
    

使用建议

(1)时间类型字段使用建议

在 ES 中,时间类型的字段使用十分灵活,但是在 Doris On ES 中如果对时间类型字段的类型设置不当,则会造成过滤条件无法下推。

创建索引时对时间类型格式的设置做最大程度的格式兼容:

"dt": {
    
    
    "type": "date",
    "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}

在 Doris 中建立该字段时建议设置为 date 或 datetime,也可以设置为 varchar 类型, 使用如下 SQL 语句都可以直接将过滤条件下推至 ES:

select * from doe where k2 > '2020-06-21';
select * from doe where k2 < '2020-06-21 12:00:00'; 
select * from doe where k2 < 1593497011; 
select * from doe where k2 < now();
select * from doe where k2 < date_format(now(), '%Y-%m-%d');

注意:

  • 在 ES 中如果不对时间类型的字段设置 format, 默认的时间类型字段格式为strict_date_optional_time||epoch_millis
  • 导入到 ES 的日期字段如果是时间戳需要转换成 ms, ES 内部处理时间戳都是按照ms 进行处理的, 否则 Doris On ES 会出现显示错误。

(2)获取 ES 元数据字段_id

导入文档在不指定_id 的情况下 ES 会给每个文档分配一个全局唯一的_id 即主键, 用户也可以在导入时为文档指定一个含有特殊业务意义的_id; 如果需要在 Doris On ES 中获取该字段值,建表时可以增加类型为 varchar 的_id 字段:

CREATE EXTERNAL TABLE `doe` (
    `_id` varchar COMMENT "",
    `city` varchar COMMENT ""
) ENGINE=ELASTICSEARCH
PROPERTIES (
    "hosts" = "http://127.0.0.1:8200",
    "user" = "root",
    "password" = "root",
    "index" = "doe",
    "type" = "doc"
)

注意:

  • _id 字段的过滤条件仅支持=和 in 两种
  • _id 字段只能是 varchar 类型

JDBC外表

JDBC External Table Of Doris 提供了Doris通过数据库访问的标准接口(JDBC)来访问外部表,外部表省去了繁琐的数据导入工作,让Doris可以具有了访问各式数据库的能力,并借助Doris本身的OLAP的能力来解决外部表的数据分析问题:

  1. 支持各种数据源接入Doris
  2. 支持Doris与各种数据源中的表联合查询,进行更加复杂的分析操作

通过JDBC_Resource来创建JDBC外表:

CREATE EXTERNAL RESOURCE jdbc_resource
properties (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url"="jdbc:mysql://192.168.0.1:3306/test?useCursorFetch=true",
    "driver_url"="http://IP:port/mysql-connector-java-5.1.47.jar",
    "driver_class"="com.mysql.jdbc.Driver"
);

CREATE EXTERNAL TABLE `baseall_mysql` (
    `k1` tinyint(4) NULL,
    `k2` smallint(6) NULL,
    `k3` int(11) NULL,
    `k4` bigint(20) NULL,
    `k5` decimal(9, 3) NULL
) ENGINE=JDBC
PROPERTIES (
    "resource" = "jdbc_resource",
    "table" = "baseall",
    "table_type"="mysql"
);

参数说明:

参数 说明
type “jdbc”, 必填项标志资源类型
user 访问外表数据库所使的用户名
password 该用户对应的密码信息
jdbc_url JDBC的URL协议,包括数据库类型,IP地址,端口号和数据库名,不同数据库协议格式不一样。例如mysql: “jdbc:mysql://127.0.0.1:3306/test?useCursorFetch=true”。
driver_class 访问外表数据库的驱动包类名,例如mysql是:com.mysql.jdbc.Driver.
driver_url 用于下载访问外部数据库的jar包驱动URL。http://IP:port/mysql-connector-java-5.1.47.jar。本地单机测试时,可将jar包放在本地路径下,“driver_url”=“file:///home/disk1/pathTo/mysql-connector-java-5.1.47.jar”,多机时需保证具有完全相同的路径信息。
resource 在Doris中建立外表时依赖的资源名,对应上步创建资源时的名字。
table 在Doris中建立外表时,与外部数据库相映射的表名。
table_type 在Doris中建立外表时,该表来自那个数据库。例如mysql,postgresql,sqlserver,oracle

如果你是本地路径方式,这里数据库驱动依赖的jar包,FE、BE节点都要放置。

Hive外表

Hive External Table of Doris 提供了 Doris 直接访问 Hive 外部表的能力,外部表省去了繁琐的数据导入工作,并借助 Doris 本身的 OLAP 的能力来解决 Hive 表的数据分析问题:

  1. 支持 Hive 数据源接入Doris
  2. 支持 Doris 与 Hive 数据源中的表联合查询,进行更加复杂的分析操作
  3. 支持 访问开启 kerberos 的 Hive 数据源
  4. 支持 访问数据存储在腾讯 CHDFS 上的 Hive 数据源

创建:

-- 语法
CREATE [EXTERNAL] TABLE table_name (
    col_name col_type [NULL | NOT NULL] [COMMENT "comment"]
) ENGINE=HIVE
[COMMENT "comment"]
PROPERTIES (
    'property_name'='property_value',
    ...
);

-- 例子1:创建 Hive 集群中 hive_db 下的 hive_table 表
CREATE TABLE `t_hive` (
    `k1` int NOT NULL COMMENT "",
    `k2` char(10) NOT NULL COMMENT "",
    `k3` datetime NOT NULL COMMENT "",
    `k5` varchar(20) NOT NULL COMMENT "",
    `k6` double NOT NULL COMMENT ""
) ENGINE=HIVE
COMMENT "HIVE"
PROPERTIES (
    'hive.metastore.uris' = 'thrift://192.168.0.1:9083',
    'database' = 'hive_db',
    'table' = 'hive_table'
);

-- 例子2:创建 Hive 集群中 hive_db 下的 hive_table 表,HDFS使用HA配置
CREATE TABLE `t_hive` (
    `k1` int NOT NULL COMMENT "",
    `k2` char(10) NOT NULL COMMENT "",
    `k3` datetime NOT NULL COMMENT "",
    `k5` varchar(20) NOT NULL COMMENT "",
    `k6` double NOT NULL COMMENT ""
) ENGINE=HIVE
COMMENT "HIVE"
PROPERTIES (
    'hive.metastore.uris' = 'thrift://192.168.0.1:9083',
    'database' = 'hive_db',
    'table' = 'hive_table',
    'dfs.nameservices'='hacluster',
    'dfs.ha.namenodes.hacluster'='n1,n2',
    'dfs.namenode.rpc-address.hacluster.n1'='192.168.0.1:8020',
    'dfs.namenode.rpc-address.hacluster.n2'='192.168.0.2:8020',
    'dfs.client.failover.proxy.provider.hacluster'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);

-- 例子3:创建 Hive 集群中 hive_db 下的 hive_table 表, HDFS使用HA配置并开启kerberos认证方式
CREATE TABLE `t_hive` (
    `k1` int NOT NULL COMMENT "",
    `k2` char(10) NOT NULL COMMENT "",
    `k3` datetime NOT NULL COMMENT "",
    `k5` varchar(20) NOT NULL COMMENT "",
    `k6` double NOT NULL COMMENT ""
) ENGINE=HIVE
COMMENT "HIVE"
PROPERTIES (
    'hive.metastore.uris' = 'thrift://192.168.0.1:9083',
    'database' = 'hive_db',
    'table' = 'hive_table',
    'dfs.nameservices'='hacluster',
    'dfs.ha.namenodes.hacluster'='n1,n2',
    'dfs.namenode.rpc-address.hacluster.n1'='192.168.0.1:8020',
    'dfs.namenode.rpc-address.hacluster.n2'='192.168.0.2:8020',
    'dfs.client.failover.proxy.provider.hacluster'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider',
    'dfs.namenode.kerberos.principal'='hadoop/[email protected]'
    'hadoop.security.authentication'='kerberos',
    'hadoop.kerberos.principal'='[email protected]',
    'hadoop.kerberos.keytab'='/path/to/doris_test.keytab'
);

-- 例子4:创建 Hive 集群中 hive_db 下的 hive_table 表, Hive数据存储在S3上
CREATE TABLE `t_hive` (
    `k1` int NOT NULL COMMENT "",
    `k2` char(10) NOT NULL COMMENT "",
    `k3` datetime NOT NULL COMMENT "",
    `k5` varchar(20) NOT NULL COMMENT "",
    `k6` double NOT NULL COMMENT ""
) ENGINE=HIVE
COMMENT "HIVE"
PROPERTIES (
    'hive.metastore.uris' = 'thrift://192.168.0.1:9083',
    'database' = 'hive_db',
    'table' = 'hive_table',
    'AWS_ACCESS_KEY' = 'your_access_key',
    'AWS_SECRET_KEY' = 'your_secret_key',
    'AWS_ENDPOINT' = 's3.us-east-1.amazonaws.com',
    'AWS_REGION' = 'us-east-1'
);

参数说明:

  • 外表列
    • 列名要于 Hive 表一一对应
    • 列的顺序需要与 Hive 表一致
    • 必须包含 Hive 表中的全部列
    • Hive 表分区列无需指定,与普通列一样定义即可。
  • ENGINE 需要指定为 HIVE
  • PROPERTIES 属性:
    • hive.metastore.uris:Hive Metastore 服务地址
    • database:挂载 Hive 对应的数据库名
    • table:挂载 Hive 对应的表名
    • hadoop.username: 访问hdfs用户名,当认证为simple时需要
    • dfs.nameservices:name service名称,与hdfs-site.xml保持一致
    • `dfs.ha.namenodes.[nameservice ID]:namenode的id列表,与hdfs-site.xml保持一致
    • dfs.namenode.rpc-address.[nameservice ID].[name node ID]:Name node的rpc地址,数量与namenode数量相同,与hdfs-site.xml保持一致
    • dfs.client.failover.proxy.provider.[nameservice ID] :HDFS客户端连接活跃namenode的java类,通常是"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
  • 访问开启kerberos的Hive数据源,需要为Hive外表额外配置如下 PROPERTIES 属性:
    • hadoop.security.authentication:认证方式请设置为 kerberos,默认为simple
    • dfs.namenode.kerberos.principal:HDFS namenode 服务的Kerberos 主体
    • hadoop.kerberos.principal:设置 Doris 连接 HDFS 时使用的 Kerberos 主体
    • hadoop.kerberos.keytab:设置 keytab 本地文件路径
    • AWS_ACCESS_KEY: AWS账户的access key id.
    • AWS_SECRET_KEY: AWS账户的secret access key.
    • AWS_ENDPOINT: S3 endpoint. 例如:s3.us-east-1.amazonaws.com
    • AWS_REGION: AWS区域. 例如:us-east-1

注意:

  • 若要使 Doris 访问开启kerberos认证方式的hadoop集群,需要在 Doris 集群所有运行节点上部署 Kerberos 客户端 kinit,并配置 krb5.conf,填写KDC 服务信息等。
  • PROPERTIES 属性 hadoop.kerberos.keytab 的值需要指定 keytab 本地文件的绝对路径,并允许 Doris 进程访问该本地文件。
  • 关于HDFS集群的配置可以写入hdfs-site.xml文件中,该配置文件在fe和be的conf目录下,用户创建Hive表时,不需要再填写HDFS集群配置的相关信息。

支持的 Hive 列类型与 Doris 对应关系如下表:

Hive Doris 描述
BOOLEAN BOOLEAN
CHAR CHAR 当前仅支持UTF8编码
VARCHAR VARCHAR 当前仅支持UTF8编码
TINYINT TINYINT
SMALLINT SMALLINT
INT INT
BIGINT BIGINT
FLOAT FLOAT
DOUBLE DOUBLE
DECIMAL DECIMAL
DATE DATE
TIMESTAMP DATETIME Timestamp 转成 Datetime 会损失精度

多源数据目录(※)

基本概念

多源数据目录(Multi-Catalog)是 Doris 1.2.0 版本中推出的功能,旨在能够更方便对接外部数据目录,以增强Doris的数据湖分析和联邦数据查询能力。

上诉JDBC、ODBC、ES、Hive外表的方式不建议使用了。

在之前的 Doris 版本中,用户数据只有两个层级:Database 和 Table。当我们需要连接一个外部数据目录时,我们只能在Database 或 Table 层级进行对接。比如通过 create external table 的方式创建一个外部数据目录中的表的映射,或通过 create external database 的方式映射一个外部数据目录中的 Database。 如果外部数据目录中的 Database 或 Table 非常多,则需要用户手动进行一一映射,使用体验不佳。

而新的 Multi-Catalog 功能在原有的元数据层级上,新增一层Catalog,构成 Catalog -> Database -> Table 的三层元数据层级。其中,Catalog 可以直接对应到外部数据目录。目前支持的外部数据目录包括:

  1. Hive
  2. Iceberg
  3. Hudi
  4. Elasticsearch
  5. JDBC: 对接数据库访问的标准接口(JDBC)来访问各式数据库的数据。

该功能将作为之前外表连接方式(External Table)的补充和增强,帮助用户进行快速的多数据目录联邦查询。

有以下概念:

  1. Internal Catalog

    Doris 原有的 Database 和 Table 都将归属于 Internal Catalog。Internal Catalog 是内置的默认 Catalog,用户不可修改或删除。

  2. External Catalog

    可以通过 CREATE CATALOG 命令创建一个 External Catalog。创建后,可以通过 SHOW CATALOGS 命令查看已创建的 Catalog。

  3. 切换 Catalog

    用户登录 Doris 后,默认进入 Internal Catalog,因此默认的使用和之前版本并无差别,可以直接使用 SHOW DATABASESUSE DB 等命令查看和切换数据库。

    用户可以通过SWITCH命令切换 Catalog。如:

    SWITCH internal;
    SWITCH hive_catalog;
    

    切换后,可以直接通过 SHOW DATABASESUSE DB 等命令查看和切换对应 Catalog 中的 Database。Doris 会自动通过 Catalog 中的 Database 和 Table。用户可以像使用 Internal Catalog 一样,对 External Catalog 中的数据进行查看和访问。

    当前,Doris 只支持对 External Catalog 中的数据进行只读访问。

  4. 删除 Catalog

    External Catalog 中的 Database 和 Table 都是只读的。但是可以删除 Catalog(Internal Catalog无法删除)。可以通过 DROP CATALOG命令删除一个 External Catalog。

    该操作仅会删除 Doris 中该 Catalog 的映射信息,并不会修改或变更任何外部数据目录的内容。

  5. Resource

    Resource 是一组配置的集合。用户可以通过 CREATE RESOURCE 命令创建一个 Resource。之后可以在创建 Catalog 时使用这个 Resource。

    一个 Resource 可以被多个 Catalog 使用,以复用其中的配置。

Hive

通过连接 Hive Metastore,或者兼容 Hive Metatore 的元数据服务,Doris 可以自动获取 Hive 的库表信息,并进行数据查询。

除了 Hive 外,很多其他系统也会使用 Hive Metastore 存储元数据。所以通过 Hive Catalog,我们不仅能访问 Hive,也能访问使用 Hive Metastore 作为元数据存储的系统。如 Iceberg、Hudi 等。

使用限制:

  1. hive 支持 1/2/3 版本。
  2. 支持 Managed Table 和 External Table。
  3. 可以识别 Hive Metastore 中存储的 hive、iceberg、hudi 元数据。
  4. 支持数据存储在 Juicefs 上的 hive 表,用法如下(需要把juicefs-hadoop-x.x.x.jar放在 fe/lib/ 和 apache_hdfs_broker/lib/ 下)。

(1)创建Catalog

CREATE CATALOG hive PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'hive.metastore.sasl.enabled' = 'true',
    'hive.metastore.kerberos.principal' = 'your-hms-principal',
    'dfs.nameservices'='your-nameservice',
    'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007',
    'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007',
    'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider',
    'hadoop.security.authentication' = 'kerberos',
    'hadoop.kerberos.keytab' = '/your-keytab-filepath/your.keytab',   
    'hadoop.kerberos.principal' = '[email protected]',
    'yarn.resourcemanager.principal' = 'your-rm-principal'
);

除了 typehive.metastore.uris 两个必须参数外,还可以通过更多参数来传递连接所需要的信息。

在所有的 BEFE 节点下放置 krb5.conf 文件和 keytab 认证文件,keytab 认证文件路径和配置保持一致,krb5.conf 文件默认放置在 /etc/krb5.conf 路径。 hive.metastore.kerberos.principal 的值需要和所连接的 hive metastore 的同名属性保持一致,可从 hive-site.xml 中获取。

提供 Hadoop KMS 加密传输信息,示例如下:

CREATE CATALOG hive PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'dfs.encryption.key.provider.uri' = 'kms://http@kms_host:kms_port/kms'
);

其它存储:

# hive数据存储在JuiceFS,示例如下:
CREATE CATALOG hive PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'hadoop.username' = 'root',
    'fs.jfs.impl' = 'io.juicefs.JuiceFileSystem',
    'fs.AbstractFileSystem.jfs.impl' = 'io.juicefs.JuiceFS',
    'juicefs.meta' = 'xxx'
);
# hive元数据存储在Glue,数据存储在S3,示例如下:
CREATE CATALOG hive PROPERTIES (
    "type"="hms",
    "hive.metastore.type" = "glue",
    "aws.region" = "us-east-1",
    "aws.glue.access-key" = "ak",
    "aws.glue.secret-key" = "sk",
    "AWS_ENDPOINT" = "s3.us-east-1.amazonaws.com",
    "AWS_REGION" = "us-east-1",
    "AWS_ACCESS_KEY" = "ak",
    "AWS_SECRET_KEY" = "sk",
    "use_path_style" = "true"
);

在 1.2.1 版本之后,我们也可以将这些信息通过创建一个 Resource 统一存储,然后在创建 Catalog 时使用这个 Resource。示例如下:

# 1. 创建 Resource
CREATE RESOURCE hms_resource PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'hadoop.username' = 'hive',
    'dfs.nameservices'='your-nameservice',
    'dfs.ha.namenodes.your-nameservice'='nn1,nn2',
    'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007',
    'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007',
    'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);
    
# 2. 创建 Catalog 并使用 Resource,这里的 Key Value 信息会覆盖 Resource 中的信息。
CREATE CATALOG hive WITH RESOURCE hms_resource PROPERTIES(
    'key' = 'value'
);

我们也可以直接将 hive-site.xml 放到 FE 和 BE 的 conf 目录下,系统也会自动读取 hive-site.xml 中的信息。信息覆盖的规则如下:

  • Resource 中的信息覆盖 hive-site.xml 中的信息。
  • CREATE CATALOG PROPERTIES 中的信息覆盖 Resource 中的信息。

连接开启 Ranger 权限校验的 Hive Metastore 需要增加配置 & 配置环境:

  • 创建 Catalog 时增加:

    "access_controller.properties.ranger.service.name" = "hive",
    "access_controller.class" = "org.apache.doris.catalog.authorizer.RangerHiveAccessControllerFactory",
    
  • 配置所有 FE 环境:

    ①将 HMS conf 目录下的配置文件ranger-hive-audit.xml,ranger-hive-security.xml,ranger-policymgr-ssl.xml复制到 <doris_home>/conf 目录下。

    ②修改 ranger-hive-security.xml 的属性,参考配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
    <configuration>
        #The directory for caching permission data, needs to be writable
        <property>
            <name>ranger.plugin.hive.policy.cache.dir</name>
            <value>/mnt/datadisk0/zhangdong/rangerdata</value>
        </property>
        #The time interval for periodically pulling permission data
        <property>
            <name>ranger.plugin.hive.policy.pollIntervalMs</name>
            <value>30000</value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.policy.rest.client.connection.timeoutMs</name>
            <value>60000</value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.policy.rest.client.read.timeoutMs</name>
            <value>60000</value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.policy.rest.ssl.config.file</name>
            <value></value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.policy.rest.url</name>
            <value>http://172.21.0.32:6080</value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.policy.source.impl</name>
            <value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
        </property>
    
        <property>
            <name>ranger.plugin.hive.service.name</name>
            <value>hive</value>
        </property>
    
        <property>
            <name>xasecure.hive.update.xapolicies.on.grant.revoke</name>
            <value>true</value>
        </property>
    
    </configuration>
    

    ③为获取到 Ranger 鉴权本身的日志,可在 <doris_home>/conf 目录下添加配置文件 log4j.properties。

    ④重启 FE。

(2)查看 Catalog

mysql> SHOW CATALOGS;
+-----------+-------------+----------+
| CatalogId | CatalogName | Type     |
+-----------+-------------+----------+
|     10024 | hive        | hms      |
|         0 | internal    | internal |
+-----------+-------------+----------+

(3)切换 Catalog

通过 SWITCH 命令切换到 hive catalog,并查看其中的数据库:

mysql> SWITCH hive;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW DATABASES;
+-----------+
| Database  |
+-----------+
| default   |
| random    |
| ssb100    |
| tpch1     |
| tpch100   |
| tpch1_orc |
+-----------+

(4)使用 Catalog

切换到 Catalog 后,则可以正常使用内部数据源的功能。

如切换到 tpch100 数据库,并查看其中的表:

mysql> USE tpch100;
Database changed

mysql> SHOW TABLES;
+-------------------+
| Tables_in_tpch100 |
+-------------------+
| customer          |
| lineitem          |
| nation            |
| orders            |
| part              |
| partsupp          |
| region            |
| supplier          |
+-------------------+

(5)查询

SELECT l_shipdate, l_orderkey, l_partkey FROM lineitem limit 10;
# 也可以和其他数据目录中的表进行关联查询:
SELECT l.l_shipdate FROM hive.tpch100.lineitem l WHERE l.l_partkey IN (SELECT p_partkey FROM internal.db1.part) LIMIT 10;

这里我们通过 catalog.database.table 这种全限定的方式标识一张表,如:internal.db1.part

其中 catalogdatabase 可以省略,缺省使用当前 SWITCH 和 USE 后切换的 catalog 和 database。

可以通过 INSERT INTO 命令,将 hive catalog 中的表数据,插入到 interal catalog 中的内部表,从而达到导入外部数据目录数据的效果:

mysql> SWITCH internal;
Query OK, 0 rows affected (0.00 sec)

mysql> USE db1;
Database changed

mysql> INSERT INTO part SELECT * FROM hive.tpch100.part limit 1000;
Query OK, 1000 rows affected (0.28 sec)
{
   
   'label':'insert_212f67420c6444d5_9bfc184bf2e7edb8', 'status':'VISIBLE', 'txnId':'4'}

lceberg

使用限制:

  • 支持 Iceberg V1/V2 表格式。
  • V2 格式仅支持 Position Delete 方式,不支持 Equality Delete。

(1)基于Hive Metastore创建Catalog

和 Hive Catalog 基本一致,这里仅给出简单示例:

CREATE CATALOG iceberg PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'hadoop.username' = 'hive',
    'dfs.nameservices'='your-nameservice',
    'dfs.ha.namenodes.your-nameservice'='nn1,nn2',
    'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007',
    'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007',
    'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);

(2)基于Iceberg API创建Catalog

  • Hive Metastore作为元数据服务

    CREATE CATALOG iceberg PROPERTIES (
        'type'='iceberg',
        'iceberg.catalog.type'='hms',
        'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
        'hadoop.username' = 'hive',
        'dfs.nameservices'='your-nameservice',
        'dfs.ha.namenodes.your-nameservice'='nn1,nn2',
        'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007',
        'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007',
        'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
    );
    
  • Glue Catalog作为元数据服务

    CREATE CATALOG glue PROPERTIES (
        "type"="iceberg",
        "iceberg.catalog.type" = "glue",
        "glue.endpoint" = "https://glue.us-east-1.amazonaws.com",
        "warehouse" = "s3://bucket/warehouse",
        "AWS_ENDPOINT" = "s3.us-east-1.amazonaws.com",
        "AWS_REGION" = "us-east-1",
        "AWS_ACCESS_KEY" = "ak",
        "AWS_SECRET_KEY" = "sk",
        "use_path_style" = "true"
    );
    

    glue.endpoint: Glue Endpoint.

    warehouse: Glue Warehouse Location. Glue Catalog的根路径,用于指定数据存放位置。

  • REST Catalog作为元数据服务

    该方式需要预先提供REST服务,用户需实现获取Iceberg元数据的REST接口。

    CREATE CATALOG iceberg PROPERTIES (
        'type'='iceberg',
        'iceberg.catalog.type'='rest',
        'uri' = 'http://172.21.0.1:8181',
    );
    

    若数据存放在S3上,properties中可以使用以下参数:

    "AWS_ACCESS_KEY" = "ak"
    "AWS_SECRET_KEY" = "sk"
    "AWS_REGION" = "region-name"
    "AWS_ENDPOINT" = "http://endpoint-uri"
    "AWS_CREDENTIALS_PROVIDER" = "provider-class-name" // 可选,默认凭证类基于BasicAWSCredentials实现。
    

Hudi

使用限制:

  • Hudi 目前仅支持 Copy On Write 表的 Snapshot Query,以及 Merge On Read 表的 Read Optimized Query。后续将支持 Incremental Query 和 Merge On Read 表的 Snapshot Query。
  • 目前仅支持 Hive Metastore 类型的 Catalog。所以使用方式和 Hive Catalog 基本一致。后续版本将支持其他类型的 Catalog。

和 Hive Catalog 基本一致,这里仅给出简单示例:

CREATE CATALOG hudi PROPERTIES (
    'type'='hms',
    'hive.metastore.uris' = 'thrift://172.21.0.1:7004',
    'hadoop.username' = 'hive',
    'dfs.nameservices'='your-nameservice',
    'dfs.ha.namenodes.your-nameservice'='nn1,nn2',
    'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007',
    'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007',
    'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);

ES

Elasticsearch Catalog 除了支持自动映射 ES 元数据外,也可以利用 Doris 的分布式查询规划能力和 ES(Elasticsearch) 的全文检索能力相结合,提供更完善的 OLAP 分析场景解决方案。

CREATE CATALOG es PROPERTIES (
    "type"="es",
    "hosts"="http://127.0.0.1:9200"
);

因为 Elasticsearch 没有 Database 的概念,所以连接 ES 后,会自动生成一个唯一的 Database:default_db

并且在通过 SWITCH 命令切换到 ES Catalog 后,会自动切换到 default_db。无需再执行 USE default_db 命令。

参数:

参数 是否必须 默认值 说明
hosts ES 地址,可以是一个或多个,也可以是 ES 的负载均衡地址
user ES 用户名
password 对应用户的密码信息
doc_value_scan true 是否开启通过 ES/Lucene 列式存储获取查询字段的值
keyword_sniff true 是否对 ES 中字符串分词类型 text.fields 进行探测,通过 keyword 进行查询。设置为 false 会按照分词后的内容匹配
nodes_discovery true 是否开启 ES 节点发现,默认为 true,在网络隔离环境下设置为 false,只连接指定节点
ssl false ES 是否开启 https 访问模式,目前在 fe/be 实现方式为信任所有
mapping_es_id false 是否映射 ES 索引中的 _id 字段
like_push_down true 是否将 like 转化为 wildchard 下推到 ES,会增加 ES cpu 消耗
  • 认证方式目前仅支持 Http Basic 认证,并且需要确保该用户有访问: /_cluster/state/、_nodes/http 等路径和 index 的读权限; 集群未开启安全认证,用户名和密码不需要设置。
  • 5.x 和 6.x 中一个 index 中的多个 type 默认取第一个。

JDBC

JDBC Catalog 通过标准 JDBC 协议,连接其他数据源。

连接后,Doris 会自动同步数据源下的 Database 和 Table 的元数据,以便快速访问这些外部数据。

使用限制:仅支持 MySQL、PostgreSQL、Oracle、SQLServer、Clickhouse、Doris

(1)MySQL

CREATE CATALOG jdbc_mysql PROPERTIES (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url" = "jdbc:mysql://127.0.0.1:3306/demo",
    "driver_url" = "mysql-connector-java-5.1.47.jar",
    "driver_class" = "com.mysql.jdbc.Driver"
)

(2)PostgreSQL

CREATE CATALOG jdbc_postgresql PROPERTIES (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url" = "jdbc:postgresql://127.0.0.1:5449/demo",
    "driver_url" = "postgresql-42.5.1.jar",
    "driver_class" = "org.postgresql.Driver"
);

映射关系如下:

Doris PostgreSQL
Catalog Database
Database Schema
Table Table

(3)Oracle

CREATE CATALOG jdbc_oracle PROPERTIES (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url" = "jdbc:oracle:thin:@127.0.0.1:1521:helowin",
    "driver_url" = "ojdbc6.jar",
    "driver_class" = "oracle.jdbc.driver.OracleDriver"
);

映射关系如下:

Doris Oracle
Catalog Database
Database User
Table Table

(4)Clickhouse

CREATE CATALOG jdbc_clickhouse PROPERTIES (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url" = "jdbc:clickhouse://127.0.0.1:8123/demo",
    "driver_url" = "clickhouse-jdbc-0.3.2-patch11-all.jar",
    "driver_class" = "com.clickhouse.jdbc.ClickHouseDriver"
);

(5)SQLServer

CREATE CATALOG sqlserver_catalog PROPERTIES (
    "type"="jdbc",
    "user"="SA",
    "password"="Doris123456",
    "jdbc_url" = "jdbc:sqlserver://localhost:1433;DataBaseName=doris_test",
    "driver_url" = "mssql-jdbc-11.2.3.jre8.jar",
    "driver_class" = "com.microsoft.sqlserver.jdbc.SQLServerDriver"
);

映射关系如下:

Doris SQLServer
Catalog Database
Database Schema
Table Table

(6)Doris

Jdbc Catalog也支持连接另一个Doris数据库:

CREATE CATALOG doris_catalog PROPERTIES (
    "type"="jdbc",
    "user"="root",
    "password"="123456",
    "jdbc_url" = "jdbc:mysql://127.0.0.1:9030?useSSL=false",
    "driver_url" = "mysql-connector-java-5.1.47.jar",
    "driver_class" = "com.mysql.jdbc.Driver"
);

猜你喜欢

转载自blog.csdn.net/qq_44766883/article/details/131353581