WEB 上的计算引擎
Web 上的数据接口以 restful 和 WebService 为主,格式通常是多层的 Json 和 XML。多层数据可承载更通用更丰富的信息,但结构上比传统的二维数据复杂,计算难度也更大。可用于 Web 计算的工具或引擎表面上不少,但都有各自的缺点,JsonPath/XPath 等类库解析能力强,但计算能力不足;Python Pandas 计算能力较强,但难以被 Java 集成,而且数据对象 DataFrame 不是专为多层数据设计的,遇到复杂计算时代码也难写;Scala Spark 在集成性方面好一些,但架构沉重,学习难度也大。
Web上的计算引擎,还有一个更好的选择:esProc SPL。
esProc SPL 是基于 JVM 的开源程序语言,内置专业的多层数据对象,提供了方便的 Web 解析 / 生成函数,可简化复杂的多层数据计算,容易被 Java 应用集成。
专业的多层数据对象
SPL 内置专业的多层数据对象序表,适合承载 Web 数据格式,擅长表达复杂的层级关系。
例如,对 Json 串进行条件查询,并用 Json 串返回计算结果:
A |
B |
|
1 |
=json(p_JsonStr) |
Json 串解析为 SPL 序表 |
2 |
=A1.conj(Orders) |
合并下层记录 |
3 |
=A2.select(Amount>1000 && Amount<=2000) |
条件查询 |
4 |
=json(A3) |
结果转为 Json 串 |
A1:外部参数 p_JsonStr 是 Json 串,函数 json 将其转为序表(SPL 的结构化数据对象)。在 SPL IDE 中点击 A1 格可以看到序表的多层结构,其中,EId、State 等字段存储简单数据类型,Orders 字段存储记录集合(二维表)。点击 Orders 中的某一行,可以展开观察数据:
A2、A3:对序表进行计算,计算结果同样是序表。
A4:函数 json 既可以将 Json 串转为序表,也可以将序表转为 Json 串。
SPL 提供了自由的多层数据访问方法,可以通过点号访问不同的层级,通过下标访问不同的位置。
第1层的单个字段的集合:A1.(Client)
第1层的多个字段的集合:A1.(\[Client,Name\])
第1层第10条记录:A1(10)
第1层第10条记录的Orders字段(即所有下层记录):A1(10).Orders
第1层第10条件记录Orders字段的单个字段的集合:(A1(10).Orders).(Amount)
第1层第10条件记录Orders字段的第5条记录:(A1(10).Orders)(5)
第1层的第10-20条记录:A1(to(10,20))
第1层的最后三条记录:A1.m(\[-1,-2,-3\])
序表是与字符串格式无关的高级数据对象,不仅可以解析 Json 串,也可以解析 XML 串,且计算代码通用。例如,p_XMLStr 是多层的 XML 串(与前面的 Json 同构),进行同样的条件查询时,只要修改 A1:=xml(p_XMLStr,"xml/row")
与函数 json 类似,函数 xml 也支持双向转换。
SPL 提供了各类 Web 数据接口,可以方便地访问 restful 或 WebService,并解析为统一的序表。比如访问 restful 接口,只要将前面例子的 A1 改为:
A |
|
1 |
=httpfile("http://127.0.0.1:6868/restful/emp_orders").read()) |
=json(A1) |
函数 httpfile 用于访问 url 网址,可指定端口、Header、字符集、IPV4/V6、Post/Get,支持 Cookie 或令牌的鉴权方式。
访问 WebService 接口:
A |
|
1 |
=ws_client("http://127.0.0.1:6868/ws/RQWebService.asmx?wsdl") |
2 |
=ws_call(A1,"RQWebService":"RQWebServiceSoap":"getEmp_orders") |
函数 ws_client 建立 WebService 客户端,函数 ws_call 查询 WebService 服务。
由于序表与字符串格式、数据源无关,所以没有特别说明时,下面例子都通用于 Json\XML\ 参数串 \Web。不难想象,SPL 还可以解析本地 Json 文件或 XML 文件,二维数据可以看作多层数据的简化情况,所以序表还可以解析并计算 csv 文件或数据库表(不是本文重点)。
强大的计算能力
对于解析后的序表,SPL 提供了丰富计算函数,可以轻松完成日常的 SQL 式计算。
A |
B |
|
1 |
…. |
生成多层序表 |
2 |
=A1.conj(Orders) |
合并下层记录 |
3 |
=A2.groups(State,Gender;avg(Salary),count(1)) |
多字段分组汇总 |
4 |
=A1.new(Name,Gender,Dept,Orders.OrderID,Orders.Client,Orders.Client,Orders.SellerId,Orders.Amount,Orders.OrderDate) |
关联 |
5 |
=A1.sort(Salary) |
排序 |
6 |
=A1.id(State) |
去重 |
7 |
=A2.top(-3;Amount) |
topN |
8 |
=A2.groups(Client;top(3,Amount)) |
组内TopN(窗口函数) |
SPL 还提供了符合 SQL92 标准的语法,支持集合计算、case when、with、嵌套子查询等。
SPL 表达能力强,适合计算结构复杂的多层数据。比如:restful 返回多层 Json,包含多个子文档,结构较复杂,部分数据如下:
[
{
"race": {
"raceId":"1.33.1141109.2",
"meetingId":"1.33.1141109"
},
...
"numberOfRunners": 2,
"runners": [
{ "horseId":"1.00387464",
"trainer": {
"trainerId":"1.00034060"
},
"ownerColours":"Maroon,pink,dark blue."
},
{ "horseId":"1.00373620",
"trainer": {
"trainerId":"1.00010997"
},
"ownerColours":"Black,Maroon,green,pink."
}
]
},
...
]
现在要对不同的层级进行分组汇总(对 trainerId 分组,统计每组中 ownerColours 的成员个数),直接在 Java 用使用一般的类型仍很复杂,SPL 就简单多了:
A |
|
1 |
… |
2 |
=A1(1).runners |
3 |
=A2.groups(trainer.trainerId; ownerColours.array().count():times) |
SPL 支持分步计算、有序计算、分组后计算等逻辑较复杂的计算,很多 SQL/ 存储过程难以实现的计算,用 SPL 解决起来就很轻松。比如,找出销售额累计占到一半的前 n 个大客户,并按销售额从大到小排序:
A |
B |
|
1 |
… |
/取数据 |
2 |
=A1.sort(amount:-1) |
/销售额逆序排序 |
3 |
=A2.cumulate(amount) |
/计算累计序列 |
4 |
=A3.m(-1)/2 |
/最后的累计即总额 |
5 |
=A3.pselect(~>=A4) |
/超过一半的位置 |
6 |
=A2(to(A5)) |
/按位置取值 |
SPL 有丰富的日期和字符串函数,能有效简化相关计算。
季度增减:elapse@q("2020-02-27",-3) // 返回 2019-05-27
N 个工作日之后的日期:workday(date("2022-01-01"),25) // 返回 2022-02-04
字符串类函数,判断是否全为数字:isdigit("12345") // 返回 true
取子串前面的字符串:substr@l("abCDcdef","cd") // 返回 abCD
按竖线拆成字符串数组:"aa|bb|cc".split("|") // 返回 ["aa","bb","cc"]
SPL 还支持年份增减、求季度、按正则表达式拆分字符串、拆出单词、按标记拆 HTML 等大量函数。
值得一提的是,为了进一步提高开发效率,SPL 还创造了独特的函数语法。比如用选项区分类似的函数,只过滤出符合条件的第 1 条记录,可使用选项 @1:
T.select@1(Amount>1000)
从后往前查找第 1 条记录,可以使用 @z:
T.select@z1(Amount>1000)
热部署集成架构
SPL 提供了 JDBC 接口,可以被 Java 代码方便地集成。简单的 SPL 代码可以像 SQL 一样,直接嵌入 JAVA:
String jsonStr=… //Json串
Class.forName("com.esproc.jdbc.InternalDriver");
Connection connection =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = connection.createStatement();
String query="=json(json($["+jsonStr+"]).conj(Orders).select((Amount>1000 && Amount<=2000)))";
ResultSet result = statement.executeQuery(query);
复杂的 SPL 代码可以先存为脚本文件,再以存储过程的形式被 JAVA 调用,可有效降低计算代码和前端应用的耦合性。
Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
CallableStatement statement = conn.prepareCall("{call getRestful(?, ?)}");
statement.setObject(1, "2020-01-01");
statement.setObject(2, "2020-01-31");
statement.execute();
SPL 是解释型语言,外置的代码无须编译就能执行,支持不停机热部署,适合变化的业务逻辑,运维复杂度低。
英文版