
Apache Druid 摄入数据格式完全指南JSON、CSV、TSV、Regex 与 JavaScript 的 parseSpec 配置详解【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid7/druidApache Druid 是一款高性能的实时分析数据库其摄入ingestion流程支持高度去规范化的数据输入。无论数据来自 Kafka 等实时流、Hadoop 批量任务还是本地文件Druid 都可以通过统一的parseSpec机制解析 JSON、CSV、TSV 以及任意自定义分隔符格式的数据。本文围绕docs/content/ingestion/data-formats.md展开系统讲解各类格式的输入数据样例、dataSchema中parseSpec的完整配置写法并结合当前仓库源码ParseSpec.java 及其各子类剖析每个参数在底层解析链路中的真实作用。读完本文你将能够为任意一种文本格式的数据源正确编写可运行的摄入规格ingestion spec。一、Druid 摄入流程中的数据格式先理解 parseSpec 的角色Druid 摄入数据时dataSchema中的parseSpec负责把原始输入行一条 JSON 对象、一行 CSV/TSV 文本等解析成 Druid 内部的“行row”结构该结构由时间戳列timestamp和维度列dimensions构成。从源码结构看这一抽象由抽象基类ParseSpec承担它持有timestampSpec与dimensionsSpec两个核心组件并通过 Jackson 注解按format字段进行多态反序列化见 ParseSpec.javaJsonTypeInfo(use JsonTypeInfo.Id.NAME, property format, defaultImpl DelimitedParseSpec.class) JsonSubTypes(value { JsonSubTypes.Type(name json, value JSONParseSpec.class), JsonSubTypes.Type(name csv, value CSVParseSpec.class), JsonSubTypes.Type(name tsv, value DelimitedParseSpec.class), JsonSubTypes.Type(name jsonLowercase, value JSONLowercaseParseSpec.class), JsonSubTypes.Type(name timeAndDims, value TimeAndDimsParseSpec.class), JsonSubTypes.Type(name regex, value RegexParseSpec.class), JsonSubTypes.Type(name javascript, value JavaScriptParseSpec.class) }) public abstract class ParseSpec { ... }可以看到仓库内置支持json、csv、tsv、jsonLowercase、timeAndDims、regex、javascript七种格式并且format未指定时默认回退为DelimitedParseSpec即 tsv。每种ParseSpec子类通过makeParser()返回对应的ParserString, Object如CSVParser、DelimitedParser、RegexParser、JavaScriptParser将原始文本行转换为键值对 Map供后续索引使用。这就是“所有形式的 Druid 摄入都需要某种 schema 对象”这句话的底层含义。二、数据样例以 Wikipedia 示例数据为例下文所有配置示例均基于 快速入门教程 使用的 Wikipedia 编辑记录数据。同一批数据可以表达为三种等价形式帮助你直观对比格式差异JSON 形式每条记录一个对象字段名内嵌在数据中{timestamp: 2013-08-31T01:02:33Z, page: Gypsy Danger, language : en, user : nuclear, unpatrolled : true, newPage : true, robot: false, anonymous: false, namespace:article, continent:North America, country:United States, region:Bay Area, city:San Francisco, added: 57, deleted: 200, delta: -143} {timestamp: 2013-08-31T03:32:45Z, page: Striker Eureka, language : en, user : speed, unpatrolled : false, newPage : true, robot: true, anonymous: false, namespace:wikipedia, continent:Australia, country:Australia, region:Cantebury, city:Syndey, added: 459, deleted: 129, delta: 330} {timestamp: 2013-08-31T07:11:21Z, page: Cherno Alpha, language : ru, user : masterYi, unpatrolled : false, newPage : true, robot: true, anonymous: false, namespace:article, continent:Asia, country:Russia, region:Oblast, city:Moscow, added: 123, deleted: 12, delta: 111} {timestamp: 2013-08-31T11:58:39Z, page: Crimson Typhoon, language : zh, user : triplets, unpatrolled : true, newPage : false, robot: true, anonymous: false, namespace:wikipedia, continent:Asia, country:China, region:Shanxi, city:Taiyuan, added: 905, deleted: 5, delta: 900} {timestamp: 2013-08-31T12:41:27Z, page: Coyote Tango, language : ja, user : cancer, unpatrolled : true, newPage : false, robot: true, anonymous: false, namespace:wikipedia, continent:Asia, country:Japan, region:Kanto, city:Tokyo, added: 1, deleted: 10, delta: -9}CSV 形式逗号分隔字符串字段加双引号2013-08-31T01:02:33Z,Gypsy Danger,en,nuclear,true,true,false,false,article,North America,United States,Bay Area,San Francisco,57,200,-143 2013-08-31T03:32:45Z,Striker Eureka,en,speed,false,true,true,false,wikipedia,Australia,Australia,Cantebury,Syndey,459,129,330 2013-08-31T07:11:21Z,Cherno Alpha,ru,masterYi,false,true,true,false,article,Asia,Russia,Oblast,Moscow,123,12,111 2013-08-31T11:58:39Z,Crimson Typhoon,zh,triplets,true,false,true,false,wikipedia,Asia,China,Shanxi,Taiyuan,905,5,900 2013-08-31T12:41:27Z,Coyote Tango,ja,cancer,true,false,true,false,wikipedia,Asia,Japan,Kanto,Tokyo,1,10,-9TSVDelimited形式Tab 分隔2013-08-31T01:02:33Z Gypsy Danger en nuclear true true false false article North America United States Bay Area San Francisco 57 200 -143 2013-08-31T03:32:45Z Striker Eureka en speed false true true false wikipedia Australia Australia Cantebury Syndey 459 129 330 2013-08-31T07:11:21Z Cherno Alpha ru masterYi false true true false article Asia Russia Oblast Moscow 123 12 111 2013-08-31T11:58:39Z Crimson Typhoon zh triplets true false true false wikipedia Asia China Shanxi Taiyuan 905 5 900 2013-08-31T12:41:27Z Coyote Tango ja cancer true false true false wikipedia Asia Japan Kanto Tokyo 1 10 -9关键提示CSV 与 TSV 数据中不包含列名表头。这一点至关重要——因为 CSV/TSV 解析器必须依靠你在parseSpec中显式声明的columns顺序来给每个字段命名。三、自定义格式Regex 解析器与 JavaScript 解析器除上述结构化格式外Druid 还支持使用Regex解析器和JavaScript解析器处理任意自定义文本格式Regex 解析器通过你提供的正则表达式的捕获分组capturing groups来切分每行数据适合日志等半结构化文本。JavaScript 解析器允许你用一段 JavaScript 函数完整接管“文本行 → 键值对”的转换逻辑灵活性最高。需要明确说明的性能边界在文档及源码中均有明确提示——使用这两种解析器处理数据其效率不如编写原生的 Java 解析器也不如使用外部流处理器external stream processor。原因在于它们是解释执行、逐行调用无法享受编译期优化。因此它们更适合处理低吞吐场景或原生解析器覆盖不到的边缘格式生产环境的高吞吐管道建议优先选择 JSON/CSV/TSV 或原生 Java 解析器。Druid 社区欢迎为新的数据格式贡献新的 Parser 实现Parser 接口定义于io.druid.java.util.common.parsers.Parser。四、parseSpec 通用结构与配置详解所有形式的 Druid 摄入都需要某种形式的 schema 对象数据格式通过dataSchema中的parseSpec条目指定。parseSpec的通用骨架由timestampSpec与dimensionsSpec组成parseSpec: { format: json|csv|tsv|regex|javascript, timestampSpec: { column: timestamp }, dimensionsSpec: { dimensions: [page, language, user, ...] } }各通用字段含义如下字段类型说明formatString数据格式类型决定实例化哪个ParseSpec子类。可选值见上文JsonSubTypes注解json、csv、tsv、jsonLowercase、timeAndDims、regex、javascript。缺省时默认DelimitedParseSpectsvtimestampSpec.columnString输入数据中作为事件时间戳的字段名可配合format时间格式一起使用dimensionsSpec.dimensionsArray声明哪些列作为维度被索引未列出的字段如数值指标通常不进入维度索引在 examples/quickstart/wikiticker-index.json 中可以看到一个完整的真实摄入规格示例它使用format: json的parseSpectimestampSpec指向time列并设置format: auto自动识别时间格式dimensionsSpec列出了 channel、cityName、user 等 16 个维度metricsSpec则定义了 count、longSum、hyperUnique 等聚合指标。这是 parseSpec 与完整dataSchema配合使用的标准范式。下面按格式逐一给出配置示例与底层解析器分析。4.1 JSON 格式JSON 是 Druid 文档中最常用的示例格式字段名直接携带在数据中因此parseSpec无需columns列表parseSpec: { format: json, timestampSpec: { column: timestamp }, dimensionsSpec: { dimensions: [page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city] } }底层由JSONParseSpec见 JSONParseSpec.java驱动format: json通过 Jackson 反序列化将ParseSpec实例化为JSONParseSpec其makeParser()基于JSONPathParserio.druid.java.util.common.parsers.JSONPathParser把每条 JSON 记录解析为字段 Map再交给timestampSpec与dimensionsSpec处理。嵌套 JSON 的处理如果输入是嵌套 JSONDruid 可以自动将其展平flatten。具体做法是在parseSpec中增加flattenSpec配置使用path或jq表达式提取嵌套字段。完整语法与示例见 flatten-json.md。4.2 CSV 格式由于 CSV 数据不允许包含表头列名必须由你在parseSpec中手动补充否则数据无法被处理parseSpec: { format: csv, timestampSpec: { column: timestamp }, columns: [timestamp, page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city, added, deleted, delta], dimensionsSpec: { dimensions: [page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city] } }columns字段必须与输入数据的列顺序完全一致。源码 CSVParseSpec.java 对这一约束做了硬校验Preconditions.checkNotNull(columns, columns); for (String column : columns) { Preconditions.checkArgument(!column.contains(,), Column[%s] has a comma, it cannot, column); }即columns不可为空否则抛NullPointerException且列名中不能包含逗号否则抛IllegalArgumentException。此外verify()方法还会校验dimensionsSpec中声明的每个维度都必须存在于columns列表中否则抛出column[xxx] not in columns.的异常——这一设计保证了维度名与列名的一致性从源头避免了“维度引用不存在的列”这类配置错误。在解析阶段CSVParseSpec.makeParser()返回CSVParser见io.druid.java.util.common.parsers.CSVParser并支持可选的listDelimiter多值维度分隔符见下文第六节。4.3 TSV 格式DelimitedTSV 与 CSV 类似同样必须显式提供columns但额外多一个delimiter参数因此它实际可以处理任意自定义分隔符的文本parseSpec: { format: tsv, timestampSpec: { column: timestamp }, columns: [timestamp, page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city, added, deleted, delta], delimiter: |, dimensionsSpec: { dimensions: [page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city] } }columns字段必须与输入数据的列顺序完全一致与 CSV 相同。务必根据你的数据把delimiter改成正确的分隔符示例中写的是|但如果数据本身是 Tab 分隔就需要写成\tJSON 字符串中的转义写法。像 CSV 一样你必须指定columns并从中选出要索引的维度子集。从源码看format: tsv与缺省格式都映射到 DelimitedParseSpec.java这也是ParseSpec上的defaultImpl。它与CSVParseSpec的差异在于多出一个delimiter属性makeParser()返回DelimitedParser并调用retVal.setFieldNames(columns)把列名列表绑定到解析器上。注意DelimitedParseSpec同样强制要求columns非空、列名不含逗号、所有维度名必须出现在columns中。此外它还提供了withDelimiter、withListDelimiter、withColumns等流式修改方法方便在代码中按需派生新配置。4.4 Regex 格式对于无法用简单分隔符切分的文本例如复杂日志行可以借助正则表达式的捕获分组来定义字段parseSpec: { format: regex, timestampSpec: { column: timestamp }, dimensionsSpec: { dimensions: [your_list_of_dimensions] }, columns: [your_columns_here], pattern: regex pattern for partitioning data }规则要点columns字段必须与正则匹配分组的顺序完全一致。即正则中第 1 个捕获组(...)的值对应columns[0]第 2 个对应columns[1]依此类推。如果未提供columns解析器会自动分配默认列名column_1、column_2、……、column_n对应捕获组数量。请确保你的列名覆盖了所有维度。源码 RegexParseSpec.java 的行为与文档完全一致Override public ParserString, Object makeParser() { if (columns null) { return new RegexParser(pattern, Optional.fromNullable(listDelimiter)); } return new RegexParser(pattern, Optional.fromNullable(listDelimiter), columns); }当columns为null时走无列名分支由RegexParser内部生成column_1、column_2等默认名否则按给定列名绑定捕获组。verify()也做了宽松处理仅当columns非空时才校验维度名必须包含在列中。另外RegexParseSpec同样支持listDelimiter用于多值维度见第六节。4.5 JavaScript 格式当正则也无法优雅表达切分逻辑时可以用 JavaScript 函数直接编写“文本行 → 键值对对象”的完整解析逻辑parseSpec: { format: javascript, timestampSpec: { column: timestamp }, dimensionsSpec: { dimensions: [page, language, user, unpatrolled, newPage, robot, anonymous, namespace, continent, country, region, city] }, function: function(str) { var parts str.split(\-\); return { one: parts[0], two: parts[1] } } }使用 JavaScript 解析器时必须注意函数必须完整解析输入行并以{key:value}对象形式返回结果。示例函数把字符串按-切分后返回含one、two两个键的对象。任何展平flattening或多维值multi-value的解析都必须在此函数内部完成。换句话说JS 函数是这个格式下唯一的转换入口没有后续的自动展平环节。安全与启用限制基于 JavaScript 的功能默认是禁用的。源码 JavaScriptParseSpec.java 的makeParser()中明确做了开关校验if (!config.isEnabled()) { throw new ISE(JavaScript is disabled); } return new JavaScriptParser(function);config是注入的JavaScriptConfig见 JavaScriptConfig.java其enabled开关默认关闭未开启时任何 JavaScript 解析都会直接抛出JavaScript is disabled异常。开启方法是在 Druid 运行时配置中把druid.javascript.enabled设为true关于 Druid JavaScript 功能的使用准则包括如何启用、安全注意事项、使用场景请参阅 JavaScript 编程指南。五、完整摄入规格示例parseSpec 如何融入 ingestion spec为了让上述配置“活”起来下面展示一个完整可运行的摄入规格骨架。它把parseSpec放进dataSchema.parser批量 Hadoop 任务使用type: hadoopyString包装器这也是 examples/quickstart/wikiticker-index.json 中真实采用的结构{ type: index_hadoop, spec: { ioConfig: { type: hadoop, inputSpec: { type: static, paths: quickstart/wikiticker-2015-09-12-sampled.json } }, dataSchema: { dataSource: wikiticker, granularitySpec: { type: uniform, segmentGranularity: day, queryGranularity: none, intervals: [2015-09-12/2015-09-13] }, parser: { type: hadoopyString, parseSpec: { format: json, timestampSpec: { column: time, format: auto }, dimensionsSpec: { dimensions: [channel, cityName, comment, countryIsoCode, countryName, isAnonymous, isMinor, isNew, isRobot, isUnpatrolled, metroCode, namespace, page, regionIsoCode, regionName, user] } } }, metricsSpec: [ { name: count, type: count }, { name: added, type: longSum, fieldName: added }, { name: deleted, type: longSum, fieldName: deleted }, { name: delta, type: longSum, fieldName: delta }, { name: user_unique, type: hyperUnique, fieldName: user } ] }, tuningConfig: { type: hadoop, partitionsSpec: { type: hashed, targetPartitionSize: 5000000 }, jobProperties: {} } } }把本文第四节任一parseSpec片段替换到parser.parseSpec位置即可把输入数据源切换为 CSV、TSV、Regex 或 JavaScript 格式。parser.type在不同摄入路径下写法不同批量 Hadoop 摄入使用hadoopyString实时流摄入如 Kafka则对应StringInputRowParser见 StringInputRowParser.java但其中parseSpec的写法完全一致。六、多值维度Multi-value dimensionslistDelimiter 与 JSON 数组维度dimension在某些场景下可以拥有多个值Druid 对多值维度的支持取决于数据格式CSV 与 TSVDelimited格式通过parseSpec中的listDelimiter指定多值维度内部的分隔符。例如某一行某字段为a,b,c且listDelimiter设为,则该字段会被解析成包含a、b、c三个值的多维字段。从源码看CSVParseSpec与DelimitedParseSpec的构造器都接受listDelimiter参数可空为空时不做多值切分并传递给底层CSVParser/DelimitedParser。注意DelimitedParseSpec对列名有一个额外限制——列名中不能包含逗号Column[%s] has a comma, it cannot这可以避免多值解析与列名解析相互混淆。JSON 格式JSON 数据天然支持多值维度——输入中该维度的值直接写成 JSON 数组即可{timestamp: 2013-08-31T01:02:33Z, page: Gypsy Danger, tags: [film, kaiju, pacific-rim]}JSON 多值维度不需要任何额外的parseSpec配置数组会被JSONParseSpec的底层解析器自动识别为多值维度。七、常见问题与最佳实践综合文档说明与源码约束整理出以下实战要点CSV/TSV 必须显式声明columns且顺序与数据完全一致这是最容易出错的地方。CSVParseSpec与DelimitedParseSpec均会通过Preconditions.checkNotNull(columns, columns)强制列名列表非空。维度名必须包含在columns中两个解析器的verify()都会检查dimensionsSpec中的每个维度名若缺失会抛出column[xxx] not in columns.。这是配置期即可发现的错误能有效避免运行期数据错位。TSV 的delimiter一定要改成实际分隔符默认语义是 Tab 分隔但示例用了|演示自定义分隔写成\t时注意 JSON 转义。Regex 的捕获组顺序 columns顺序不写columns会自动生成column_1 ... column_n若你需要可读的维度名务必显式声明。JavaScript 解析器默认禁用需要先设置druid.javascript.enabledtrue且函数必须返回{key:value}结构展平与多值切分都要在函数内自行完成。性能取舍Regex 与 JavaScript 解析器不如原生 Java 解析器高效高吞吐场景优先选 JSON/CSV/TSV若确有新格式需求可为 Druid 贡献原生 Parser 实现实现io.druid.java.util.common.parsers.Parser接口并注册到对应ParseSpec子类。扩展格式仓库的 extensions 列表 还提供了更多数据格式扩展如 Avro、Parquet、ORC、Thrift 等它们同样基于ParseSpec体系扩展例如 AvroStreamInputRowParser.java 与 OrcHadoopInputRowParser.java 都内嵌了各自的ParseSpec用法需要时可查阅对应文档。通过本文的配置示例与源码对照你已经掌握了在 Apache Druid 中为 JSON、CSV、TSV、正则和 JavaScript 格式编写parseSpec的完整方法也理解了columns、delimiter、listDelimiter、pattern、function等关键参数在底层解析链路中的真实语义与校验规则。这足以支撑你为任意文本格式的数据源编写出正确、可复用的 Druid 摄入规格。【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid7/druid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考