查询引擎
查询需求
- SELECT 属性 1, 属性 2, 属性 3
- SUM/AVG GROUP BY
- JOIN ON
- 支持指定特定源查询,也支持扫描所有源
FROM source1, source2
FROM all - 屏蔽底层具体数据源,SUM, AVG, GROUP BY 需要能够跨引擎
- 需要能够知道配置了哪些数据源
SHOW SOURCES
数据源
数据源是从具体的存储抽象出来的一个逻辑概念。
具体存储指的是:MySQL、Redis、LogFile、Kafka…
因为查询有跨引擎的需求,具体的存储应该被屏蔽。
屏蔽的方式是将不同的具体存储都视为流式数据,即将每个存储都看做没有索引的元素容器,查询时需要遍历该容器寻找符合要求的元素,这对于 LogFile、Kafka、Redis 是非常适用的。
但是对于 MySQL 这种关系型数据库,显然我们可以走数据库的索引来提高效率,因此我们可以对 MySQL 做特殊处理。
数据源的抽象映射
前面说过,数据源是具体存储的抽象,我们需要从不同的具体存储中抽象出一个公共的模型,但毕竟 MySQL 和 Redis 是完全不同的两种存储,一个完美符合两者的公共模型是不太实际的,因此这个模型不可避免地会或多或少地牺牲具体存储的一些特性,比如 Redis 将牺牲掉 Key 这个属性,将所有 Value 作为一个数据集合。
通过抽象,我们可以将每个不同的具体存储看做是一个 Python 的列表,而我们的所有查询,都可以看做对列表的遍历查询操作(前文提到过,如果具体存储是 MySQL,对于 WHERE 条件的实现当然可以走 MySQL 的索引查询提高效率)。
列表中可以存储的数据类型有数字、字符串、字典、列表。
- 数字 -> 对应数字类型
- 字符串 -> 对应字符串
- 字典 -> 对应可以反序列化的类型如 JSON
- 列表 -> 同上
确定公共模型的意义在于,保证了对具体存储的抽象,当遇到冲突时,可以给出一个合理的选择。比如 Redis 为了符合这种模型,需要取出整个库的所有 Value 构成一个列表,而舍弃 Key 的概念。
而接下来的问题就是对于具体的存储如何看做一个列表。
LogFile
一个文件,或多个文件的行的集合,都可以满足构成列表的条件。即一个或多个文件可以映射为一个数据源。
通常来说,日志文件都是一行一行地以 Append 模式写入的,考虑一种理想状况,每行日志文件都是 JSON 数据格式,比如:
{"a":1,"b":2,"c":3}
{"a":4,"b":5,"c":6}
{"a":7,"b":8,"c":9}
对应 Python 中
[
{"a":1,"b":2,"c":3},
{"a":4,"b":5,"c":6},
{"a":7,"b":8,"c":9}
]
但是我们可能有些日志并没有明显的结构:
"这是第一条记录"
"这是第二条记录"
"这是第三条记录"
对应 Python 中
[
"这是第一条记录",
"这是第二条记录",
"这是第三条记录"
]
甚至是混搭:
这是一个日志文件
{"a":1,"b":2,"c":3}
"sample string"
123
{"a":2,"b":3,"c":4}
{"d":2,"e":3,"f":4}
对应
[
{"a":1,"b":2,"c":3},
"sample string",
123,
{"a":2,"b":3,"c":4},
{"d":2,"e":3,"f":4}
]
Kafka
当确定了地址端口,主题,分区这类信息后,Kafka 中的数据也类似一行一行的日志文件,消息的集合同样可以看做一个列表。
Redis
前面不止一次提到过,舍弃 Key,取出所有的 Value 即可以构成一个集合则可以看做一个列表。
key - value 形式
key | value |
---|---|
job | programmer |
tags | {‘service_type’: ‘es3’, ‘level’: ‘info’, ‘host’: ‘192.168.11.26’, ‘message’: ‘Ohlckicfuq qnrs xnnwyxetj hsg aoopflwr ohnlht tdzgscr nffzwqnt nrcc mbydudmtvq ytgifryzk xwh qwhciav.’, ‘time’: ‘1591150809’} |
book-name | Mastering C++ in 21 days |
infomsg | {‘name’: ‘tom’, ‘alias’: ‘superman’, ‘sex’: ‘male’, ‘height’: ‘175’, ‘postal code’: ‘100086’, ‘Tel’: ‘133’} |
description | redis basic commands for caching |
message | Jbgfpnwb qwzhgsql gcg nmykxo uyums bpiyd btahopglmh ujgc ggox pog lljdywp ddsmw. |
books | {‘name’: ‘c++ primer’, ‘money’: ‘12’, ‘time’: ‘20200425-3712’, ‘dir’: ‘chengdu’, ‘color’: ‘blue’} |
language | python |
time | 20200615 |
可以看做
a = [
"programmer",
{
service_type: "es3",
level: "info",
host: "192.168.11.26",
message:"Ohlckicfuq qnrs xnnwyxetj hsg aoopflwr ohnlht tdzgscr nffzwqnt nrcc mbydudmtvq ytgifryzk xwh qwhciav.",
time: "1591150809",
},
{
name: "tom",
alias: "superman",
sex: "male",
height: "175",
postal_code: "100086",
Tel: "133",
},
{
name: "c++ primer",
money: "12",
time: "20200425-3712",
dir: "chengdu",
colo: "blue",
},
"python",
"20200615"
]
MySQL
MySQL 中的表的所有行的集合可以看做一个列表。
id | name | age |
---|---|---|
1 | abc | 12 |
2 | def | 13 |
3 | ghi | 14 |
4 | jkl | 15 |
[
{ id: 1, name: "abc", age: 12 },
{ id: 2, name: "def", age: 13 },
{ id: 3, name: "ghi", age: 14 },
{ id: 4, name: "jkl", age: 15 }
]
数据源配置
以下配置使用 TOML 格式
# 名为source1的源,具体存储为mysql,将mysql的users表映射为一个源
[[sources]]
name = "source1"
type = "mysql"
host = "mike.jabingp.cn"
port = "3306"
dbname = "demo001"
username = "demo01"
password = "demo01"
table = "users"
# 名为source2的源,具体存储为日志文件,将"mql/testdata/1_2.log"这个文件中的所有行的集合映射为一个源
[[sources]]
name = "source2"
type = "file"
path = "mql/testdata/1_2.log"
# 名为source3的源,具体存储为kafka
[[sources]]
name = "source3"
type = "kafka"
kafkaServerAddress = "192.168.128.1"
port = 9092
topic = "myTest"
partiton = 1
group = "test_group"
# 名为source3的源,具体存储为redis
[[sources]]
name = "source4"
type = "redis"
redisServerAddress = "127.0.0.1"
port = 6379
dbIndex = 0
connectionTimeout = 1000
soTimeout = 1000
查询语法
考虑到 SELECT 字段、SUM、AVG、GROUP BY、JOIN 的需求,使用类 SQL 作为查询语法是个不错的选择。不同的是,SQL 中的 FROM 后接的是表名,而在我们的查询中,FROM 后面接的应该是配置文件中配置的数据源。
语法1
SELECT *
SELECT table1
结果集: 将选定的数据源/全部 进行映射显示
语法2
SELECT tag
FROM redis_table,file_table
结果集:
筛选出tag属性的LogFile
[{"tag": "info"},
{"tag": "debug"},
{"tag": "info"},
{"tag": "error"},
{"tag": "info"}]
以及redis
a = [
{ tag: "info"},
{tag: "error"}
]
语法3
SELECT *
FROM redis_table as a
LEFT JOIN file_table as c
ON a.id = b.id
解析:根据两个源的id进行左连接,聚合显示
uid host tag time name brief email
0 1500 200.30.36.143 info 1255095527 NaN NaN NaN
1 1326 10.215.146.234 debug 378838103 NaN NaN NaN
2 413 218.203.252.80 info 383582233 NaN NaN NaN
3 1727 14.161.246.157 info 167274074 NaN NaN NaN
4 572 26.25.90.194 info 1247598870 NaN NaN NaN
5 744 86.240.180.212 error 972164180 NaN NaN NaN
6 1986 35.101.195.153 info 554925744 NaN NaN NaN
7 1332 135.211.187.254 debug 1540890931 Megan Frost Woman
lot newspaper direction leader guess. mendozamichael@hotmail.com
8 1622 45.92.39.57 debug 790456638 NaN NaN NaN
9 1566 64.105.44.117 error 447483343 NaN NaN NaN
10 563 144.189.106.153 debug 113813126 NaN NaN NaN
11 840 158.34.190.251 info 1179569947 NaN NaN NaN
12 234 191.85.117.133 error 550242415 NaN NaN NaN
13 996 90.137.211.93 error 298459976 NaN NaN NaN
14 1700 83.103.69.191 error 997882493 NaN NaN NaN
15 1061 171.235.88.135 info 1403719928 NaN NaN NaN
16 859 43.82.243.105 info 641182423 NaN NaN NaN
语法4
SELECT *
FROM redis_table
WHERE time = 20200618
结果集:
{"uid": 1700, "host": "83.103.69.191", "tag": "error", "time": 20200618}
{"uid": 1061, "host": "171.235.88.135", "tag": "info", "time": 20200618}
{"uid": 859, "host": "43.82.243.105", "tag": "info", "time": 20200618}
语法5
SELECT day_of_week,tag,price
FROM file_table
GROUP BY day_of_week,tag
结果集:
day_of_week tag price
Friday debug 1022.147887
error 971.437956
info 1009.618321
Monday debug 1043.233577
error 954.007692
info 920.764331
Saturday debug 1048.394737
error 1039.977099
info 1042.748092
Sunday debug 1055.455696
error 959.321637
info 1082.108571
Thursday debug 990.337748
error 981.364286
info 981.171429
Tuesday debug 1041.352201
error 1054.400000
info 1021.818182
Wednesday debug 1151.352000
error 997.659420
info 1027.931624
语法 6
SELECT *
FROM redis_table as a
LEFT JOIN kafka_table as b
ON a.id = b.id
LEFT JOIN file_table as c
ON c.id = a.id
WHERE id = "xxxxx"
GROUP BY tag
解析: From:选择三个 source table ; 然后在取数据出来时,根据 where 条件先进行筛选,减少不符合的数据 ; left join 表示: 将得到的三个表的数据进行左连接 ;group by : 根据一个或多个列对结果集进行分组 ,可以配合算子使用,如 sum。
结果集:
tag uid host time name brief email
info 1500 200.30.36.143 1255095527 NaN NaN NaN
413 218.203.252.80 383582233 NaN NaN NaN
1727 14.161.246.157 167274074 NaN NaN NaN
572 26.25.90.194 1247598870 Megan NaN NaN
debug 1326 10.215.146.234 378838103 Tom NaN NaN
563 144.189.106.153 113813126 NaN NaN NaN
error 744 86.240.180.212 972164180 NaN NaN NaN
语法 7
SELECT AVG(price)
FROM table_1 , table_2
WHERE time = "20200618"
AND tag = "error"
例如 table_1 是 mysql 的表**(未映射)**
id | tag | time | price |
---|---|---|---|
1 | info | 20200618 | 250 |
2 | error | 20200618 | 220 |
3 | error | 20200618 | 170 |
4 | debug | 20200618 | 100 |
table_2 是 redis (已映射)
{ value: { id: 1, tag:"error",age: 11,price: 300,time: "20200618"}}
{ value: "123123123" }
{ value: { id: 2, tag:"error",age: 20,price: 100,time: "20200618"}}
{ value: { id: 3, tag:"info",age: 18,price: 240,time: "20200618"}}
则执行上述语法后的结果集显示为:
{price : 197.5 } // 四条error的平均
语法 8
SELECT tag,avg(price)
FROM table_1 , table_2
WHERE time = "20200618"
GROUP BY tag
执行后显示结果集为:
{tag : info , price : 245} // 两条info的平均
{tag : error, price : 197.5} // 四条error的平均
{tag : debug, price : 100}