开发文档(网易游戏实习)

本文介绍了构建一个统一查询引擎的概念,该引擎能够屏蔽底层的具体数据源,支持MySQL、Redis、LogFile和Kafka等。通过抽象不同存储为列表,实现了SQL-like查询,包括SELECT、SUM、AVG、GROUP BY和JOIN操作。同时,文章讨论了如何配置数据源,并给出了查询语法示例,展示了如何处理不同类型的数据源。
摘要由CSDN通过智能技术生成

查询引擎

查询需求

  1. SELECT 属性 1, 属性 2, 属性 3
  2. SUM/AVG GROUP BY
  3. JOIN ON
  4. 支持指定特定源查询,也支持扫描所有源
    FROM source1, source2
    FROM all
  5. 屏蔽底层具体数据源,SUM, AVG, GROUP BY 需要能够跨引擎
  6. 需要能够知道配置了哪些数据源
    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 形式

keyvalue
jobprogrammer
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-nameMastering C++ in 21 days
infomsg{‘name’: ‘tom’, ‘alias’: ‘superman’, ‘sex’: ‘male’, ‘height’: ‘175’, ‘postal code’: ‘100086’, ‘Tel’: ‘133’}
descriptionredis basic commands for caching
messageJbgfpnwb qwzhgsql gcg nmykxo uyums bpiyd btahopglmh ujgc ggox pog lljdywp ddsmw.
books{‘name’: ‘c++ primer’, ‘money’: ‘12’, ‘time’: ‘20200425-3712’, ‘dir’: ‘chengdu’, ‘color’: ‘blue’}
languagepython
time20200615

可以看做

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 中的表的所有行的集合可以看做一个列表。

idnameage
1abc12
2def13
3ghi14
4jkl15
[
    { 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 的表**(未映射)**

idtagtimeprice
1info20200618250
2error20200618220
3error20200618170
4debug20200618100

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}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值