开源项目

Meilisearch 自托管搜索上手:为站内内容与文档库补上可用的全文检索

站内搜索最常见的问题不是“没有搜索框”,而是搜索框背后的查询太原始:文档库用 LIKE '%关键词%' 扫表,标题命中了正文却不命中,错一个字就零结果,结果顺序按主键而不是按相关性排。数据量小的时候还能忍,文档一多、字段一杂,搜索就会变成“有功能但不好用”。Meilisearch 要解决的正是这一段:它是一个用 Rust 写的开源搜索引擎,对外提供 REST API,默认监听 7700 端口,把索引、文档、设置和异步任务拆成几个清晰的资源,适合给网站、文档站和内部知识库补上一层可用的全文检索。

Meilisearch Docker 部署与数据持久化示意

Meilisearch 的定位:搜索层,不是数据库

理解 Meilisearch 的一个简单方式是把它当成“搜索层”。你的业务数据仍然放在 PostgreSQL、MySQL、SQLite 或 Markdown 文件里,Meilisearch 只保存为了搜索而建立的文档副本。每条文档是一个 JSON 对象,必须有一个唯一主键;多个文档组成一个索引(index)。查询时你请求某个索引,Meilisearch 在索引内部完成分词、匹配和排序,返回文档或文档片段。

它和“在数据库里加索引”不是一回事。数据库的全文检索通常受限于语言支持、分词能力和排序模型;Meilisearch 则把相关性排序做成了默认能力,开箱就有错字容忍、前缀匹配、字段权重和同义词设置。代价是你要多维护一份索引数据,并且接受“写入后不是立刻可查”的异步模型。

写入是异步的:添加文档、修改设置、创建索引都会返回一个 taskUid,任务先进入队列,完成状态要另外查询。这个设计让批量导入不会阻塞查询,但也意味着你不能在 POST 返回后立刻假设数据已经可搜。第一次使用时,很多“搜不到”的问题其实只是任务还没跑完。

用 Docker 跑起一个可验证的实例

准备与启动

需要准备的东西不多:一台能跑 Docker 的机器、一个用来放数据的目录,以及一个足够长的 master key。生产环境不要把 master key 写进命令历史或提交到 Git,先生成并保存到安全位置:

纯文本
openssl rand -base64 32 > meili_master_key.txt
chmod 600 meili_master_key.txt

开发时可以跑一个前台容器快速验证:

纯文本
docker run -it --rm 
  -p 7700:7700 
  -v $(pwd)/meili_data:/meili_data 
  getmeili/meilisearch:latest 
  meilisearch --master-key="dev-key-change-me"

更接近生产环境的启动方式是把容器放到后台,用环境变量传入密钥,并固定数据目录:

纯文本
docker run -d --name meilisearch 
  --restart unless-stopped 
  -p 7700:7700 
  -e MEILI_ENV=production 
  -e MEILI_MASTER_KEY="$(cat meili_master_key.txt)" 
  -v /srv/meilisearch/data:/meili_data 
  getmeili/meilisearch:latest

容器内的工作目录是 /meili_data,数据库文件位于 /meili_data/data.ms。-v 挂载不是可选项:不挂载的话,容器删除或重建时索引和文档会一起消失。官方 Docker 指南也把数据持久化单独拿出来讲,并提醒默认容器写入的数据会随容器停止而丢失,参见 Using Meilisearch with Docker。

生产模式 MEILI_ENV=production 会要求设置 master key,并关闭开发模式下的一些宽松行为。示例里用了 latest 标签,实际部署建议固定到具体版本号,升级前先看对应版本的 release notes,避免镜像悄悄变化带来行为差异。

确认服务健康

启动后先确认进程是否正常:

纯文本
curl http://localhost:7700/health

正常会返回类似 {"status":"available"} 的内容。再带 key 查版本:

纯文本
curl -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  http://localhost:7700/version

如果 /health 都不通,先看容器日志和端口占用;如果健康检查通过但带 key 的请求返回 401,问题通常在密钥内容或 Authorization 头格式,而不是索引。

索引、文档与任务:先把数据写进去

创建索引

下面用一个 docs 索引演示最小流程。主键用 id,文档里必须有这个字段:

纯文本
curl -X POST 'http://localhost:7700/indexes' 
  -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  -H 'Content-Type: application/json' 
  --data '{"uid":"docs","primaryKey":"id"}'

返回里会带一个 taskUid。创建索引本身也是任务,可以稍后统一查状态。

导入文档

先放两条中文文档进去,字段设计尽量贴近真实内容:标题、正文、标签。标签后面可以用来做过滤。

纯文本
curl -X POST 'http://localhost:7700/indexes/docs/documents' 
  -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  -H 'Content-Type: application/json' 
  --data '[
    {
      "id": 1,
      "title": "Docker 部署入门",
      "content": "使用容器运行服务,挂载数据卷持久化,避免重启后数据丢失。",
      "tags": ["部署", "Docker"]
    },
    {
      "id": 2,
      "title": "中文分词与搜索相关性",
      "content": "分词结果会影响召回率和排序,专有名词需要额外处理。",
      "tags": ["搜索", "中文"]
    }
  ]'

假设返回的 taskUid 是 0,用任务接口确认结果:

纯文本
curl -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  http://localhost:7700/tasks/0

status 变成 succeeded 才算写入完成。如果是 failed,error 字段会说明原因,常见的是主键重复、JSON 格式错误或字段类型不一致。批量导入大量文档时,不要一条一条发请求,按批提交并观察任务队列;队列积压时查询仍然可用,但新内容会延迟出现。

设置可搜索字段和过滤字段

默认情况下 Meilisearch 会搜索所有字段,但真实项目通常需要控制权重和返回内容。下面这组设置把标题放在可搜索字段最前面,把标签设为可过滤,并加上中文停用词和两组同义词:

纯文本
curl -X PATCH 'http://localhost:7700/indexes/docs/settings' 
  -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  -H 'Content-Type: application/json' 
  --data '{
    "searchableAttributes": ["title", "content", "tags"],
    "filterableAttributes": ["tags"],
    "sortableAttributes": ["id"],
    "stopWords": ["的", "了", "和"],
    "synonyms": {
      "部署": ["安装", "上线"],
      "搜索": ["检索", "查询"]
    }
  }'

searchableAttributes 的顺序会影响 attribute 排序规则:越靠前的字段,在同等匹配条件下权重越高。filterableAttributes 不设置的话,带 filter 的查询会报错,这是新手很容易踩的一个点。

查询与前端接入的最小闭环

直接查一次

纯文本
curl -X POST 'http://localhost:7700/indexes/docs/search' 
  -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  -H 'Content-Type: application/json' 
  --data '{
    "q": "中文 搜索",
    "limit": 5,
    "attributesToRetrieve": ["id", "title", "tags"],
    "attributesToHighlight": ["title", "content"]
  }'

返回的 hits 是命中文档,_formatted 里会带高亮片段。limit 控制每页数量,配合 offset 做分页;只取必要字段能减少传输量,也能避免把内部字段暴露给前端。

不要在前端使用 master key

master key 拥有全部权限,放到浏览器里等于把索引管理权限公开。正确做法是创建一个只能搜索的 key:

纯文本
curl -X POST 'http://localhost:7700/keys' 
  -H "Authorization: Bearer $(cat meili_master_key.txt)" 
  -H 'Content-Type: application/json' 
  --data '{
    "description": "website search",
    "actions": ["search"],
    "indexes": ["docs"],
    "expiresAt": null
  }'

返回的 key 字段才是给前端用的搜索 key。它只能对 docs 索引执行搜索,不能写文档、改设置或管理其他索引。即便如此,也建议把 Meilisearch 放在内网或反向代理后面,加上 TLS、访问频率限制和必要的来源控制;搜索 key 泄露虽然破坏力有限,但仍然可能被用来刷请求。

如果站点已经有后端,更稳妥的方式是让后端代理搜索请求:后端持有搜索 key,前端只调用自己的 /api/search,由后端决定返回哪些字段、是否过滤草稿内容、是否按用户权限裁剪结果。这样多一层控制,也方便做缓存和日志。

中文分词与相关性调优的边界

中文不是“配一下同义词”就结束

Meilisearch 使用内置的 Charabia 分词器,对中文有基础支持,不是简单按空格切词。但它和 Elasticsearch 生态里可插拔的中文分词插件不是同一种东西:它不提供一套让你自由替换词典、细粒度控制切分方式的完整接口。实际效果取决于版本、字段内容和查询写法,不能假设“装完就懂中文”。

具体会遇到的边界包括:产品型号、缩写、人名、行业术语可能被拆成多个词,导致召回变差;同音字和错别字不会因为 typoTolerance 就自动解决,错字容忍主要面向拉丁字母的拼写错误;停用词设得太多会把有意义的短词去掉。较新版本提供 dictionary 设置,可以把专有名词加入分词词典;也支持 localizedAttributes 按字段声明语言。使用前先确认你所装版本是否包含这些设置,字段名和取值以对应版本文档为准。

Meilisearch 索引、分词与相关性调优流程示意

可调的几只手

调优时不要一上来就改 rankingRules。默认排序规则是 words、typo、proximity、attribute、sort、exactness,大多数问题可以通过更前面的设置解决:

  • 字段顺序:把标题、摘要放前面,正文放后面;不要让标签或无关字段抢占 searchableAttributes 的前排。
  • 同义词:把用户实际会输入的说法和文档里的正式说法互相映射,比如“安装”和“部署”、“检索”和“搜索”。改完用真实查询验证,不要只看配置成功。
  • 停用词:中文停用词要保守。去掉“的”“了”通常安全,去掉“上”“下”“前”这类方向词可能破坏短语。
  • 分词补偿:如果版本支持 dictionary,把产品名、型号、专有名词加进去;不支持时,用同义词或在文档里补充别名。
  • 搜索字段限制:查询时用 attributesToSearchOn 把搜索限制在必要字段,减少噪声召回。
  • 排序观察:搜索请求里加 showRankingScore: true,看 _rankingScore 的变化,比凭感觉调参可靠。

typoTolerance 对中文的收益有限,不要指望它处理同音字;exactness 和 sort 的顺序调整会影响“精确匹配优先”还是“按时间优先”,改之前先明确业务目标。

用一组真实查询做回归

相关性调优最怕“改完感觉好多了”。更实际的做法是准备 20 到 50 条真实用户查询,记录当前 top 5 结果,每次改设置后重新跑一遍,看召回有没有变差、目标文档有没有进入前三。这个查询集不需要复杂工具,一个 JSON 文件加一段脚本就够。开箱效果是起点,不是最终效果;没有评估集,调优很容易变成盲调。

资源占用、备份与升级

资源占用看的是索引规模,不是空实例

Meilisearch 用 Rust 编写,数据落在磁盘上,查询性能受内存、索引大小、字段数量和并发影响。空实例占用很低,但不能用空实例的数字推断生产环境。文档越多、searchableAttributes 越多、单条文档越大,索引和内存映射的占用就越高。容器部署时建议给内存设置上限并观察实际使用,不要和数据库、构建任务挤在一台没有余量的机器上。

如果发现查询变慢,先看是不是返回了过多字段、分页过深、过滤字段没有设置,或者一次导入了大量文档导致任务队列堆积。把 attributesToRetrieve 收窄、限制 limit、给容器留出磁盘和内存余量,通常比换硬件更先见效。

备份:dump 和 snapshot 不是一回事

Docker volume 只保证容器重建后数据还在,它不是备份。误删索引、错误设置、版本升级失败,volume 都救不了。Meilisearch 提供两种备份方式:

  • dump:逻辑备份,通过 POST /dumps 触发,完成后在 /meili_data/dumps/ 下生成文件。它适合跨版本迁移和长期归档,导入时用 --import-dump 启动。
  • snapshot:物理快照,通过 POST /snapshots 触发,文件在 /meili_data/snapshots/ 下。它适合同版本快速回滚,恢复时要停实例并替换数据目录,操作前先看对应版本文档。

生产环境至少要做到定时 dump 并把文件传到另一台机器或对象存储。升级前先 dump,再换镜像;升级后先验证健康检查和几条关键查询,再放流量。直接用新版本挂旧数据目录,出问题时回滚成本会高很多。

常见故障排查

现象常见原因处理
请求返回 401master key 不对、没带 Authorization 头、shell 变量为空检查 Bearer 后面的值;确认读取密钥的文件存在且权限正确
写入后任务 failed主键重复、JSON 格式错误、字段类型不一致查 GET /tasks/{taskUid} 的 error 字段,修正后重新提交
容器重启后索引消失没有挂载 /meili_data,或挂载路径写错用 docker inspect 看 Mounts,确认宿主机目录和容器路径对应
中文查询召回差字段没设为可搜索、分词切分不符合预期、文档还没索引完先确认任务状态,再检查 searchableAttributes;用同义词或 dictionary 补偿
带 filter 的查询报错过滤字段没加入 filterableAttributes修改索引设置,等任务完成后再查
结果排序不符合预期searchableAttributes 顺序、同义词或 rankingRules 被改动用 showRankingScore 观察;临时恢复默认规则做对比
查询变慢或内存吃紧返回字段过多、分页过深、索引字段太杂、容器资源不足收窄 attributesToRetrieve、限制 limit、精简可搜索字段、给容器留余量
升级后服务起不来数据目录与版本不兼容、启动参数变化回滚镜像;用升级前的 dump 重新导入,再排查版本说明

适合与不适合的场景

Meilisearch 适合单机或小规模集群下的站内搜索、文档库检索、内部知识库和产品目录搜索。它的优势是部署简单、API 直观、默认相关性可用,开发者可以在很短时间里跑通“导入数据—查询—前端展示”的闭环。它的边界也很清楚:如果你需要复杂的聚合分析、PB 级日志检索、跨集群容灾,或者需要像 Elasticsearch 那样深度定制中文分词插件,Meilisearch 不是替代品。

最小验证路径可以压到四步:用 Docker 挂载数据目录启动实例;创建索引并导入一批真实文档;配置 searchableAttributes、filterableAttributes 和同义词;用一组真实查询检查召回和排序。跑完这四步,再决定是否把它接进生产。开箱效果能帮你快速判断方向,但中文分词、相关性排序和备份策略,仍然需要按自己的内容特点和查询习惯逐项验证。

热门话题

52okp 是一名关注人工智能、开源软件与效率工具的技术内容创作者,长期实践 Stable Diffusion、ComfyUI、AI 智能体、MCP、Codex 和各类开源项目。通过实际安装、配置与测试,整理可复现的操作教程、问题排查方法和工具使用经验。

登录用户才能发表评论! 登录账户

取消回复

评论列表 (6条):

加载更多评论 Loading...

延伸阅读:

trove-ai 稍后读与 AI 知识库:把中文互联网内容收进可检索的个人库

收藏夹里有 47 篇公众号长文、十几个知乎回答、三份 PDF 报告和几个播客链接,它们大多是在地铁上、会议间隙或者睡前刷...

52okp
2026-11-07

用 Dify 搭建 RAG 应用前,先想清楚数据、检索与评估这三件事

在搭建 AI 应用时,许多开发者都遇到过一个共同的难题:明明花了很多时间上传了知识库数据,却发现 AI 回答总是跑偏,或...

52okp
2026-10-22

OpenClaw 本地智能体部署指南:用开源网关打通 IM 工具与自主任务执行

许多开发者在尝试将 AI 智能体(Agent)引入日常工作流时,往往会面临两难抉择:云端 SaaS 平台开箱即用,但敏感...

52okp
2026-11-02

NocoBase 自托管业务系统实战:用无代码与 AI 插件搭出团队管理工作流

团队内部的管理系统往往处在两个极端之间:要么用现成的 SaaS 工具,功能被厂商锁死,数据也放在别人手里;要么从零开发,...

52okp
2026-10-30

Stirling-PDF 自托管实践:把常用 PDF 操作搬到自己的服务器上

季度末整理合同归档时,我手头有二十多份扫描件要合并成册,还要把几份带敏感信息的报价单压到能发邮件的体积。按老习惯打开某个...

52okp
2026-10-19
    返回顶部