例行导入(Routine Load)功能,支持用户提交一个常驻的导入任务,通过不断的从指定的数据源读取数据,将数据导入到 Doris 中。
本文主要介绍该功能的实现原理、使用方式以及最佳实践。
+---------+
| Client |
+----+----+
|
+-----------------------------+
| FE | |
| +-----------v------------+ |
| | | |
| | Routine Load Job | |
| | | |
| +---+--------+--------+--+ |
| | | | |
| +---v--+ +---v--+ +---v--+ |
| | task | | task | | task | |
| +--+---+ +---+--+ +---+--+ |
| | | | |
+-----------------------------+
| | |
v v v
+---+--+ +--+---+ ++-----+
| BE | | BE | | BE |
+------+ +------+ +------+
如上图,Client 向 FE 提交一个Routine Load 作业。
FE 通过 JobScheduler 将一个导入作业拆分成若干个 Task。每个 Task 负责导入指定的一部分数据。Task 被 TaskScheduler 分配到指定的 BE 上执行。
在 BE 上,一个 Task 被视为一个普通的导入任务,通过 Stream Load 的导入机制进行导入。导入完成后,向 FE 汇报。
FE 中的 JobScheduler 根据汇报结果,继续生成后续新的 Task,或者对失败的 Task 进行重试。
整个 Routine Load 作业通过不断的产生新的 Task,来完成数据不间断的导入。
当前我们仅支持从 Kafka 进行例行导入。该部分会详细介绍 Kafka 例行导入使用方式和最佳实践。
创建例行导入任务的详细语法可以连接到 Doris 后,查看CREATE ROUTINE LOAD命令手册,或者执行 HELP ROUTINE LOAD;
查看语法帮助。
下面我们以几个例子说明如何创建Routine Load任务:
CREATE ROUTINE LOAD example_db.test1 ON example_tbl
COLUMNS TERMINATED BY ",",
COLUMNS(k1, k2, k3, v1, v2, v3 = k1 * 100)
PROPERTIES
(
"desired_concurrent_number"="3",
"max_batch_interval" = "20",
"max_batch_rows" = "300000",
"max_batch_size" = "209715200",
"strict_mode" = "false"
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
"kafka_topic" = "my_topic",
"property.group.id" = "xxx",
"property.client.id" = "xxx",
"property.kafka_default_offsets" = "OFFSET_BEGINNING"
);
CREATE ROUTINE LOAD example_db.test1 ON example_tbl
COLUMNS(k1, k2, k3, v1, v2, v3 = k1 * 100),
WHERE k1 > 100 and k2 like "%doris%"
PROPERTIES
(
"desired_concurrent_number"="3",
"max_batch_interval" = "20",
"max_batch_rows" = "300000",
"max_batch_size" = "209715200",
"strict_mode" = "true"
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
"kafka_topic" = "my_topic",
"kafka_partitions" = "0,1,2,3",
"kafka_offsets" = "101,0,0,200"
);
3. 导入Json格式数据使用示例
Routine Load导入的json格式仅支持以下两种
第一种只有一条记录,且为json对象: 当使用单表导入(即通过 ON TABLE_NAME 指定 表名)时,json 数据格式如下
{"category":"a9jadhx","author":"test","price":895}
当使用动态/多表导入 Routine Load (即不指定具体的表名)时,json 数据格式如下
table_name|{"category":"a9jadhx","author":"test","price":895}
假设我们需要导入数据到 user_address 以及 user_info 两张表,那么消息格式如下
eg: user_address 表的 json 数据
user_address|{"user_id":128787321878,"address":"朝阳区朝阳大厦XXX号","timestamp":1589191587}
eg: user_info 表的 json 数据
user_info|{"user_id":128787321878,"name":"张三","age":18,"timestamp":1589191587}
第二种为json数组,数组中可含多条记录
当使用单表导入(即通过 ON TABLE_NAME 指定 表名)时,json 数据格式如下
[
{
"category":"11",
"author":"4avc",
"price":895,
"timestamp":1589191587
},
{
"category":"22",
"author":"2avc",
"price":895,
"timestamp":1589191487
},
{
"category":"33",
"author":"3avc",
"price":342,
"timestamp":1589191387
}
]
当使用动态/多表导入(即不指定具体的表名)时,json 数据格式如下
table_name|[
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
},
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
},
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
}
]
同样我们以 user_address
以及 user_info
两张表为例,那么消息格式如下
eg: user_address 表的 json 数据
user_address|[
{
"category":"11",
"author":"4avc",
"price":895,
"timestamp":1589191587
},
{
"category":"22",
"author":"2avc",
"price":895,
"timestamp":1589191487
},
{
"category":"33",
"author":"3avc",
"price":342,
"timestamp":1589191387
}
]
eg: user_info 表的 json 数据
user_info|[
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
},
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
},
{
"user_id":128787321878,
"address":"朝阳区朝阳大厦XXX号",
"timestamp":1589191587
}
创建待导入的Doris数据表
CREATE TABLE `example_tbl` (
`category` varchar(24) NULL COMMENT "",
`author` varchar(24) NULL COMMENT "",
`timestamp` bigint(20) NULL COMMENT "",
`dt` int(11) NULL COMMENT "",
`price` double REPLACE
) ENGINE=OLAP
AGGREGATE KEY(`category`,`author`,`timestamp`,`dt`)
COMMENT "OLAP"
PARTITION BY RANGE(`dt`)
(
PARTITION p0 VALUES [("-2147483648"), ("20200509")),
PARTITION p20200509 VALUES [("20200509"), ("20200510")),
PARTITION p20200510 VALUES [("20200510"), ("20200511")),
PARTITION p20200511 VALUES [("20200511"), ("20200512"))
)
DISTRIBUTED BY HASH(`category`,`author`,`timestamp`) BUCKETS 4
PROPERTIES (
"replication_num" = "1"
);
以简单模式导入json数据
CREATE ROUTINE LOAD example_db.test_json_label_1 ON table1
COLUMNS(category,price,author)
PROPERTIES
(
"desired_concurrent_number"="3",
"max_batch_interval" = "20",
"max_batch_rows" = "300000",
"max_batch_size" = "209715200",
"strict_mode" = "false",
"format" = "json"
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
"kafka_topic" = "my_topic",
"kafka_partitions" = "0,1,2",
"kafka_offsets" = "0,0,0"
);
精准导入json格式数据
CREATE ROUTINE LOAD example_db.test1 ON example_tbl
COLUMNS(category, author, price, timestamp, dt=from_unixtime(timestamp, '%Y%m%d'))
PROPERTIES
(
"desired_concurrent_number"="3",
"max_batch_interval" = "20",
"max_batch_rows" = "300000",
"max_batch_size" = "209715200",
"strict_mode" = "false",
"format" = "json",
"jsonpaths" = "[\"$.category\",\"$.author\",\"$.price\",\"$.timestamp\"]",
"strip_outer_array" = "true"
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
"kafka_topic" = "my_topic",
"kafka_partitions" = "0,1,2",
"kafka_offsets" = "0,0,0"
);
注意: 表里的分区字段 dt
在我们的数据里并没有,而是在我们Routine load 语句里通过 dt=from_unixtime(timestamp, '%Y%m%d')
转换出来的
strict mode 与 source data 的导入关系
这里以列类型为 TinyInt 来举例
注:当表中的列允许导入空值时
source data | source data example | string to int | strict_mode | result |
---|---|---|---|---|
空值 | \N | N/A | true or false | NULL |
not null | aaa or 2000 | NULL | true | invalid data(filtered) |
not null | aaa | NULL | false | NULL |
not null | 1 | 1 | true or false | correct data |
这里以列类型为 Decimal(1,0) 举例
注:当表中的列允许导入空值时
source data | source data example | string to int | strict_mode | result |
---|---|---|---|---|
空值 | \N | N/A | true or false | NULL |
not null | aaa | NULL | true | invalid data(filtered) |
not null | aaa | NULL | false | NULL |
not null | 1 or 10 | 1 | true or false | correct data |
注意:10 虽然是一个超过范围的值,但是因为其类型符合 decimal的要求,所以 strict mode对其不产生影响。10 最后会在其他 ETL 处理流程中被过滤。但不会被 strict mode 过滤。
访问 SSL 认证的 Kafka 集群
访问 SSL 认证的 Kafka 集群需要用户提供用于认证 Kafka Broker 公钥的证书文件(ca.pem)。如果 Kafka 集群同时开启了客户端认证,则还需提供客户端的公钥(client.pem)、密钥文件(client.key),以及密钥密码。这里所需的文件需要先通过 CREAE FILE
命令上传到 Doris 中,并且 catalog 名称为 kafka
。CREATE FILE
命令的具体帮助可以参见 HELP CREATE FILE;
。这里给出示例:
上传文件
CREATE FILE "ca.pem" PROPERTIES("url" = "https://example_url/kafka-key/ca.pem", "catalog" = "kafka");
CREATE FILE "client.key" PROPERTIES("url" = "https://example_urlkafka-key/client.key", "catalog" = "kafka");
CREATE FILE "client.pem" PROPERTIES("url" = "https://example_url/kafka-key/client.pem", "catalog" = "kafka");
创建例行导入作业
CREATE ROUTINE LOAD db1.job1 on tbl1
PROPERTIES
(
"desired_concurrent_number"="1"
)
FROM KAFKA
(
"kafka_broker_list"= "broker1:9091,broker2:9091",
"kafka_topic" = "my_topic",
"property.security.protocol" = "ssl",
"property.ssl.ca.location" = "FILE:ca.pem",
"property.ssl.certificate.location" = "FILE:client.pem",
"property.ssl.key.location" = "FILE:client.key",
"property.ssl.key.password" = "abcdefg"
);
Doris 通过 Kafka 的 C++ API
librdkafka
来访问 Kafka 集群。librdkafka
所支持的参数可以参阅https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
访问 PLAIN 认证的 Kafka 集群
访问开启 PLAIN 认证的Kafka集群,需要增加以下配置:
创建例行导入作业
CREATE ROUTINE LOAD db1.job1 on tbl1
PROPERTIES (
"desired_concurrent_number"="1",
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092",
"kafka_topic" = "my_topic",
"property.security.protocol"="SASL_PLAINTEXT",
"property.sasl.mechanism"="PLAIN",
"property.sasl.username"="admin",
"property.sasl.password"="admin"
);
访问 Kerberos 认证的 Kafka 集群
SinceVersion 1.2
访问开启kerberos认证的Kafka集群,需要增加以下配置:
创建例行导入作业
CREATE ROUTINE LOAD db1.job1 on tbl1
PROPERTIES (
"desired_concurrent_number"="1",
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092",
"kafka_topic" = "my_topic",
"property.security.protocol" = "SASL_PLAINTEXT",
"property.sasl.kerberos.service.name" = "kafka",
"property.sasl.kerberos.keytab" = "/etc/krb5.keytab",
"property.sasl.kerberos.principal" = "[email protected]"
);
注意:
查看作业状态的具体命令和示例可以通过 HELP SHOW ROUTINE LOAD;
命令查看。
查看任务运行状态的具体命令和示例可以通过 HELP SHOW ROUTINE LOAD TASK;
命令查看。
只能查看当前正在运行中的任务,已结束和未开始的任务无法查看。
用户可以修改已经创建的作业。具体说明可以通过 HELP ALTER ROUTINE LOAD;
命令查看或参阅 ALTER ROUTINE LOAD。
用户可以通过 STOP/PAUSE/RESUME
三个命令来控制作业的停止,暂停和重启。可以通过 HELP STOP ROUTINE LOAD;
HELP PAUSE ROUTINE LOAD;
以及 HELP RESUME ROUTINE LOAD;
三个命令查看帮助和示例。
例行导入作业和 ALTER TABLE 操作的关系
例行导入作业和其他导入作业的关系(LOAD, DELETE, INSERT)
例行导入作业和 DROP DATABASE/TABLE 操作的关系
当例行导入对应的 database 或 table 被删除后,作业会自动 CANCEL。
kafka 类型的例行导入作业和 kafka topic 的关系
当用户在创建例行导入声明的 kafka_topic
在kafka集群中不存在时。
auto.create.topics.enable = true
,则 kafka_topic
会先被自动创建,自动创建的 partition 个数是由用户方的kafka集群中的 broker 配置 num.partitions
决定的。例行作业会正常的不断读取该 topic 的数据。auto.create.topics.enable = false
, 则 topic 不会被自动创建,例行作业会在没有读取任何数据之前就被暂停,状态为 PAUSED
。所以,如果用户希望当 kafka topic 不存在的时候,被例行作业自动创建的话,只需要将用户方的kafka集群中的 broker 设置 auto.create.topics.enable = true
即可。
在网络隔离的环境中可能出现的问题 在有些环境中存在网段和域名解析的隔离措施,所以需要注意
advertised.listeners
, advertised.listeners
中的地址必须能够被Doris服务访问关于指定消费的 Partition 和 Offset
Doris 支持指定 Partition 和 Offset 开始消费。新版中还支持了指定时间点进行消费的功能。这里说明下对应参数的配置关系。
有三个相关参数:
kafka_partitions
:指定待消费的 partition 列表,如:"0, 1, 2, 3"。kafka_offsets
:指定每个分区的起始offset,必须和 kafka_partitions
列表个数对应。如:"1000, 1000, 2000, 2000"property.kafka_default_offset
:指定分区默认的起始offset。在创建导入作业时,这三个参数可以有以下组合:
组合 | kafka_partitions |
kafka_offsets |
property.kafka_default_offset |
行为 |
---|---|---|---|---|
1 | No | No | No | 系统会自动查找topic对应的所有分区并从 OFFSET_END 开始消费 |
2 | No | No | Yes | 系统会自动查找topic对应的所有分区并从 default offset 指定的位置开始消费 |
3 | Yes | No | No | 系统会从指定分区的 OFFSET_END 开始消费 |
4 | Yes | Yes | No | 系统会从指定分区的指定offset 处开始消费 |
5 | Yes | No | Yes | 系统会从指定分区,default offset 指定的位置开始消费 |
STOP和PAUSE的区别
FE会自动定期清理STOP状态的ROUTINE LOAD,而PAUSE状态的则可以再次被恢复启用。
一些系统配置参数会影响例行导入的使用。
max_routine_load_task_concurrent_num
FE 配置项,默认为 5,可以运行时修改。该参数限制了一个例行导入作业最大的子任务并发数。建议维持默认值。设置过大,可能导致同时并发的任务数过多,占用集群资源。
max_routine_load_task_num_per_be
FE 配置项,默认为5,可以运行时修改。该参数限制了每个 BE 节点最多并发执行的子任务个数。建议维持默认值。如果设置过大,可能导致并发任务数过多,占用集群资源。
max_routine_load_job_num
FE 配置项,默认为100,可以运行时修改。该参数限制的例行导入作业的总数,包括 NEED_SCHEDULED, RUNNING, PAUSE 这些状态。超过后,不能在提交新的作业。
max_consumer_num_per_group
BE 配置项,默认为 3。该参数表示一个子任务中最多生成几个 consumer 进行数据消费。对于 Kafka 数据源,一个 consumer 可能消费一个或多个 kafka partition。假设一个任务需要消费 6 个 kafka partition,则会生成 3 个 consumer,每个 consumer 消费 2 个 partition。如果只有 2 个 partition,则只会生成 2 个 consumer,每个 consumer 消费 1 个 partition。
max_tolerable_backend_down_num FE 配置项,默认值是0。在满足某些条件下,Doris可PAUSED的任务重新调度,即变成RUNNING。该参数为0代表只有所有BE节点是alive状态才允许重新调度。
period_of_auto_resume_min FE 配置项,默认是5分钟。Doris重新调度,只会在5分钟这个周期内,最多尝试3次. 如果3次都失败则锁定当前任务,后续不在进行调度。但可通过人为干预,进行手动恢复。
关于 Routine Load 使用的更多详细语法,可以在Mysql客户端命令行下输入 HELP ROUTINE LOAD
获取更多帮助信息。