Druid批处理:大规模数据加载方案
【免费下载链接】druid 项目地址: https://gitcode.com/gh_mirrors/dr/druid
你是否在处理海量数据时遇到加载缓慢、资源占用过高的问题?是否需要一种既能保证数据完整性又能提升处理效率的解决方案?本文将详细介绍Apache Druid的批处理数据加载方案,帮助你快速掌握大规模数据的高效加载方法。读完本文后,你将了解Druid批处理的两种主要任务类型、并行索引的工作原理、配置方法以及实际操作流程,并能通过示例快速上手。
批处理任务类型
Apache Druid支持两种基于JSON的批处理索引任务,分别适用于不同的场景:
- Parallel task indexing(
index_parallel):可以同时运行多个索引任务,非常适合生产环境中的数据摄入任务。它能够充分利用集群资源,显著提高数据加载速度。 - Simple task indexing(
index):一次只能运行单个索引任务,适用于开发和测试环境。
本文主要介绍index_parallel任务的配置和使用,这是生产环境中推荐的批处理方案。有关批处理索引的更多相关信息,可以参考:
并行索引工作原理
index_parallel任务是一个多线程批处理索引任务,它仅依赖Druid自身资源,不依赖Hadoop等外部系统。其工作流程如下:
- 任务协调:
index_parallel任务作为协调任务协调整个索引过程,它会将输入数据分割并创建工作任务来处理各个数据部分。 - 任务调度:协调任务将工作任务提交给Overlord服务,Overlord在Middle Managers或Indexers上调度和运行这些工作任务。
- 结果汇报:每个工作任务成功处理分配的数据部分后,会向协调任务报告生成的段列表。
- 任务监控与重试:协调任务定期检查工作任务的状态,如果任务失败,会重试直到达到配置的重试次数上限。
- 段发布:当所有工作任务成功完成后,协调任务会一次性发布所有报告的段并完成数据摄入。
并行任务的详细行为因partitionsSpec的不同而有所差异,具体可参考partitionsSpec部分。
并行任务需要满足以下条件:
ioConfig中使用可分割的inputSource,具体支持的可分割输入格式可参考可分割输入源。tuningConfig中的maxNumConcurrentSubTasks大于1,否则任务将顺序运行。
数据加载界面
在Web控制台中,你可以通过Load Data UI来定义和提交摄入规范。以下是数据加载过程中的界面示例,展示了批处理数据加载的配置步骤:
提交索引任务
要运行基于JSON的批处理索引任务,可以通过以下两种方式:
使用Web控制台
通过Web控制台中的Load Data UI定义并提交摄入规范,这种方式直观易用,适合初学者。
使用API或脚本
- 根据示例和批处理索引参考主题定义JSON格式的摄入规范。
- 将摄入规范POST到Tasks API端点,即Overlord服务的
/druid/indexer/v1/task接口。 - 或者,使用Druid提供的索引脚本
bin/post-index-task。
并行索引示例
以下是一个并行索引任务的配置示例,你可以根据自己的需求进行修改:
点击查看示例
{
"type": "index_parallel",
"spec": {
"dataSchema": {
"dataSource": "wikipedia_parallel_index_test",
"timestampSpec": {
"column": "timestamp"
},
"dimensionsSpec": {
"dimensions": [
"country",
"page",
"language",
"user",
"unpatrolled",
"newPage",
"robot",
"anonymous",
"namespace",
"continent",
"region",
"city"
]
},
"metricsSpec": [
{
"type": "count",
"name": "count"
},
{
"type": "doubleSum",
"name": "added",
"fieldName": "added"
},
{
"type": "doubleSum",
"name": "deleted",
"fieldName": "deleted"
},
{
"type": "doubleSum",
"name": "delta",
"fieldName": "delta"
}
],
"granularitySpec": {
"segmentGranularity": "DAY",
"queryGranularity": "second",
"intervals": [
"2013-08-31/2013-09-02"
]
}
},
"ioConfig": {
"type": "index_parallel",
"inputSource": {
"type": "local",
"baseDir": "examples/indexing/",
"filter": "wikipedia_index_data*"
},
"inputFormat": {
"type": "json"
}
},
"tuningConfig": {
"type": "index_parallel",
"partitionsSpec": {
"type": "single_dim",
"partitionDimension": "country",
"targetRowsPerSegment": 5000000
},
"maxNumConcurrentSubTasks": 2
}
}
}
并行索引配置
并行索引任务的配置主要包括以下几个部分:
主要配置 sections
| 属性 | 描述 | 是否必填 |
|---|---|---|
type | 任务类型,对于并行任务索引,设置为index_parallel | 是 |
id | 任务ID,如果省略,Druid会使用任务类型、数据源名称、时间间隔和时间戳生成任务ID | 否 |
spec | 定义data schema、IO config和tuning config的摄入规范 | 是 |
context | 指定各种任务配置参数的上下文,详见任务上下文参数 | 否 |
dataSchema
该字段是必填项,它定义了Druid存储数据的方式,包括主时间戳列、维度、指标和任何转换。概述详见Ingestion Spec DataSchema。
为索引并行定义granularitySpec时,如果知道数据的时间范围,建议显式定义intervals。这样可以更快地发现锁定失败,并且Druid不会意外替换某些行包含意外时间戳的数据。
ioConfig
ioConfig的属性如下表所示:
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 任务类型,设置为index_parallel | 无 | 是 |
inputFormat | inputFormat,用于指定如何解析输入数据 | 无 | 是 |
appendToExisting | 创建段作为最新版本的额外分片,有效地追加到段集而不是替换它。这意味着可以将新段追加到任何数据源,无论其原始分区方案如何。必须对追加的段使用dynamic分区类型。如果指定了不同的分区类型,任务将失败并出错 | false | 否 |
dropExisting | 如果为true,且appendToExisting为false,并且granularitySpec包含interval,则摄入任务在发布新段时会替换所有完全包含在指定interval内的现有段。如果摄入失败,Druid不会更改任何现有段。在appendToExisting为true或granularitySpec中未指定interval的错误配置情况下,即使dropExisting为true,Druid也不会替换任何段。警告:此功能仍处于实验阶段 | false | 否 |
tuningConfig
tuningConfig是可选的,如果未指定,Druid会使用默认参数。其属性如下表所示:
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 任务类型,设置为index_parallel | 无 | 是 |
maxRowsInMemory | 确定Druid何时执行中间持久化到磁盘。通常不需要设置此值。根据数据的性质,如果行的字节数较短,例如,可能不希望在内存中存储一百万行,这时可以设置此值 | 1000000 | 否 |
maxBytesInMemory | 用于确定Druid何时执行中间持久化到磁盘。通常Druid会在内部计算此值,无需设置。此值表示在持久化之前在堆内存中聚合的字节数,基于内存使用的粗略估计,而非实际使用情况。索引的最大堆内存使用量为maxBytesInMemory * (2 + maxPendingPersists)。注意,maxBytesInMemory还包括从中间持久化创建的工件的堆使用量。这意味着每次持久化后,直到下一次持久化的maxBytesInMemory量会减少。当所有中间持久化工件的字节总和超过maxBytesInMemory时,任务失败 | JVM最大内存的1/6 | 否 |
maxColumnsToMerge | 发布时合并段时,单个阶段合并的段数限制。此限制会影响要合并的一组段中存在的总列数。如果超过限制,段合并会分多个阶段进行。无论此设置如何,每个阶段至少合并2个段 | -1(无限制) | 否 |
maxTotalRows | 已弃用,使用partitionsSpec代替。等待推送的段中的总行数,用于确定何时应进行中间推送 | 20000000 | 否 |
numShards | 已弃用,使用partitionsSpec代替。使用hashed partitionsSpec时直接指定要创建的分片数。如果指定了此值并且在granularitySpec中指定了intervals,索引任务可以跳过通过数据确定间隔/分区的过程 | null | 否 |
splitHintSpec | 用于控制每个第一阶段任务读取的数据量的提示。根据输入源的实现,Druid可能会忽略该提示,详见Split hint spec | 基于大小的拆分提示规范 | 否 |
partitionsSpec | 定义如何在每个timeChunk中分区数据,详见PartitionsSpec | 如果forceGuaranteedRollup = false,则为dynamic;如果forceGuaranteedRollup = true,则为hashed或single_dim | 否 |
indexSpec | 定义索引时使用的段存储格式选项,详见IndexSpec | null | 否 |
indexSpecForIntermediatePersists | 定义索引时用于中间持久化临时段的段存储格式选项。可以使用此配置禁用中间段的维度/指标压缩,以减少最终合并所需的内存。但是,如果禁用中间段的压缩,在Druid将它们合并为最终发布的段之前,页面缓存的使用可能会增加,详见IndexSpec | 与indexSpec相同 | 否 |
maxPendingPersists | 保持未开始的最大挂起持久化数。如果新的中间持久化超过此限制,摄入将阻塞,直到当前运行的持久化完成。索引的最大堆内存使用量与maxRowsInMemory * (2 + maxPendingPersists)成比例 | 0(意味着一个持久化可以与摄入同时运行,没有可以排队的持久化) | 否 |
forceGuaranteedRollup | 强制perfect rollup。完美的rollup可以优化生成段的总大小和查询时间,但会增加索引时间。如果为true,在granularitySpec中指定intervals,并对partitionsSpec使用hashed或single_dim。不能将此标志与IOConfig的appendToExisting结合使用,详见Segment pushing modes | false | 否 |
reportParseExceptions | 如果为true,Druid会抛出解析过程中遇到的异常并停止摄入;如果为false,Druid会跳过无法解析的行和字段 | false | 否 |
pushTimeout | 等待推送段的毫秒数,必须 >= 0,0表示永远等待 | 0 | 否 |
segmentWriteOutMediumFactory | 创建段时使用的段写出介质,详见SegmentWriteOutMediumFactory | 如果未指定,使用druid.peon.defaultSegmentWriteOutMediumFactory.type的值 | 否 |
maxNumConcurrentSubTasks | 可以同时并行运行的最大工作任务数。协调任务会生成工作任务,直到达到maxNumConcurrentSubTasks,而不管可用的任务槽数量。如果此值设置得太大,协调者可能会创建太多工作任务,从而阻碍其他摄入任务,详见Capacity planning | 1 | 否 |
maxRetry | 任务失败的最大重试次数 | 3 | 否 |
maxNumSegmentsToMerge | 当forceGuaranteedRollup为true时,合并阶段中单个任务可以同时合并的最大段数 | 100 | 否 |
totalNumMergeTasks | 当partitionsSpec设置为hashed或single_dim时,合并阶段中合并段的任务总数 | 10 | 否 |
taskStatusCheckPeriodMs | 检查运行任务状态的轮询周期(毫秒) | 1000 | 否 |
chatHandlerTimeout | 工作任务中报告推送段的超时时间 | PT10S | 否 |
chatHandlerNumRetries | 工作任务中报告推送段的重试次数 | 5 | 否 |
awaitSegmentAvailabilityTimeoutMillis | 摄入完成后等待新索引的段可用于查询的毫秒数。如果<= 0,则不等待。如果> 0,任务会等待协调器指示新段可用于查询。如果超时,任务成功退出,但段未确认可用于查询 | Long | 否(默认=0) |
Split Hint Spec
拆分提示规范用于帮助协调任务划分输入源,每个工作任务处理一个输入分区。可以控制第一阶段中每个工作任务读取的数据量。
Size-based Split Hint Spec
基于大小的拆分提示规范影响除HTTP输入源和SQL输入源之外的所有可拆分输入源。
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 设置为maxSize | 无 | 是 |
maxSplitSize | 单个子任务处理的输入文件的最大字节数。如果单个文件大于限制,Druid会在单个子任务中单独处理该文件。Druid不会跨任务拆分文件。即使多个文件的总大小小于maxSplitSize,一个子任务也不会处理超过maxNumFiles的文件。支持Human-readable format | 1GiB | 否 |
maxNumFiles | 单个子任务处理的最大输入文件数。此限制可避免摄入规范过长时导致任务失败。序列化摄入规范的最大大小有两个已知限制:ZooKeeper中的最大ZNode大小(jute.maxbuffer)和MySQL中的最大数据包大小(max_allowed_packet)。当序列化摄入规范大小达到其中一个限制时,可能会导致摄入任务失败。即使文件总数小于maxNumFiles,一个子任务也不会处理超过maxSplitSize的数据 | 1000 | 否 |
Segments Split Hint Spec
段拆分提示规范仅用于DruidInputSource。
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 设置为segments | 无 | 是 |
maxInputSegmentBytesPerTask | 单个子任务处理的输入段的最大字节数。如果单个段大于此数量,Druid会在单个子任务中单独处理该段。Druid永远不会跨任务拆分输入段。即使段总数小于maxNumSegments,一个子任务也不会处理超过maxInputSegmentBytesPerTask的数据。支持Human-readable format | 1GiB | 否 |
maxNumSegments | 单个子任务处理的最大输入段数。此限制可避免摄入规范过长时导致任务失败。序列化摄入规范的最大大小有两个已知限制:ZooKeeper中的最大ZNode大小(jute.maxbuffer)和MySQL中的最大数据包大小(max_allowed_packet)。当序列化摄入规范大小达到其中一个限制时,可能会导致摄入任务失败。即使段总数小于maxNumSegments,一个子任务也不会处理超过maxInputSegmentBytesPerTask的数据 | 1000 | 否 |
partitionsSpec
Druid的主要分区是时间,可以在分区规范中定义二级分区方法。根据rollup方法选择适用的partitionsSpec类型。
对于完美rollup,可以使用:
hashed:基于每行指定维度的哈希值进行分区single_dim:基于单个维度的值范围进行分区range:基于多个维度的值范围进行分区
对于最佳rollup,使用dynamic。
概述详见Partitioning。
不同partitionsSpec类型的特点如下表所示:
| PartitionsSpec | 摄入速度 | 分区方法 | 支持的rollup模式 | 查询时的二级分区剪枝 |
|---|---|---|---|---|
dynamic | 最快 | 基于段中行数的Dynamic partitioning | 最佳rollup | N/A |
hashed | 中等 | 基于多个维度的hash-based partitioning,通过改善数据局部性可能减少数据源大小和查询延迟,详见Partitioning | 完美rollup | 代理可以使用分区信息提前剪枝段以加快查询速度。由于代理知道如何对partitionDimensions值进行哈希以定位段,因此给定包含所有partitionDimensions过滤器的查询,代理可以仅选择持有满足partitionDimensions过滤器的行的段进行查询处理。注意,必须在摄入时设置 partitionDimensions才能在查询时启用二级分区剪枝 |
single_dim | 较慢 | 基于单个维度的range partitioning,通过改善数据局部性可能减少数据源大小和查询延迟,详见Partitioning | 完美rollup | 代理可以使用分区信息提前剪枝段以加快查询速度。由于代理知道每个段中partitionDimension值的范围,因此给定包含partitionDimension过滤器的查询,代理可以仅选择持有满足partitionDimension过滤器的行的段进行查询处理 |
Dynamic partitioning
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 设置为dynamic | 无 | 是 |
maxRowsPerSegment | 用于分片,确定每个段中的行数 | 5000000 | 否 |
maxTotalRows | 等待推送的所有段的总行数,用于确定何时应进行中间段推送 | 20000000 | 否 |
使用动态分区时,并行索引任务在单个阶段运行,生成多个工作任务(类型为single_phase_sub_task),每个任务创建段。
工作任务创建段的方式如下:
- 每当当前段中的行数超过
maxRowsPerSegment时。 - 当所有时间块的所有段中的总行数达到
maxTotalRows时。此时任务会将所有已创建的段推送到深度存储并创建新段。
Hash-based partitioning
| 属性 | 描述 | 默认值 | 是否必填 |
|---|---|---|---|
type | 设置为hashed | 无 | 是 |
numShards | 直接指定要创建的分片数。如果指定了此值并且在granularitySpec中指定了intervals,索引任务可以跳过通过数据确定间隔/分区的过程。此属性和targetRowsPerSegment不能同时设置 | 无 | 否 |
targetRowsPerSegment | 每个分区的目标行数。如果未指定numShards,并行任务会自动确定分区数,使得每个分区的行数接近目标,假设输入数据中的键分布均匀。如果numShards和targetRowsPerSegment均为null,则使用每个段500万行的目标 | null(如果numShards和targetRowsPerSegment均为null,则为5,000,000) | 否 |
partitionDimensions | 要分区的维度,留空则选择所有维度 | null | 否 |
总结与展望
本文详细介绍了Druid批处理数据加载方案,包括批处理任务类型、并行索引的工作原理、提交任务的方法、配置示例以及各主要配置项的说明。通过合理配置并行索引任务,可以高效地处理大规模数据加载,充分利用集群资源,提高数据处理速度。
在实际应用中,建议根据数据量、集群规模和业务需求选择合适的批处理任务类型和配置参数。对于生产环境,优先使用index_parallel任务,并根据数据特点调整partitionsSpec、splitHintSpec等参数以达到最佳性能。
未来,Druid可能会进一步优化批处理性能,提供更智能的分区策略和资源管理机制,帮助用户更轻松地处理海量数据。建议持续关注Druid的官方文档和更新日志,及时了解新功能和最佳实践。
如果你觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多关于Druid的实用教程和最佳实践。下期我们将介绍Druid的实时数据摄入方案,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考










