Meilisearch: Rust 全文搜索引擎¶
为什么要学 Meilisearch¶
给应用添加搜索功能通常意味着接入 Elasticsearch——一个功能强大但复杂、资源消耗大的系统。对于中小型应用(电商、文档站、博客、知识库),Elasticsearch 往往是"大炮打蚊子"。
Meilisearch 用 Rust 编写,专注于做一件事:为终端用户提供快速、相关、容错的搜索体验。
| 维度 | Elasticsearch | Meilisearch |
|---|---|---|
| 定位 | 通用搜索和分析引擎 | 终端用户搜索 |
| 安装复杂度 | 高(JVM、集群) | 极低(单二进制) |
| 内存需求 | 4GB+ | 256MB 可启动 |
| 配置量 | 大量映射和分析器配置 | 几乎零配置 |
| Typo 容错 | 需要配置 fuzzy | 开箱即用 |
| 搜索延迟 | 10-100ms | <50ms(通常 <20ms) |
| 中文分词 | 需要 ik 插件 | 内置中文支持 |
| 同义词 | 需要配置 | 简单 API 配置 |
| 学习曲线 | 陡峭 | 平缓 |
Meilisearch 的口号是"一种闪电般快速的搜索体验"——它针对的是你在电商网站搜索栏输入关键词时,期望看到的那种即时响应。
核心概念¶
白话解释¶
Meilisearch 的工作方式: 1. 你把数据(文档)推送给 Meilisearch 2. 它建立优化过的索引 3. 用户搜索时,在毫秒内返回最相关的结果
关键特点是容错搜索:输入 "pyhton" 也能找到 "python",输入 "机器学" 也能找到 "机器学习"。
核心概念表¶
| 概念 | 说明 | 数据库类比 |
|---|---|---|
| Index | 文档的集合 | 数据库表 |
| Document | 一条可搜索的记录 | 表中的一行 |
| Primary Key | 文档唯一标识字段 | 主键 |
| Searchable Attributes | 被搜索的字段 | 全文索引列 |
| Filterable Attributes | 可筛选的字段 | WHERE 条件列 |
| Sortable Attributes | 可排序的字段 | ORDER BY 列 |
| Ranking Rules | 搜索结果排序规则 | 相关性算法 |
| Typo Tolerance | 拼写错误容忍 | Fuzzy 匹配 |
| Facet | 分面搜索(按分类统计) | GROUP BY |
| Synonym | 同义词映射 | 无等价物 |
| Stop Words | 忽略的常见词 | 停用词 |
搜索流程¶
用户输入 "pyyhon 教程"
↓
Typo 容错:识别 "pyyhon" → "python"
↓
分词:["python", "教程"]
↓
索引查找:匹配包含这些词的文档
↓
排序:按相关性规则排序
↓
返回结果(<20ms)
安装配置¶
方式一:Docker(推荐)¶
# 快速启动
docker run -d --name meilisearch \
-p 7700:7700 \
-v meili-data:/meili_data \
-e MEILI_MASTER_KEY='your-master-key-at-least-16-chars' \
getmeili/meilisearch:latest
# 访问 http://localhost:7700 查看仪表板
方式二:直接安装¶
# macOS
brew install meilisearch
# Linux
curl -L https://install.meilisearch.com | sh
sudo mv meilisearch /usr/local/bin/
# 启动
meilisearch --master-key="your-master-key-at-least-16-chars"
方式三:Docker Compose¶
version: '3'
services:
meilisearch:
image: getmeili/meilisearch:latest
container_name: meilisearch
ports:
- '7700:7700'
volumes:
- meili-data:/meili_data
environment:
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
MEILI_ENV: production
MEILI_DB_PATH: /meili_data
MEILI_HTTP_ADDR: 0.0.0.0:7700
restart: unless-stopped
volumes:
meili-data:
API Key 管理¶
Meilisearch 使用 Master Key 派生出 API Key:
# 生成 API Key
curl -X POST 'http://localhost:7700/keys' \
-H 'Authorization: Bearer your-master-key' \
-H 'Content-Type: application/json' \
--data-binary '{
"description": "Search-only key",
"actions": ["search"],
"indexes": ["*"],
"expiresAt": null
}'
| Key 类型 | 权限 | 用途 |
|---|---|---|
| Master Key | 全部权限 | 管理操作(不要暴露给前端) |
| Default Admin Key | 管理权限 | 后端服务索引管理 |
| Default Search Key | 仅搜索 | 前端搜索请求(可暴露) |
快速上手¶
添加文档¶
# 创建索引并添加文档
curl -X POST 'http://localhost:7700/indexes/articles/documents' \
-H 'Authorization: Bearer your-master-key' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 1,
"title": "Python 数据分析入门",
"content": "本教程介绍如何使用 Pandas 进行数据分析...",
"category": "编程",
"tags": ["python", "数据分析", "pandas"],
"date": "2024-01-15"
},
{
"id": 2,
"title": "机器学习基础",
"content": "机器学习是人工智能的一个分支...",
"category": "AI",
"tags": ["机器学习", "AI", "深度学习"],
"date": "2024-02-20"
}
]'
搜索¶
# 基本搜索
curl 'http://localhost:7700/indexes/articles/search' \
-H 'Authorization: Bearer your-search-key' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "python 数据" }'
# 搜索 + 过滤 + 排序
curl 'http://localhost:7700/indexes/articles/search' \
-H 'Authorization: Bearer your-search-key' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "机器学习",
"filter": "category = AI",
"sort": ["date:desc"],
"limit": 10
}'
Python SDK¶
import meilisearch
# 连接
client = meilisearch.Client('http://localhost:7700', 'your-master-key')
# 创建索引
index = client.index('articles')
# 添加文档
documents = [
{
"id": 1,
"title": "单细胞 RNA-seq 分析",
"content": "Scanpy 是一个用于单细胞分析的 Python 工具...",
"category": "生信",
"tags": ["scRNA-seq", "scanpy", "python"],
"year": 2024
},
{
"id": 2,
"title": "GWAS 分析流程",
"content": "全基因组关联分析是一种...",
"category": "生信",
"tags": ["GWAS", "遗传学", "统计"],
"year": 2024
},
]
task = index.add_documents(documents)
print(f"Task UID: {task.task_uid}")
# 等待索引完成
client.wait_for_task(task.task_uid)
# 搜索
results = index.search("单细胞 分析")
for hit in results['hits']:
print(f" {hit['title']} (score: {hit.get('_rankingScore', 'N/A')})")
# 带过滤搜索
results = index.search("分析", {
'filter': 'category = "生信" AND year >= 2024',
'sort': ['year:desc'],
'limit': 20,
'attributesToHighlight': ['title', 'content'],
'highlightPreTag': '<mark>',
'highlightPostTag': '</mark>',
})
JavaScript/TypeScript SDK¶
import { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({
host: 'http://localhost:7700',
apiKey: 'your-search-key',
});
// 搜索
const results = await client.index('articles').search('python', {
filter: ['category = "编程"'],
limit: 10,
attributesToHighlight: ['title', 'content'],
});
console.log(results.hits);
进阶用法¶
索引设置¶
index = client.index('articles')
# 设置可搜索的字段
index.update_searchable_attributes([
'title', # 最高优先级
'content', # 次优先级
'tags', # 第三优先级
])
# 设置可过滤的字段
index.update_filterable_attributes([
'category',
'tags',
'year',
'date',
])
# 设置可排序的字段
index.update_sortable_attributes([
'date',
'year',
'title',
])
# 配置同义词
index.update_synonyms({
'scRNA-seq': ['单细胞RNA测序', 'single cell RNA-seq'],
'ML': ['机器学习', 'machine learning'],
'DL': ['深度学习', 'deep learning'],
})
# 停用词
index.update_stop_words(['的', '了', '和', '是', '在', '有'])
分面搜索(Faceted Search)¶
# 配置分面字段
index.update_filterable_attributes(['category', 'tags', 'year'])
# 搜索时请求分面统计
results = index.search('分析', {
'facets': ['category', 'tags', 'year'],
})
# 返回结果包含各分面的统计
print(results['facetDistribution'])
# {
# "category": {"生信": 5, "编程": 3, "AI": 2},
# "tags": {"python": 4, "R": 3, "scanpy": 2},
# "year": {"2024": 6, "2023": 4}
# }
实时搜索前端集成¶
<!-- 使用 InstantSearch.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@meilisearch/instant-meilisearch/templates/basic_search.css" />
<div id="searchbox"></div>
<div id="hits"></div>
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@meilisearch/instant-meilisearch/dist/instant-meilisearch.umd.min.js"></script>
<script>
const { instantMeiliSearch } = window.instantMeiliSearch;
const searchClient = instantMeiliSearch('http://localhost:7700', 'your-search-key');
const search = instantsearch({
indexName: 'articles',
searchClient,
});
search.addWidgets([
instantsearch.widgets.searchBox({ container: '#searchbox' }),
instantsearch.widgets.hits({
container: '#hits',
templates: {
item: (hit) => `
<div>
<h3>${hit._highlightResult.title.value}</h3>
<p>${hit._highlightResult.content.value}</p>
</div>
`,
},
}),
]);
search.start();
</script>
批量导入¶
import json
# 从 JSON 文件批量导入
with open('articles.json') as f:
documents = json.load(f)
# 分批导入(每批 10000 条)
batch_size = 10000
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
task = index.add_documents(batch)
client.wait_for_task(task.task_uid)
print(f"Imported batch {i // batch_size + 1}")
多租户搜索¶
# 使用 tenant token 实现多租户隔离
import meilisearch
# 为不同用户生成限制性 token
token = client.generate_tenant_token(
api_key_uid='search-key-uid',
search_rules={
'articles': {
'filter': f'user_id = {user_id}'
}
},
expires_at=None
)
# 前端使用这个 token,自动只能搜到自己的数据
常见问题¶
Q1: Meilisearch 能处理多大的数据量?¶
- 推荐文档数量:<1000 万条(单实例)
- 超过这个量级考虑 Elasticsearch/Typesense
- 单个文档建议 <1MB
- 索引大小通常是原始数据的 1-3 倍
Q2: 中文搜索支持怎么样?¶
Meilisearch 内置了中文分词支持(基于 jieba),开箱即用。不需要像 Elasticsearch 那样安装 ik 分词插件。
Q3: 如何保持搜索数据和数据库同步?¶
常见方案: - 应用层同步:数据库写入后,同时更新 Meilisearch - 定时全量同步:每 N 分钟从数据库拉取最新数据 - 事件驱动:通过消息队列异步更新
# 应用层同步示例
def create_article(article_data):
# 1. 写入数据库
article = db.insert(article_data)
# 2. 同步到 Meilisearch
index.add_documents([article.to_dict()])
return article
Q4: Meilisearch 和 Typesense 怎么选?¶
两者定位类似,都是轻量级搜索引擎: - Meilisearch:更好的中文支持,更活跃的社区,Rust 编写 - Typesense:更好的集群支持,C++ 编写 - 中文项目推荐 Meilisearch
Q5: 生产环境部署建议?¶
- 启用 Master Key(
MEILI_MASTER_KEY) - 设置
MEILI_ENV=production - 使用反向代理(Caddy/Nginx)处理 HTTPS
- 定期备份
meili_data目录 - 搜索请求使用 Search Key(不要暴露 Master Key)
参考资源¶
| 资源 | 链接 |
|---|---|
| 官方网站 | https://www.meilisearch.com |
| GitHub 仓库 | https://github.com/meilisearch/meilisearch |
| 官方文档 | https://www.meilisearch.com/docs |
| Python SDK | https://github.com/meilisearch/meilisearch-python |
| JavaScript SDK | https://github.com/meilisearch/meilisearch-js |
| InstantSearch 集成 | https://github.com/meilisearch/instant-meilisearch |
| 搜索演示 | https://where2watch.meilisearch.com |
总结:Meilisearch 是"搜索即服务"理念的最佳开源实现。如果你的应用需要面向用户的搜索功能(知识库搜索、文章搜索、产品搜索),Meilisearch 能在最短时间内给你一个专业级的搜索体验。它的容错搜索、中文分词、即时响应让用户体验远超简单的 SQL LIKE 查询。