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

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 按字段声明语言。使用前先确认你所装版本是否包含这些设置,字段名和取值以对应版本文档为准。

可调的几只手
调优时不要一上来就改 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,再换镜像;升级后先验证健康检查和几条关键查询,再放流量。直接用新版本挂旧数据目录,出问题时回滚成本会高很多。
常见故障排查
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 请求返回 401 | master 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 和同义词;用一组真实查询检查召回和排序。跑完这四步,再决定是否把它接进生产。开箱效果能帮你快速判断方向,但中文分词、相关性排序和备份策略,仍然需要按自己的内容特点和查询习惯逐项验证。

评论列表 (6条):
加载更多评论 Loading...