
数据库数据分析OLAP大数据实时分析数据仓库后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid7/druid点击查看免费下载本文围绕 Druid 官方的 JavaScript 扩展机制展开系统讲解如何在查询聚合、维度抽取、过滤、后聚合、数据解析、路由与 Worker 选择等环节注入 JavaScript 函数并结合仓库源码说明其底层实现与安全边界。读完本文你将掌握druid.javascript.enabled开关的正确用法、各类 JavaScript 扩展的 JSON 编写规范以及何时该选择原生扩展而非脚本。为什么 Druid 需要 JavaScriptDruid 允许通过 JavaScript 在多个层面扩展其能力官方文档 docs/content/development/javascript.md 明确列出了以下扩展点聚合器Aggregator抽取函数Extraction function过滤器Filter后聚合器Post-aggregator输入解析器Input parser路由策略Router strategyWorker 选择策略Worker select strategyJavaScript 可以在运行时被动态注入这意味着你可以快速原型化新功能而不需要编写并部署一个完整的 Druid 扩展extension——不需要重新编译 JAR、不需要重启集群节点、不需要走完整的扩展加载链路。对于探索性分析、临时逻辑验证、小规模定制等场景这种热注入能力极具吸引力。从源码结构看这些扩展点分散在 Druid 的多个模块中但全部共享同一个开关druid.javascript.enabled与同一套基于 Mozilla Rhino 的编译执行基础设施这为统一治理 JavaScript 扩展提供了便利。底层机制Mozilla Rhino 引擎与优化级别 9Druid 使用Mozilla Rhino 引擎来编译和执行 JavaScript 代码编译时使用优化级别 9Rhino 的最高优化级别对应编译到字节码模式。这一事实可以在源码中得到直接印证。配置常量定义于 api/src/main/java/io/druid/js/JavaScriptConfig.javapublic static final int DEFAULT_OPTIMIZATION_LEVEL 9;而各个扩展点在编译 JavaScript 时都会显式调用context.setOptimizationLevel(JavaScriptConfig.DEFAULT_OPTIMIZATION_LEVEL)例如processing/src/main/java/io/druid/query/aggregation/JavaScriptAggregatorFactory.java 中通过ContextFactory.getGlobal().enterContext()进入 Rhino 上下文设置优化级别后用compileFunction依次编译fnAggregate、fnReset、fnCombine三个函数processing/src/main/java/io/druid/query/extraction/JavaScriptExtractionFn.java 中同样以优化级别 9 编译抽取函数processing/src/main/java/io/druid/query/filter/JavaScriptDimFilter.java 中的JavaScriptPredicateFactory在构造时cx.setOptimizationLevel(9)后编译过滤谓词。Rhino 把 JavaScript 编译为 JVM 字节码后再执行这也是 Druid 中 JavaScript 函数能获得接近原生性能的基础。安全模型无沙箱执行与默认禁用这是使用 JavaScript 扩展前必须理解的一点Druid不会在沙箱中执行 JavaScript 函数这些函数拥有访问运行机器的全部权限。也就是说一个 JavaScript 函数可以在 Druid 进程内执行任意代码——读取文件、访问网络、调用 JVM 系统资源等。因此默认情况下 JavaScript 功能是关闭的。如何开启在 Druid 的运行时配置common.runtime.properties或各服务节点的runtime.properties中设置druid.javascript.enabled true该配置项在 docs/content/configuration/index.md 中有明确说明配置项说明默认值druid.javascript.enabled设为true以启用 JavaScript 功能。影响 JavaScript 解析器、过滤器、抽取函数、聚合器、后聚合器、路由策略和 Worker 选择策略。false从配置绑定机制看server/src/main/java/io/druid/guice/JavaScriptModule.java 通过 Guice 的JsonConfigProvider.bind(binder, druid.javascript, JavaScriptConfig.class)将druid.javascript.*前缀下的属性绑定到 api/src/main/java/io/druid/js/JavaScriptConfig.java 对应的enabled字段而该字段的默认值即为false。开启后的行为差异开启开关后各扩展点在构造时才会真正编译 JavaScript。若未开启构造阶段不会报错但一旦查询/执行真正用到这些扩展就会抛出JavaScript is disabled之类的运行时异常。例如JavaScriptAggregatorFactory在禁用时compiledScript为null调用getCompiledScript()时抛出ISE(JavaScript is disabled)见 JavaScriptAggregatorFactory.javaJavaScriptExtractionFn在禁用时fn为nullapply()时抛出ISE(JavaScript is disabled)见 JavaScriptExtractionFn.javaJavaScriptDimFilter.toFilter()在禁用时直接抛出ISE(JavaScript is disabled)见 JavaScriptDimFilter.javaJavaScriptPostAggregator的构造函数则使用Preconditions.checkState(config.isEnabled(), JavaScript is disabled.)在构造期直接拒绝见 JavaScriptPostAggregator.java。安全使用建议只有在开发/预发环境或经过严格安全加固的生产环境中才应开启此开关。任何向该环境提交查询或任务的人都等于获得了执行任意代码的能力务必把查询权限控制在与生产权限同级的可信范围内。七大 JavaScript 扩展点详解下面逐个介绍各扩展点的 JSON 编写方式并给出源码层面的说明。以下示例中的锚点对应官方文档 docs/content/querying/aggregations.md、docs/content/ingestion/data-formats.md、docs/content/development/router.md、docs/content/configuration/indexing-service.md 中的 JavaScript 小节。1. JavaScript 聚合器Aggregator聚合器需要提供三个函数fnAggregate逐行累加、fnCombine合并部分结果、fnReset重置初始值。官方文档 docs/content/querying/aggregations.md 中的模板与示例{ type : javascript, name : sumLog, fieldNames : [a, b], fnAggregate : function(current, column1, column2, ...) { aggregate current with column1, column2... return computed value; }, fnCombine : function(partialA, partialB) { return combined partial results; }, fnReset : function() { return initial value; } }具体例子{ type : javascript, name : sumLog, fieldNames : [a, b], fnAggregate : function(current, a, b) { return current (Math.log(a) * b); }, fnCombine : function(partialA, partialB) { return partialA partialB; }, fnReset : function() { return 10; } }源码层面的实现要点见 JavaScriptAggregatorFactory.java传入fnAggregate的参数依次是current当前累加值和各fieldNames对应列的值数组类型的列会被拷贝为 Rhino 数组再传入源码中注释说明Context.javaToJS对数组的处理并不完美因此做了显式拷贝函数返回值统一通过Context.toNumber转为double因此该聚合器的类型名是float、中间结果大小为Doubles.BYTES缓存键使用 SHA-1 对fnAggregate fnReset fnCombine求摘要见 JavaScriptAggregatorFactory.java这意味着脚本内容变化后缓存键随之变化不会出现新旧逻辑复用同一缓存的问题。2. JavaScript 抽取函数Extraction function抽取函数用于在查询时对维度值进行变换其 JSON 结构为{ type : javascript, function : function(str) { return str.substring(0, 3); }, injective : false }其中function接收维度值字符串或数值并返回变换后的字符串。若返回null抽取结果会被转换为空值处理见 JavaScriptExtractionFn.java 中的Strings.emptyToNullinjective声明该函数是否为单射一对一。true表示输出与输入一一对应有助于 Druid 做优化对应ExtractionType.ONE_TO_ONEfalse默认表示多对一见 JavaScriptExtractionFn.java。抽取函数在 Druid 中的用途非常广泛可叠加在维度dimensionSpec、过滤器extractionFn等位置使用也常用于 groupBy、topN、search 等查询的维度变换。3. JavaScript 过滤器FilterJavaScript 过滤器对指定维度逐值调用函数返回布尔值决定行是否保留。JSON 结构为{ type : javascript, dimension : name, function : function(value) { return value bar; }, extractionFn : null }字段说明dimension要过滤的维度列名function接收该维度的值返回布尔判断extractionFn可选先对维度值执行抽取变换再交给函数判断。源码实现在 JavaScriptDimFilter.java 中JavaScriptPredicateFactory内部用 Rhino 编译出fnApply并实现了makeStringPredicate、makeLongPredicate、makeFloatPredicate等不同数值类型的谓词工厂见 JavaScriptDimFilter.java从而在列式扫描时尽量匹配对应的原始类型。需要留意的是getDimensionRangeSet返回null说明该过滤器无法被下推为范围过滤会退化为逐行判断见 JavaScriptDimFilter.java。4. JavaScript 后聚合器Post-aggregator后聚合器基于查询已经聚合出的结果做进一步计算{ type : javascript, name : absPercentile, fieldNames : [delta, total], function : function(delta, total) { return Math.abs(delta) / total; } }name后聚合器的输出名fieldNames依赖的聚合结果字段列表函数参数按此顺序传入function接收各字段值返回计算结果转为double。实现见 JavaScriptPostAggregator.javacompute()从combinedAggregators中按fieldNames依次取出聚合值打包成Object[]传给编译好的函数。getDependentFields()返回fieldNames集合供查询引擎做依赖分析。5. JavaScript 输入解析器Input parser在数据摄入阶段parseSpec可以指定 JavaScript 解析器用于处理非标准格式的输入行。官方文档 docs/content/ingestion/data-formats.md 中的示例{ 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}形式的对象。任何展平flattening或多维值的解析都必须在这个函数内部完成Druid 不会再对返回值做二次处理见 docs/content/ingestion/data-formats.md。实现见 api/src/main/java/io/druid/data/input/impl/JavaScriptParseSpec.javamakeParser()在开关关闭时抛出ISE(JavaScript is disabled)开启时才构造JavaScriptParser来逐行执行函数。6. JavaScript 路由策略Router strategyRouter 节点上可以配置 JavaScript 路由规则把不同查询路由到不同 tier 的 Broker。官方文档 docs/content/development/router.md 给出的示例是把聚合器数量大于等于 3 的查询发往优先级最低的 Broker{ type : javascript, function : function (config, query) { if (query.getAggregatorSpecs query.getAggregatorSpecs().size() 3) { var size config.getTierToBrokerMap().values().size(); if (size 0) { return config.getTierToBrokerMap().values().toArray()[size-1] } else { return config.getDefaultBrokerServiceName() } } else { return null } } }函数接收config路由配置与query查询对象两个参数返回目标 tier 名返回null则使用默认 tier。实现类是 server/src/main/java/io/druid/server/router/JavaScriptTieredBrokerSelectorStrategy.java对应测试见 server/src/test/java/io/druid/server/router/JavaScriptTieredBrokerSelectorStrategyTest.java。7. JavaScript Worker 选择策略Worker select strategyOverlord 在为任务分配 MiddleManager Worker 时可以注入 JavaScript 策略。函数签名接收config、zkWorkers当前可用的 Worker 信息映射和task待分配任务返回选中的 Worker 名返回null表示不分配。实现见 indexing-service/src/main/java/io/druid/indexing/overlord/setup/JavaScriptWorkerSelectStrategy.java它通过ScriptEngineManager().getEngineByName(javascript)拿到脚本引擎把var apply fn编译后经Invocable适配为SelectorFunction接口再在findWorkerForTask()中调用fnSelector.apply(config, zkWorkers, task)并到zkWorkers里查回对应的ImmutableWorkerInfo。值得注意的是该策略在构造函数里就通过ISE(JavaScript is disabled)强制校验开关见 JavaScriptWorkerSelectStrategy.java配置不合法会在服务启动/任务分配阶段尽早暴露。配置位置见 docs/content/configuration/indexing-service.md 中关于 Worker 选择策略的章节。全局变量必须规避的陷阱避免在 JavaScript 函数中使用全局变量。Druid 会在多个线程之间共享 Rhino 的全局作用域ScriptableObject scope如果脚本中声明了全局变量不同线程/不同查询的并发执行可能互相污染状态导致不可预测的结果。从源码看各扩展点都只编译一次、全局复用例如JavaScriptExtractionFn.compile()创建的scope是编译时initStandardObjects()得到的共享对象见 JavaScriptExtractionFn.javaJavaScriptDimFilter.JavaScriptPredicateFactory的scope同样是构造时创建并在线程间共享见 JavaScriptDimFilter.java。正确的写法是把每次调用的状态全部收在函数参数和局部变量中函数保持无副作用pure function这是 Druid JavaScript 扩展能并发安全运行的前提。性能特征与垃圾回收性能方面官方文档给出的结论是简单的 JavaScript 函数相对原生实现通常只有轻微的性能损失复杂的 JavaScript 函数性能损失会明显加大Druid每个节点、每个查询只编译一次 JavaScript 函数编译结果被缓存复用见上述各扩展点构造函数中的一次性编译逻辑因此编译开销不是主要瓶颈运行时逐行调用的开销才是。如果你重度使用 JavaScript 函数还需要特别关注垃圾回收GC问题尤其是对已编译类本身的回收Rhino 以优化级别 9 编译出的字节码会生成对应的类频繁更换函数源码会导致这些类持续累积建议使用支持及时回收未使用类的 GC 配置官方文档特别指出在JDK 8 Metaspace环境下通常比JDK 7PermGen更容易做到及时回收。由于 Druid 按查询缓存编译结果、缓存键基于函数源码如聚合器对函数串取 SHA-1生产实践中应避免在查询/任务里内联大段、频繁变化的脚本尽量让同一段脚本被稳定复用。JavaScript vs 原生扩展如何选择官方文档给出的取舍建议可以总结为一张对照表考量维度JavaScript原生 Druid 扩展安全性无沙箱函数拥有宿主机全部权限默认禁用受扩展加载机制与代码审查约束开发速度快运行时热注入无需编译部署慢需要编写 Java 代码、打包、部署性能简单函数接近原生复杂函数损耗加大最优内存需要关注 Rhino 编译类与对象的 GC可控无额外脚本层开销灵活性受限只能覆盖文档列出的扩展点高可自定义数据格式、实现 sketch 等复杂类型官方建议当安全性不是问题、且开发速度比性能/内存更重要时优先使用 JavaScript当安全性是硬约束或性能和内存占用被放在首位时应开发原生 Druid 扩展此外原生扩展比 JavaScript 更灵活。某些扩展必须写成原生扩展——典型例子是各类sketch草图数据结构如近似去重、分位数估计因为它们需要自定义的二进制数据格式、跨节点合并协议这是纯 JavaScript 函数无法承载的只能通过 JavaScript 聚合器以double为中间值传递无法表达复杂结构。最佳实践总结默认关闭按需开启druid.javascript.enabled默认false只有开发/预发或严格加固的生产环境才开启并严格管控查询/任务提交权限。函数保持无状态杜绝全局变量所有状态走参数与局部变量保证多线程共享作用域下的并发安全。善用缓存键语义各扩展点的缓存键都基于函数源码生成脚本内容变化即视为不同函数注意避免缓存膨胀。关注 GC 配置重度使用脚本时优先 JDK 8 及以上并使用支持 Metaspace 及时回收的 GC 参数。关键路径用原生扩展需要极致性能、复杂数据类型或 sketch 能力时选择原生扩展而非 JavaScript。尽早暴露配置错误像 Worker 选择策略那样在构造期就校验开关状态让配置问题在启动阶段暴露而不是等运行时才报错。深入阅读配置开关说明docs/content/configuration/index.md聚合器中的 JavaScript 小节docs/content/querying/aggregations.md数据解析器中的 JavaScript 小节docs/content/ingestion/data-formats.mdRouter 的 JavaScript 路由策略docs/content/development/router.mdWorker 选择策略配置docs/content/configuration/indexing-service.md核心配置类api/src/main/java/io/druid/js/JavaScriptConfig.java配置绑定模块server/src/main/java/io/druid/guice/JavaScriptModule.java各类扩展点实现聚合器 JavaScriptAggregatorFactory.java、抽取函数 JavaScriptExtractionFn.java、过滤器 JavaScriptDimFilter.java、后聚合器 JavaScriptPostAggregator.java、解析器 JavaScriptParseSpec.java、路由策略 JavaScriptTieredBrokerSelectorStrategy.java、Worker 选择策略 JavaScriptWorkerSelectStrategy.java赞分享数据库数据分析OLAP大数据实时分析数据仓库后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid7/druid点击查看免费下载相关推荐5步打造专属JavaScript引擎Rhino自定义扩展开发完全指南5步打造专属JavaScript引擎Rhino自定义扩展开发完全指南 Rhino是一款完全用Java编写的开源JavaScript引擎它允许开发者在Java语言运行时编译器Apache Druid Kerberos 认证扩展实战指南为 Druid 节点启用 SPNEGO 安全认证Apache Druid Kerberos 认证扩展实战指南为 Druid 节点启用 SPNEGO 安全认证 Apache Druid 作为高性能实时分析数据数据库数据分析OLAP大数据实时分析数据仓库后端Apache Druid扩展电子书扩展开发完全指南Apache Druid扩展电子书扩展开发完全指南 你是否在使用Apache Druid时遇到数据格式不兼容、存储系统不支持或需要自定义聚合函数的问题作为一数据库OLAP大数据后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考