登录
  • 人们都希望被别人需要 却往往事与愿违
  • 小时候一个劲地教你做好人, 长大了一个劲地教你做坏人这就是中国式教育

轻量级全文搜索引擎 Meilisearch 使用体验

建站运维 Benny 小土豆 9525 次浏览 4113 字 7 个评论
文章目录 [显示]

自从人人影视分享站被攻击之后,我便一直在想如何提升搜索性能。

问题分析

人人影视分享站的搜索功能主要包括这三大模块:

  1. 搜索 yyets 数据库的cnnameennamealiasname三个字段,也就是之前爬下来的老的数据库的内容
  2. 搜索评论信息,同时找到评论所在页面
  3. 如果以上都无结果,那么去搜索其他网站

三大模块的优化方案

  • 1 和 2 都是使用了正则作为模糊搜索,因此加索引是没用的。虽然数据库不算很大,但是毕竟要全表检索,数量多就容易过载
  • 搜索评论时,是 Python 拿到结果再去请求 yyets,其实可以通过聚合查询一次性搞定。让 MongoDB 去做这件事情肯定比 Python 去做要方便得多,也省得写乱七八糟的处理代码
  • 站外查询这个功能可以禁用了
  • 升级服务器

但是以上方案无论怎么折腾,都逃不过搜索时要全表检索,只要涉及到全表检索那肯定会重 IO 重 CPU。

那不请求数据库,加 redis 做缓存呢?好是好,但是命中率会很低,因为每个人搜索关键词可能差很多,而且缓存多久合适?很难搞的问题

那有没有更好的办法呢,那种搜索很快的数据库?当然有的了,比如知名的 Elastic Search

全文搜索引擎

ElasticSearch 可谓是最致命知名的全文搜索引擎了,几年前在做实习生的时候简单地接触过。那时候在我的眼中,ES 似乎和 MongoDB 没什么区别。

为了能够让人人影视分享站的搜索功能更上一个台阶,我需要选择一个全文搜索引擎。

ES 确实是非常知名的全文搜索引擎,但是同时也非常致命:需要非常的配置,尤其是内存,官方建议似乎最少 8G 了,我真的很穷😭

我对于这个全文搜索的要求如下:

  1. 轻量级,对配置要求比较低
  2. 开发活跃,有相应的 Python SDK
  3. 支持 CJK
  4. 最好再支持 docker,真的不想自己写Dockerfile,太累了
  5. 使用简单,像 MongoDB 那样随便插入,不需要定义 schema,心智负担小一点吧

网上搜索了一圈,发现了这么几个比较不错的产品

  1. sonic 官方宣称轻量级的 ES 替代品,支持几十种语言。坏消息是 Python library 没一个能用的,全挂了。官方维护的 nodejs 的倒还可以,不愧是亲儿子啊
  2. Tantivy 官方说也支持十几种语言,并且可以通过插件支持 CJK,还有一个tantivy-py,rust 的 bingding,虽然没啥自动补全了,安装时说不准还得来个 rust,但是也能用。缺点就是,这个tantivy-py,不支持第三方 tokenizer,那和不支持 CJK 有什么区别嘛……
  3. Typesense 也是非常有名的全文搜索引擎,不过对 CJK 的支持不太完善,并且需要定义 schema

最终我找到了今天的主角,Meilisearch,美丽搜索。支持 CJK,可以容忍错别字,有官方的各种语言的 SDK,自带一个简单的检索页面,使用 rust 构建,同样轻量级

轻量级全文搜索引擎 Meilisearch使用体验

Meilisearch 消费数据

对于全文检索来说最重要的无非是两件事情,存储数据和检索。对 Meilisearch 来说这很简单:

  1. import Meilisearch
  2.  
  3. client = Meilisearch.Client("http://127.0.0.1:7700", "masterKey")
  4. index = client.index("demo")
  5. documents = [
  6. {
  7. "id": 123,
  8. "title": "后端的接口大概在半年前就写好了"
  9. },
  10. {
  11. "id": 456,
  12. "title": "我最近才学了一点点 React做好了邮件验证的功能"
  13. }
  14. ]
  15. index.add_documents(documents)

可一次性提交很多数据,Meilisearch 会异步处理。如果想要知道处理结果可以看 tasks 结果,如:

  1. task = index.add_documents(documents)
  2. time.sleep(1)
  3. print(index.get_task(task.task_uid))

Meilisearch 的 ID

Meilisearch 要求数据包含 ID,如果你提交的数据是这样的

  1. documents = [
  2. {
  3. "title": "后端的接口大概在半年前就写好了",
  4. "titleId": "123a",
  5. "content": "好的",
  6. "contentId": "yagwah2"
  7. },
  8. {
  9. "title": "我最近才学了一点点 React做好了邮件验证的功能",
  10. "titleId": "456b",
  11. "content": "不好",
  12. "contentId": "9777"
  13. },
  14. ]

你会发现在网页上看不到这些索引,这是因为 Meilisearch 要求数据中包含唯一 ID,这也是以后更新索引的依据。给每一条数据加上一个唯一的 ID 就好了。或者:

如果你的数据中不包括 id 字段,那么 Meilisearch 将会尝试自动从其他类似 ID 的字段中推导,比如你的数据是:

  1. {
  2. "username": "abcdefg",
  3. "date": "2021-06-13 22:32:48",
  4. "comment": "很好看",
  5. "commentID": "60c61710004833fc8cd4b240",
  6. "origin": "comment",
  7. "hasAvatar": None,
  8. "resourceName": "急诊室的故事",
  9. }

Meilisearch 就会把commentID当作 ID 去使用。但是如果非常不巧你的数据中包含多个 ID 结尾的字段,如

  1. {
  2. "username": "abcdefg",
  3. "date": "2021-06-13 22:32:48",
  4. "comment": "很好看",
  5. "commentID": "60c61710004833fc8cd4b240",
  6. "origin": "comment",
  7. "hasAvatar": None,
  8. "resourceID": 10323,
  9. "resourceName": "急诊室的故事",
  10. }

那么就会推导失败,此时要么自己添加好 id,要么指定主键名

  1. index.add_documents(documents, primary_key="commentID")

Meilisearch 检索数据

非常简单高效,唯一的缺点是没有提供 confidence score 之类的东西,有的时候搜出来的东西实在是离谱🤡

  1. result = index.search("棒")

不用考虑简体繁体,有非常多的参数可以选择,具体可以看文档

集成 Meilisearch

把 Meilisearch 集成到人人影视分享站,要考虑的问题如下:

  1. 数据持久化,我的所有应用都是 docker 跑的,这样就一定要考虑数据持久化的事情
  2. 消费数据,要保证 Meilisearch 的数据和 MongoDB 同步更新,并且在每次重启容器时也不会有丢失
  3. 保证返回一样的数据结构,避免前端改来改去

数据持久化

Meilisearch 的数据并不重要,因此不需要映射宿主机的目录到容器之中,只需要 docker volume 就可以了。

  1. docker volume create meili

然后

  1. docker run -v meili:/meili_data getmeili/meilisearch:v1.0.2

docker-compose的话,也基本一样

  1. version: '3.1'
  2.  
  3. services:
  4. meili:
  5. image: getmeili/meilisearch:v1.0.2
  6. volumes:
  7. - meilisearch:/meili_data
  8.  
  9. volumes:
  10. meilisearch:

第一次消费数据

由于 Meilisearch 是异步处理数据的,也就意味着提交一大堆数据之后,我们并不会阻塞。

因此问题就变成了如何更优雅的从 MongoDB 中查询到我们想要的数据,然后一股脑的塞过去。

此时就要用到 MongoDB 的aggregate了,非常强大的功能,一个查询就可以全部搞定,不用拿 Python 再遍历然后二次查

我的第一次消费数据也非常简单粗暴,程序启动时丢个线程去跑就好了,反正也没多少,第一次几秒钟就结束

  1. threading.Thread(target=engine.run_import).start()

同步数据

MongoDB 有一个功能叫做 Change Stream,这是一种发布订阅的模式,简单的说就是当数据库发生变更,MongoDB 会 “推送” 数据过去。

唯一的小缺点是,数据库药需要配置成 replica set 才可以。当然了,只有一个节点的 replica set 也没问题啦。

用起来很容易,先在配置文件中启用 replica set,如果用的 docker,那么 command 追加--replSet rs0即可,然后启动 MongoDB,

配置 replica set,比如我要配置两个节点的

  1. rs.initiate({
  2. _id: "rs0",
  3. members: [{
  4. _id: 0,
  5. host: "localhost:27017"
  6. },
  7. {
  8. _id: 1,
  9. host: "mongo2:27017"
  10. }]
  11. })

rs.status()可以查看配置结果

多个节点的话,等到数据同步好了,可选配置一下优先级

  1. cfg = rs.conf()
  2. cfg.members[0].priority = 0.5
  3. cfg.members[1].priority = 0.5
  4. cfg.members[2].priority = 1 # 最高
  5. rs.reconfig(cfg)

配置成功之后,比如我想监听comment这个集合的变化

  1. cursor = self.db.comment.watch()
  2. for change in cursor:
  3. print(change)

无论是增删改都会在这个change中体现。我同样也很简单粗暴,拿到数据了add_documents 就好。

数据结构

由于在喂数据的时候就是根据前端的要的数据构造的,所以这里根本不用管,直接把 Meilisearch 返回的结果返回给前端就好了

性能对比

在开了省电模式的 M1 Macbook 单核心、2G 内存的 docker engine 环境,用ab -c 100 -n 2000 的跑分,优化前:

轻量级全文搜索引擎 Meilisearch使用体验

优化后:

轻量级全文搜索引擎 Meilisearch使用体验

每秒请求次数从 20 左右提升到 200 多,直接 10 倍,这不比升级机器配置好多啦!并且 IO、CPU、RAM 占用也比 MongoDB 理想多了。

 

后续

我也不知道这个人是谁,还在孜孜不倦的爬呀爬😂 虽然并无任何影响,但是看着就很恶心人,有什么好的办法吗

轻量级全文搜索引擎 Meilisearch使用体验

 


文章版权归原作者所有丨本站默认采用 CC-BY-NC-SA 4.0 协议进行授权 |
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/meilisearch.html
喜欢 (1)
分享:-)
关于作者:
If you have any further questions, feel free to contact me in English or Chinese.
发表我的评论
取消评论

                     

去你妹的实名制!

  • 昵称 (必填)
  • 邮箱 (必填,不要邮件提醒可以随便写)
  • 网址 (选填)
(7) 个小伙伴在吐槽
  1. 请问博主有考虑过用 `long2ice/meilisync` 这个库来同步数据吗。我最近有一个项目想用这个库来同步 mongodb 和 meilisearch 的数据,但是遇到了一点困难,想请教一下大佬
    阿兹特 eee2023-10-25 10:38 回复
  2. 如果前端广告的加载请求返回独一无二的值(并且这个值在广告商有记录并且可以发请求校验)的话,那就所有的 api 请求都带上这个值,然后后端去校验,不存在就返回点问候语,嘿嘿嘿~😏 (不过有这么贴心的广告商吗 (´・ω・`)?)
    土豆真好吃 2023-03-15 09:07 回复
    • 哈哈哈哈我做过类似的事情,前端的 jQuery 里藏一小段代码,还是 AES 的,请求时带上这个请求头😂 但是还是被爱看代码的爬虫们发现了
      -- 本评论由 Telegram Bot 回复~❤️
      Benny 小土豆 2023-03-15 10:20 回复
  3. ES 低内存也是可以的 目前建在家里用公网 ip 访问提供给服务器 100w 数据量查询速度很快
    max2023-03-15 08:43 回复
    • 🥹我的机器只有 2G 内存,可能有些太少了
      -- 本评论由 Telegram Bot 回复~❤️
      Benny 小土豆 2023-03-15 08:58 回复
      • 我是在我笔记本里跑的 16G 虚拟机套娃 8G 的 Linux 然后又套了个限制 jvm1G 的 ES docker 容器 2G 内存可以尝试一下 ZINC 新兴的一个全文检索引擎
        max2023-03-15 11:11 回复
        • 你这 1G 的堆 / 栈内存是不是有点难为 es 了 zinc 看起来不错 感谢推荐
          -- 本评论由 Telegram Bot 回复~❤️
          Benny 小土豆 2023-03-15 11:17
您直接访问了本站! 莫非您记住了我的域名. 厉害~ 我倍感荣幸啊 嘿嘿