跳转至

Meilisearch: Rust 全文搜索引擎

为什么要学 Meilisearch

给应用添加搜索功能通常意味着接入 Elasticsearch——一个功能强大但复杂、资源消耗大的系统。对于中小型应用(电商、文档站、博客、知识库),Elasticsearch 往往是"大炮打蚊子"。

Meilisearch 用 Rust 编写,专注于做一件事:为终端用户提供快速、相关、容错的搜索体验

维度ElasticsearchMeilisearch
定位通用搜索和分析引擎终端用户搜索
安装复杂度高(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

pip install meilisearch
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

npm install meilisearch
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(['的', '了', '和', '是', '在', '有'])
# 配置分面字段
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 SDKhttps://github.com/meilisearch/meilisearch-python
JavaScript SDKhttps://github.com/meilisearch/meilisearch-js
InstantSearch 集成https://github.com/meilisearch/instant-meilisearch
搜索演示https://where2watch.meilisearch.com

总结:Meilisearch 是"搜索即服务"理念的最佳开源实现。如果你的应用需要面向用户的搜索功能(知识库搜索、文章搜索、产品搜索),Meilisearch 能在最短时间内给你一个专业级的搜索体验。它的容错搜索、中文分词、即时响应让用户体验远超简单的 SQL LIKE 查询。