背景
面向数据加工领域的搜索DSL(特定领域语言,Domain-Specific Language)无需使用者编写较为复杂的通常程序语言,通过简洁的DSL即可实现复杂的搜索需求,具有较好的用户体验。搜索算子语法支持较为丰富,传统方案采用遍历AST(抽象语法树,Abstract Syntax Tree)的方式,根据当前的标识符类型执行对应操作。上述设计也就是常说的“解析执行”方案,由于每次都需要 “判断标识符类型”,执行效率较慢。
本文,提出一种高效的数据加工搜索特定领域语言,不仅采用“编译执行”的设计方案,避免了重复“判断标识符类型”,直接搜索表达式对应操作,而且采用了语义等价转换的方式以尽可能构建较优的运行时数据结构,不仅保证了语义的正确性,而且翻译得到的运行时代码执行效率较高。
怎么实现的?
本文提出了一种高效的数据加工搜索特定领域语言,具有灵活和丰富的语义,“编译执行”避免了类型判断等重复性操作,通过语义等价转换,进而使用了“较优的运行时数据结构”,执行速度较快,数据处理能力好,吞吐率较高。系统模块设计分为四部分:搜索语义定义、抽象语法树构造、语义等价优化、运行时代码生成。
A)搜索语义定义,编写搜索语义对应的Antlr4对应的语法文件。
1.全文搜索,语法格式为“字符串”, 同时字符串中间存在空格,则表示字符串是或逻辑关系。例如:e_search("active error")。
2.字段搜索,语法格式为“字段:字符串”,同时字符串中间存在空格,则表示字符串是或逻辑关系。例如:e_search("status: active")。
3.完全匹配, 语法格式为“字段=="字符串"”。例如:e_search('author== "john smith"')。
4.正则匹配,语法格式为“字段~=字符串”。例如:e_search('content~="正则表达式"')。
5.通配符搜索,星号(*)匹配零个或多个字符,问号(?)匹配一个字符。例如:e_search("status: active*test")。
6.搜索值转义,星号(*)或问号(?)需要使用反斜线(\)转义。例如:e_search('status: "\*\?()[]:="')。
7.字段名转义,特殊字符用反斜线(\)转义。例如:e_search("__tag__\:__container_name__: abc")。
8.数字,支持数字大小和范围的搜索匹配。例如:e_search('count: [100, 200]'),e_search('age >= 18')。
9.使用关系运算符,支持not、and和or操作。例如:e_search("abc OR xyz"),e_search("abc and not (xyz and not zzz)")。
B)抽象语法树构造,用于将作业的DSL解析并改写为语义一致的AST(抽象语法树,Abstract Syntax Tree)。
1.将DSL采用词法语法工具Antlr4解析成为AST。
2.采用监听器模式,访问AST节点,并在exist方法中转换构造语义更加完备的AST。比如:将二目逻辑运算符OR,转换为多目逻辑运算符OR,以此方便后续的语义优化。
C)语义等价优化,在保证语义相同的前提下,尽量转换为执行效率更高的操作,也就是为转换为高效的运行时代码做准备。
1.通配符处理。前后通配符去除,通配符转换为正则,以此去除较为常见的DSL编写手误,此优化为一对一转换,较为简单。
2.表达式优先级排序。不同表达式的计算复杂度是不同的,算术运算相等比较运行较快,子串搜索次之,正则匹配计算复杂度较高,由此将算术运算相等比较尽可能前置,正则匹配尽可能后置,能够得到较好的执行效果。
3.公共字段提取。每个人的编写DSL习惯不同,有些搜索表达式中充斥着大量的重复字段字段,比如对某个字段的子串或运算,如果将这个公共字段提取,这个字段取值只需执行一次即可,而且这个字段的操作可以进一步转换为更高效的运行时数据结构。
4.复杂逻辑表达式合并。当复杂逻辑表达式元素超过阈值时,可以等价转换为高效的运行时数据结构,大量的算术运算相等比较可以转换为集合in操作,大量的子串搜索可以转换为AC自动机,大量的正则匹配可以合并为同一条正则。
注意:由于语义等价优化会改变原AST的结构,进而可能又会产生新的语义等价优化点,因此,本文设计语义等价优化迭代次数为2。
D)运行时代码生成,将AST转换为能够可以执行的运行时C++代码。此步骤较为简单为AST和C++类结构的一对一翻译,采用拼接字符串的方式翻译为C++类(编译器的一种执行方式,把AST翻译为程序代码),然后将翻译之后的运行时代码打包既可以运行。
搜索特定领域语言参照:
有哪些实际价值?
1)语义灵活丰富:搜索特定领域语言支持多种搜索语义,并且这些表达式均可嵌套,进而可以灵活地实现客户复杂需求。
2)优化门槛低:优化过程极为简单,完全由后台程序自动实现,客户无需深入分析及手动优化。
3)执行速度快:避免了类型判断等重复性操作,并且采用了高效的运行时数据结构,提高了运行时的执行速度,优化了事件吞吐率和实时性。