在 VPS Ubuntu 上安装 Meilisearch 进行全文搜索

Meilisearch 是一款专为开发者设计的开源搜索引擎。它能在毫秒内返回结果,优雅地处理错别字,并支持多维度筛选——全部运行在自托管的 VPS 上。本指南涵盖 Ubuntu 的完整安装、systemd 服务配置、Nginx 反向代理,以及与 PHP 和 Node.js 的集成。

为什么选择 Meilisearch 而非 SQL LIKE

系统要求:Meilisearch 会将整个索引加载到内存中。最低需要 1 GB 内存;生产环境建议 2 GB 或以上。配备 2 GB 以上内存的 VPS 是理想选择。

在 Ubuntu 上安装 Meilisearch

第一步:下载并安装

# Official installer
curl -L https://install.meilisearch.com | sh

# Move binary to PATH
sudo mv ./meilisearch /usr/local/bin/

# Verify
meilisearch --version

第二步:创建专用用户和数据目录

sudo useradd -r -s /bin/false meilisearch
sudo mkdir -p /var/lib/meilisearch/data
sudo chown -R meilisearch:meilisearch /var/lib/meilisearch

第三步:创建 systemd 服务

sudo nano /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch Search Engine
After=network.target

[Service]
User=meilisearch
Group=meilisearch
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch \
  --db-path /var/lib/meilisearch/data \
  --http-addr 127.0.0.1:7700 \
  --master-key YOUR_MASTER_KEY_HERE \
  --env production
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

将 YOUR_MASTER_KEY_HERE 替换为强随机密钥(至少 16 个字符)。可使用以下命令生成:openssl rand -hex 32

第四步:启用并启动

sudo systemctl daemon-reload
sudo systemctl enable meilisearch
sudo systemctl start meilisearch
sudo systemctl status meilisearch

# Test the API
curl http://127.0.0.1:7700/health

Nginx 反向代理

通过添加 Nginx 服务器块,将 Meilisearch 通过 HTTPS 对外暴露:

server {
    listen 443 ssl;
    server_name search.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/search.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/search.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:7700;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

使用 Node.js 索引文档

npm install meilisearch
const { MeiliSearch } = require('meilisearch');

const client = new MeiliSearch({
  host: 'https://search.yourdomain.com',
  apiKey: 'YOUR_MASTER_KEY_HERE'
});

const index = client.index('products');

// Add documents
await index.addDocuments([
  { id: 1, name: 'iPhone 15', category: 'smartphone', price: 32900 },
  { id: 2, name: 'Samsung Galaxy S24', category: 'smartphone', price: 28900 },
]);

// Search
const results = await index.search('iphone', {
  limit: 10,
  attributesToHighlight: ['name']
});
console.log(results.hits);

与 PHP 集成

composer require meilisearch/meilisearch-php
<?php
require 'vendor/autoload.php';
use Meilisearch\Client;

$client = new Client('https://search.yourdomain.com', 'YOUR_MASTER_KEY_HERE');
$index  = $client->index('products');

// Index documents
$index->addDocuments([
  ['id' => 1, 'name' => 'Test Product', 'price' => 999],
]);

// Search
$results = $index->search('product');
foreach ($results->getHits() as $hit) {
    echo $hit['name'] . PHP_EOL;
}

API 密钥安全:对于客户端 JavaScript,请创建仅用于搜索的 API 密钥,而非直接暴露主密钥。使用 POST /keys 配合 "actions":["search"] 生成权限受限的密钥。

调整排序规则与可过滤属性

Meilisearch 开箱即有合理的默认配置,但您可以针对每个索引自定义排序和过滤规则以匹配特定场景。在过滤查询中使用任何属性之前,必须先将其声明为可过滤属性——Meilisearch 需要为此构建专用结构以实现快速过滤。

声明可过滤和可排序属性

const index = client.index('products');

// Declare attributes available for filtering
await index.updateFilterableAttributes([
  'category',
  'price',
  'brand',
  'in_stock'
]);

// Declare attributes available for sorting
await index.updateSortableAttributes([
  'price',
  'created_at',
  'rating'
]);

// Search with filter and sort
const results = await index.search('laptop', {
  filter: 'category = "electronics" AND price < 30000 AND in_stock = true',
  sort: ['price:asc'],
  limit: 20
});

自定义排序规则

// View current ranking rules
await index.getRankingRules();

// Update ranking rules (append a custom rule)
await index.updateRankingRules([
  'words',
  'typo',
  'proximity',
  'attribute',
  'sort',
  'exactness',
  'rating:desc'  // Custom: boost higher-rated items
]);

监控、健康检查与备份

在生产环境中,监控和备份可保护搜索索引免遭意外数据丢失,并帮助您及早发现性能问题。

健康检查与统计接口

# Check Meilisearch health
curl -H 'Authorization: Bearer MASTER_KEY' \
  http://127.0.0.1:7700/health

# Global stats (all indexes)
curl -H 'Authorization: Bearer MASTER_KEY' \
  http://127.0.0.1:7700/stats

# Stats for a specific index
curl -H 'Authorization: Bearer MASTER_KEY' \
  http://127.0.0.1:7700/indexes/products/stats

创建备份转储文件

# Trigger a dump via the API
curl -X POST 'http://127.0.0.1:7700/dumps' \
  -H 'Authorization: Bearer MASTER_KEY'

# Dump files land in:
ls /var/lib/meilisearch/data/dumps/

# Alternatively, rsync the entire data directory
sudo rsync -av /var/lib/meilisearch/data/ /backup/meilisearch/

使用 Cron 自动化每日备份

sudo crontab -e

# Back up every day at 02:00
0 2 * * * rsync -a /var/lib/meilisearch/data/ /backup/meilisearch/$(date +\%Y-\%m-\%d)/
API 接口 方法 用途
/health GET 服务健康状态
/stats GET 索引统计与磁盘用量
/dumps POST 创建完整数据转储
/indexes/{uid}/documents DELETE 删除索引中的所有文档

容错设置与停用词

容错搜索是 Meilisearch 的旗舰功能之一。您可以自定义其纠正拼写错误的力度,并定义停用词——如 "the"、"and"、"in" 等无搜索意义的词——在文档匹配时忽略这些词。

为每个索引配置容错设置

await index.updateTypoTolerance({
  enabled: true,
  minWordSizeForTypos: {
    oneTypo: 5,    // allow 1 typo in words of 5+ chars
    twoTypos: 9    // allow 2 typos in words of 9+ chars
  },
  disableOnWords: ['iphone', 'samsung'],      // brand names must match exactly
  disableOnAttributes: ['serial_number', 'sku']
});

定义停用词

await index.updateStopWords([
  'the', 'a', 'an', 'is', 'in', 'on', 'at', 'for', 'with',
  'and', 'or', 'of', 'to', 'from', 'by'
]);

// Inspect the current stop words
await index.getStopWords();

停用词可减小索引体积并提升相关度——当 Meilisearch 忽略填充词时,有意义的词条在排序公式中获得更大权重。对于用户以自然语言短语搜索的商品目录而言,这尤为重要。

即时搜索与前端实时搜索

即时搜索——用户每输入一个字符即显示结果——正是 Meilisearch 最闪耀的场景。低于 50 毫秒的响应时间让 UI 即便在普通 VPS 上也能给人即时感。关键在于将防抖输入监听器与仅供搜索的 API 密钥相结合,并暴露给浏览器。

原生 JavaScript 即时搜索

<input type="text" id="searchBox" placeholder="Search products...">
<div id="searchResults"></div>

<script>
const searchBox = document.getElementById('searchBox');
const resultsDiv = document.getElementById('searchResults');

let debounceTimer;
searchBox.addEventListener('input', function() {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(async () => {
    const query = this.value.trim();
    if (query.length < 2) { resultsDiv.innerHTML = ''; return; }

    const res = await fetch(
      'https://search.yourdomain.com/indexes/products/search',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer SEARCH_ONLY_API_KEY'
        },
        body: JSON.stringify({
          q: query,
          limit: 8,
          attributesToHighlight: ['name', 'description'],
          highlightPreTag: '<mark>',
          highlightPostTag: '</mark>'
        })
      }
    );
    const data = await res.json();

    resultsDiv.innerHTML = data.hits.map(hit => `
      <div class="result-item">
        <strong>${hit._formatted?.name || hit.name}</strong>
        <span>$${hit.price.toLocaleString()}</span>
      </div>
    `).join('');
  }, 200); // 200ms debounce
});
</script>

200 毫秒的防抖可防止每次按键都发出请求。响应中的 _formatted 字段包含每个匹配属性的高亮版本,可直接在浏览器中渲染。

为即时搜索配置 Nginx CORS 头

# In your Meilisearch Nginx server block
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;

location / {
    if ($request_method = 'OPTIONS') { return 204; }
    proxy_pass http://127.0.0.1:7700;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

性能建议:如果 Meilisearch 与您的 Web 应用共用同一台 VPS,建议通过 PHP 后端接口转发搜索请求,而不是在 JavaScript 中直接调用 Meilisearch。这样既能隐藏 API 密钥,又能完全消除 CORS 配置需求。

更新与删除文档

Meilisearch 的文档管理 API 简单直观。所有写操作(添加、更新、删除)均为异步执行——API 立即返回任务 ID,Meilisearch 在后台处理变更。这种设计即便在索引大批量数据时也能保持 API 的高速响应。

更新与删除操作

// Update one or more documents (partial update supported)
await index.updateDocuments([
  { id: 1, name: 'iPhone 15 Pro', price: 42900 }
]);

// Delete a single document by its primary key
await index.deleteDocument(1);

// Delete multiple documents matching a filter
await index.deleteDocuments({ filter: 'price < 1000' });

// Check the status of an async task
const task = await index.addDocuments([...]);
const status = await client.getTask(task.taskUid);
console.log(status.status); // 'enqueued' | 'processing' | 'succeeded' | 'failed'

查看任务队列

# List all tasks
curl -H 'Authorization: Bearer MASTER_KEY' \
  'http://127.0.0.1:7700/tasks'

# Get a specific task by ID
curl -H 'Authorization: Bearer MASTER_KEY' \
  'http://127.0.0.1:7700/tasks/TASK_ID'

对于大量导入,请持续轮询任务接口,直到状态变为 succeeded,再运行首次搜索查询——文档在索引完成前不可被搜索到。

多索引搜索与联合查询

多索引搜索允许您在单个 HTTP 请求中查询多个索引。这非常适合同时展示商品、文章和分类的全局搜索框——将三次请求减少为一次。

// Search across multiple indexes in one call
const results = await client.multiSearch({
  queries: [
    { indexUid: 'products',   q: 'iphone', limit: 5 },
    { indexUid: 'articles',   q: 'iphone', limit: 3 },
    { indexUid: 'categories', q: 'smartphone', limit: 3 }
  ]
});

console.log(results.results[0].hits); // products
console.log(results.results[1].hits); // articles

多索引搜索特别适用于按类型分组显示结果的即时搜索下拉框——商品在上、文章居中、分类在下——仅凭一个运行在您 VPS 上的自托管引擎,即可为用户带来类似 Google 的搜索体验。

Meilisearch vs Elasticsearch vs Typesense

为 VPS 选择自托管搜索引擎时,三款开源选项最为主流。各自的优势因项目规模和技术复杂度而有所不同。

特性 Meilisearch Elasticsearch Typesense
安装难度 低——单一二进制文件 高——需要 Java,配置复杂 低——与 Meilisearch 类似
最低内存 1 GB 2–4 GB 1 GB
容错搜索 出色(内置) 需手动配置 良好(内置)
最适合 电商、博客、中型应用 日志分析、企业级应用 电商、SaaS

对于正在启动新项目或运行中型应用的开发者,Meilisearch 在简洁性、速度和低资源占用之间提供了最佳平衡。Elasticsearch 则是需要企业级强大日志聚合与分析能力的团队的正确选择。

需要 VPS 部署搜索引擎?

Linux VPS 每月起价 500 泰铢,提供完整 Root 权限与 SSD 存储——运行 Meilisearch、Elasticsearch 或任何您选择的搜索方案。

查看 VPS 套餐

查看泰国VPS主机全部套餐 →