Varobj

2020-09-28

使用 Sonic 搭建轻量搜索服务



经常遇到需要在数据库中模糊搜索某字段的需求,例如:模糊查找plan_conf表的计划名称。全表数据五百万+,字段有索引,但也只能使用 like 前缀匹配的方式查询,否则就是全表扫描,效率特别低。

表结构如下:

mysql dev@db.read:middle> explain select * from plan_conf where ad_name like 'dy450%';
+----+-------------+--------------+------------+-------+---------------------+-------------+---------+--------+------+----------+-----------------------+
| id | select_type | table        | partitions | type  | possible_keys       | key         | key_len | ref    | rows | filtered | Extra                 |
+----+-------------+--------------+------------+-------+---------------------+-------------+---------+--------+------+----------+-----------------------+
| 1  | SIMPLE      | plan_conf | <null>     | range | idx_ad_name,ad_name | idx_ad_name | 770     | <null> | 2    | 100.0    | Using index condition |
+----+-------------+--------------+------------+-------+---------------------+-------------+---------+--------+------+----------+-----------------------+

mysql dev@db.read:middle> explain select * from plan_conf where ad_name like '%dy450%';
+----+-------------+--------------+------------+------+---------------+--------+---------+--------+---------+----------+-------------+
| id | select_type | table        | partitions | type | possible_keys | key    | key_len | ref    | rows    | filtered | Extra       |
+----+-------------+--------------+------------+------+---------------+--------+---------+--------+---------+----------+-------------+
| 1  | SIMPLE      | plan_conf | <null>     | ALL  | <null>        | <null> | <null>  | <null> | 5242904 | 11.11    | Using where |
+----+-------------+--------------+------------+------+---------------+--------+---------+--------+---------+----------+-------------+

这种需求,只能选择搜索引擎来实现。搜索引擎可以选择诸如 ElasticsearchSolr 等搭建,也可以选择轻量的搜索如 Sonic 等。 Elasticsearch 是最出名的开源搜索产品,功能齐全且强大。但是如果你只需要一个简单的搜索名称功能而已,不想搭建复杂的 Elasticsearch 相关环境,或者只是为了一个免费的服务搭建搜索功能而服务器资源有限,毕竟 Elasticsearch 初始化完成后,什么数据都没有就占用了至少上百 M 内存,而 Sonic 只需要数十 M 内存即可。

1. Sonic 介绍

有个同名电影 Sonic(索尼刺猬克)讲的是,能光速移动的外星生物 sonic 误入地球的动画片。可能作者应该是借以表达 sonic 是一个高性能的搜索吧。它是由 Rust 语言编写,高性能且轻量级,无 Schema 的搜索服务。它的作者意在将其打造成“可搜索的Redis”,它基于 TCP 的协议传递用户搜索的字符到索引并查询,返回给用户相关度最高的一匹标识符,需要用户自己根据标识符去外部存储,如 MySQL 中查找标识符对应的文本。详细的理论介绍参考文档,相当于借助 sonic 搭建一个类似 MySQL 的二级索引、倒排索引。

2. 存储数据

存储数据包含 3 层,分别是:

collection (集合) -> buket (桶) -> terms { object(对象、标识符) => text(需要搜索的文本) }

对应和关系数据库的层级很像

database(数据库) -> table(表) -> rows {field(字段) => vlaue(值)}

3. 安装

从 github 下载二进制包,或者使用 cargo build 的方式安装

git clone https://github.com/valeriansaliou/sonic

安装好之后直接启动,默认端口 1491

./sonic -c config.cfg > /dev/null 2>&1 &

目前只有服务端,没有像 redis-cli 一样的 cli 客户端,所以只能使用 telnet 发送 socket 指令来使用。

[root@varobj ~]# telnet ::1 1491
Trying ::1...
Connected to ::1.
Escape character is '^]'.
CONNECTED <sonic-server v1.3.0>

这个时候需要快速(10s内)选择开启的 session 模式,并带上密码,例如开始搜索模式 START search SecretPassword 回车键输入

[root@varobj ~]# telnet ::1 1491
Trying ::1...
Connected to ::1.
Escape character is '^]'.
CONNECTED <sonic-server v1.3.0>
START search SecretPassword
STARTED search protocol(1) buffer(20000)

看到 STARTED search protocol(1) buffer(20000) 输出就表示进入了搜索模式,可以开始搜索了。

4. socket 协议

详细协议文档参考 Github, 这里列出几种常用的命令。如上 sonic 包含三种不同的搜索模式

// telnet 中

// 开启搜索模式,SecretPassword 是 config 中的默认密码
START search SecretPassword

// 开启插入模式
START ingest SecretPassword

// 开启管理模式
START control SecretPassword

顾名思义 search 模式用来查询数据,ingest 用来插入数据,这里主要使用这两种模式。每种模式下又支持不同的命令,模式之间相互隔离,所以插入模式下无法查询数据,查询模式下无法更新数据。目前什么数据都没有,需要先插入一些实例

说明:telnet 中不同模式之前好像无法直接切换,只能先退出再使用其他模式

4.1 PUSH 数据

命令格式:

PUSH <collection> <bucket> <object> "<text>" [LANG(<locale>)]?

PUSH 插入新数据,collection、buket 就相当于 MySQL 的 database、table。 object、text 相当于 Redis 的 key、value 就很好理解了。需要注意的是可选参数 LANG,默认不传 sonic 会根据 text 猜测语言,其他只支持 ISO 639-3 中的编码,其中英文是 eng,中文只能使用普通话的 cmn , zh 这些不支持。下面使用的示例都是默认不传语言设置的。


[root@varobj ~]# telnet ::1 1491
...
START ingest SecretPassword

PUSH plan_name default ad:1 "csjfkccy3052-2020.01.17-5"
OK

PUSH plan_name default ad:2 "zyyklzcsj1737-穿山甲-安卓-素材-竖图-0924"
OK

buket 如果是预计只有一个,推荐使用 default 命名,如果预计多个,建议使用自定义有意义的名称命名。

4.2 QUERY 查询数据

命令格式:

QUERY <collection> <bucket> "<terms>" [LIMIT(<count>)]? [OFFSET(<count>)]?

[] 括起来的都是可选参数,查询刚才插入的数据


[root@varobj ~]# telnet ::1 1491
...
START search SecretPassword

QUERY plan_name default "2020.01.17"
PENDING FEs4WSIa
EVENT QUERY FEs4WSIa ad:1

QUERY plan_name default "穿山甲"
PENDING 1GoedgJ0
EVENT QUERY 1GoedgJ0 ad:2

query 结果返回的是两行数据,第一行表示正在执行 EventID=FEs4WSIa 的查询。第二行表示查询 FEs4WSIa 返回了一个结果的标识符 ad:1, 如果有多个结果,以一个空白字符分割。这里可以拿 ad:1去数据库查找完整的名称信息。

4.3 SUGGEST 推荐数据

很多时间,我们也不知道能够查询到什么数据,只需要键入少量字符,就可以使用 suggest 推荐已包含的分词,对需要快速筛选到结果很有帮助。因为搜索引擎不像数据库 like 查询,有的单词不是分词是无法查询到具体结果的,具体原因,目前我也不清楚搜索引擎原理。但是可以肯定的是,如果 suggest 能够给到你一些单词,用这些单词肯定能搜索到结构。但是很可惜,目前 suggest 不支持中文。目前版本 1.3 不支持,不知道未来是否能够支持中文的推荐。

命令格式:

SUGGEST <collection> <bucket> "<word>" [LIMIT(<count>)]?

需要注意的是,word 中最好只包含英文字符,不能包含空格,特殊符号甚至中文等。需要程序自行判断处理,否则遇到这些字符会报错


[root@varobj ~]# telnet ::1 1491
...
START search SecretPassword

uggest plan_name default "2020"
PENDING dBxHd4kD
EVENT SUGGEST dBxHd4kD 2020.01.17

5. 实战

首先需要提前把数据库的所有名称和 ID 都提前 Push 到 Sonic 中。测试了下单进程批量读数据库 + push 操作,大约 200 / s 的速度。全部写入后磁盘占用不到 300 M,内存消耗不到 100 M, 可以说占用资源很少了。

[root@varobj ~]# du -h ./data
128K    ./data/store/kv/3e2023cf
108K    ./data/store/kv/5ba6f894
259M    ./data/store/kv/5b196aac
259M    ./data/store/kv
4.0K    ./data/store/fst/3e2023cf
4.0K    ./data/store/fst/4f744a98
4.0K    ./data/store/fst/5ba6f894
992K    ./data/store/fst
260M    ./data/store
260M    ./data/

经过简单的封装客户端代码,实现以上几个功能,并提供接口供后台查询使用,效果如下:

images

images

images

查询基本瞬间完成,数据库查询需要至少数分钟。

6. 优缺点

优点:

缺点:

整体用起来相当不错,如果你也需要一个轻量级的搜索,又不想折腾体积庞大的 ES,那么 Sonic 也许适合你。

其他相关参考