🔍 搜索引擎选型

Meilisearch · Typesense · Elasticsearch · OpenSearch · Vespa · pgvector · 向量搜索 · 混合搜索
从应用内搜索到 AI 驱动的语义检索,一文搞懂选什么

目录 01 搜索引擎全景:2026 年的分叉路口 02 全文搜索基础:倒排索引与 BM25 03 向量搜索:语义检索的崛起 04 混合搜索:2026 年的标准答案 05 Meilisearch — 极简主义的 Rust 之选 06 Typesense — 开发者友好的 C++ 快马 07 Elasticsearch — 巨人仍在 08 OpenSearch — AWS 生态的自然选择 09 Vespa — 实时 AI 服务引擎 10 PostgreSQL + pgvector — 不加新组件的搜索方案 11 全面对比表 12 决策框架:我该选什么? 13 实战代码示例 14 进阶主题 15 关联模块

01 搜索引擎全景:2026 年的分叉路口

2018 年,"用什么搜索引擎"几乎只有一个答案:Elasticsearch。日志用它、站内搜索用它、推荐也勉强用它。

2026 年,同一个问题至少分裂成五个方向:

📱 应用内搜索

商品目录、文档搜索、自动补全
候选:Meilisearch、Typesense、Algolia

Rust C++
📊 通用搜索 + 分析

日志、指标、聚合、全文检索
候选:Elasticsearch、OpenSearch

Java/JVM
🤖 RAG / AI 搜索

向量 + 全文 + 重排序
候选:Vespa、pgvector、Meilisearch hybrid

C++ PostgreSQL
📝 日志 / 可观测性

海量日志、S3 归档、低成本
候选:Quickwit、OpenSearch、Loki

Rust Go
核心观点:2026 年,"全文 vs 向量"的二选一已经死了。答案几乎总是混合搜索(BM25 + 向量 + 重排序)。问题在于谁让混合搜索做得最顺滑。

02 全文搜索基础:倒排索引与 BM25

倒排索引(Inverted Index)

搜索引擎的核心数据结构。不是"文档→词",而是"词→文档列表"

# 正排索引:文档 → 词
Doc1: ["搜索引擎", "选型", "SaaS"]
Doc2: ["Meilisearch", "搜索引擎", "Rust"]

# 倒排索引:词 → 文档列表
"搜索引擎" → [Doc1, Doc2]
"选型"      → [Doc1]
"SaaS"      → [Doc1]
"Meilisearch" → [Doc2]
"Rust"      → [Doc2]

倒排索引让查找包含某个词的文档变成 O(1) 的跳表查找,而不是遍历所有文档。

BM25 算法

全文搜索的默认排序算法。核心思想:一个词在文档中出现越多、在整个语料库中越罕见,这个文档的相关性越高。

BM25 公式

score(D, Q) = Σ IDF(qi) × f(qi, D) × (k1 + 1) / (f(qi, D) + k1 × (1 - b + b × |D| / avgdl))

其中 f(qi,D) = 词频,IDF = 逆文档频率,b = 文档长度归一化(通常 0.75),k1 = 词频饱和参数(通常 1.2-2.0)

BM25 的局限性

03 向量搜索:语义检索的崛起

Embedding:把文本变成数字

向量搜索的第一步是将文本通过嵌入模型转换为高维向量。语义相近的文本在向量空间中距离更近。

# 使用 OpenAI text-embedding-3-small
from openai import OpenAI
client = OpenAI()

response = client.embeddings.create(
    model="text-embedding-3-small",
    input="笔记本电脑推荐"
)
# → 1536 维向量 [0.0023, -0.0145, 0.0078, ...]

response2 = client.embeddings.create(
    model="text-embedding-3-small", 
    input="laptop recommendation"
)
# → 语义相似,向量距离很近!

HNSW 索引

目前最主流的向量索引算法(Hierarchical Navigable Small World)。几乎所有搜索引擎都用它。

HNSW 工作原理
  1. 多层图结构:类似跳表,上层稀疏连接用于快速跳跃,下层密集连接用于精确定位
  2. 搜索过程:从最高层入口节点开始,贪心地找到最近邻,然后下降到下一层继续搜索
  3. 时间复杂度:O(log N),比暴力搜索 O(N) 快几个数量级
  4. 参数:M(每层最大连接数,默认 16-64)、ef_construction(构建时搜索宽度)、ef_search(查询时搜索宽度)

M 越大索引越大但召回更好;ef_search 越大查询越慢但越准确。这是空间/时间/质量的三方权衡。

向量搜索的局限

04 混合搜索:2026 年的标准答案

混合搜索 = BM25 全文 + 向量检索 + 重排序(Reranker)
三者协同:BM25 保证精确匹配召回,向量保证语义召回,Reranker 对合并结果精排。

Reciprocal Rank Fusion (RRF)

最常用的多路召回合并算法。不需要归一化分数,直接用排名:

# RRF 公式
score(d) = Σ 1 / (k + rank_i(d))

# 示例:某文档在 BM25 排第 2,向量排第 5
# k = 60(默认值)
score = 1/(60+2) + 1/(60+5) = 0.0161 + 0.0154 = 0.0315

典型混合搜索架构

用户查询 "蓝色跑鞋" │ ├──────────────────┐ ▼ ▼ BM25 全文检索 Embedding → 向量检索 "蓝色" "跑鞋" 语义:蓝色运动鞋/跑步鞋 │ │ ▼ ▼ Top 50 Top 50 │ │ └──────┬───────────┘ ▼ RRF 合并去重 │ ▼ Cross-Encoder Reranker ← 精排(可选) │ ▼ Top 10 最终结果

各引擎的混合搜索实现

引擎混合方式Reranker开箱程度
MeilisearchsemanticRatio 参数调权内置 embedder⭐⭐⭐⭐⭐
Typesensevector_query + 全文并行需外部⭐⭐⭐
ElasticsearchBoolean + kNN 组合查询ELSER / learning_to_rank⭐⭐⭐⭐
OpenSearchhybrid query pipeline内置 ML plugin⭐⭐⭐⭐
Vespa原生多路召回 + ranking profiles内置 onnx 模型⭐⭐⭐⭐⭐
pgvectorSQL 联合查询需自建⭐⭐

05 Meilisearch — 极简主义的 Rust 之选

Meilisearch Rust 应用内搜索

一句话:Algolia 的开源替代,5 分钟上手,开箱即用的应用内搜索。

当前版本:v1.13+(2026 稳定线) | 许可证:MIT

核心特性

混合搜索代码示例

// Meilisearch 1.x — 混合搜索
const results = await client.index('products').search('蓝色跑鞋', {
  hybrid: {
    embedder: 'openai',        // 使用已配置的 OpenAI embedder
    semanticRatio: 0.7,        // 70% 向量,30% BM25
  },
  limit: 20,
  attributesToHighlight: ['name', 'description'],
})

// 配置 embedder(一次性设置)
await client.index('products').updateSettings({
  embedders: {
    openai: {
      source: 'openAi',
      model: 'text-embedding-3-small',
      apiKey: process.env.OPENAI_API_KEY,
      documentTemplate: '{{doc.name}} {{doc.description}}',
    }
  }
})

性能数据

📋 1M 产品索引基准(2026 数据)

✅ 优势

❌ 劣势

何时选 Meilisearch

06 Typesense — 开发者友好的 C++ 快马

Typesense C++ 应用内搜索

一句话:和 Meilisearch 同赛道但更注重可扩展性和集群高可用。

当前版本:v0.25+ | 许可证:GPL v3

核心特性

代码示例

# Typesense — 创建集合并启用向量搜索
curl -X POST 'http://localhost:8108/collections' \
  -H 'X-TYPESENSE-API-KEY: xyz' \
  -d '{
    "name": "products",
    "fields": [
      {"name": "name", "type": "string"},
      {"name": "description", "type": "string"},
      {"name": "price", "type": "float"},
      {"name": "embedding", "type": "float[]", "num_dim": 1536}
    ]
  }'

# 混合搜索:全文 + 向量
curl 'http://localhost:8108/multi_search' \
  -H 'X-TYPESENSE-API-KEY: xyz' \
  -d '{
    "searches": [
      {"collection": "products", "q": "蓝色跑鞋", "query_by": "name,description"},
      {"collection": "products", "q": "*", "vector_query": "embedding:([0.01,-0.02,...]),k:50"}
    ]
  }'

✅ 优势

❌ 劣势

Meilisearch vs Typesense 关键差异

维度MeilisearchTypesense
语言RustC++
许可证MIT ✅GPL v3 ⚠️
集群高可用实验性Raft 内置 ✅
混合搜索一个参数 ✅需 multi_search 手动合并
内存模型全内存mmap 磁盘索引 ✅
前端 SDK更丰富 ✅够用
Typo 容错更优秀的算法 ✅基础支持

07 Elasticsearch — 巨人仍在

Elasticsearch Java 通用搜索 日志分析

一句话:什么都行、什么都大、什么都要调。2026 年依然是最全能的搜索引擎。

当前版本:8.x | 许可证:AGPL v3 / Elastic License v2 双许可

2024-2026 关键变化

许可证回归开源

2024 年底,Elastic 将 Elasticsearch 和 Kibana 重新以 AGPL v3 开源(同时保留 Elastic License v2 二选一)。这直接回应了 OpenSearch 阵营,激活了自托管和第三方云服务的信心。

ESQL — 管道式 SQL 查询语言

-- ESQL:ES 8.11+ 的管道式查询
FROM logs-*
| WHERE @timestamp > NOW() - 1 hour AND http.status >= 500
| STATS count(*) BY service.name
| SORT count DESC
| LIMIT 10

ESQL 让 Elasticsearch 在日志/指标场景更接近 OLAP,可直接与 ClickHouse/Apache Doris 竞争。

向量搜索一等公民

8.x 后期版本:dense_vector 字段 + HNSW 索引 + BM25 + kNN 混合搜索已标准化。ELSER(Elastic 训练的稀疏嵌入模型)和学习排序(learning_to_rank)让 RAG 管线可以完全在 ES 内闭环。

代码示例:混合搜索

# Elasticsearch 8.x — 混合搜索
POST products/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": {
              "query": "蓝色跑鞋",
              "boost": 0.3
            }
          }
        }
      ]
    }
  },
  "knn": {
    "field": "embedding",
    "query_vector": [0.01, -0.02, ...],
    "k": 50,
    "num_candidates": 200,
    "boost": 0.7
  },
  "rank": {
    "rrf": {
      "window_size": 100,
      "rank_constant": 60
    }
  },
  "size": 10
}

✅ 优势

❌ 劣势

⚠️ Elasticsearch 的陷阱:很多团队用 ES 做应用内搜索,结果花 80% 精力在 ES 运维上、20% 在业务。如果你的需求只是商品搜索 + 自动补全,强烈考虑 Meilisearch/Typesense。

08 OpenSearch — AWS 生态的自然选择

OpenSearch Java 通用搜索

一句话:AWS 2021 年从 Elasticsearch 7.10 fork 出来的项目,5 年后已是独立生态。

当前版本:2.x | 许可证:Apache 2.0

关键变化

✅ 何时选 OpenSearch

❌ 何时避开

09 Vespa — 实时 AI 服务引擎

Vespa C++ AI 搜索 推荐系统

一句话:唯一在推理时实时计算 ML 模型的搜索引擎。Spotify、Wikipedia、Pinterest 在用。

许可证:Apache 2.0

Vespa 的独特定位

其他搜索引擎是"存好索引 → 查询时检索"的离线模式。Vespa 可以在查询时实时执行 ML 模型——这意味着每个用户的搜索结果可以是实时个性化排序的。

查询请求 + 用户特征 │ ▼ ┌────────────┐ │ Vespa │ │ Content │ ← 倒排索引 + 向量索引 + Tensor 字段 │ Cluster │ └─────┬──────┘ │ ▼ ┌────────────┐ │ Vespa │ ← 在 Container 层执行 ONNX 模型 │ Container │ ← 实时重排序、个性化 └─────┬──────┘ │ ▼ 个性化排序结果

代码示例:Vespa 排序表达式



  
    expression: 0.3 * bm25(name) + 0.7 * closeness(field, embedding)
  
  
    expression: onnx(model_name) { ... }  
  

✅ 优势

❌ 劣势

10 PostgreSQL + pgvector — 不加新组件的搜索方案

PostgreSQL + pgvector PostgreSQL 轻量搜索

一句话:已经用了 PostgreSQL?那你已经有了搜索引擎的 80%。加上 pgvector 就有向量搜索。

PostgreSQL 全文搜索

-- PostgreSQL 内置全文搜索
-- 创建 tsvector 索引
CREATE INDEX idx_products_search ON products 
  USING GIN (to_tsvector('simple', name || ' ' || description));

-- 全文搜索查询
SELECT name, ts_rank(search_vector, query) AS rank
FROM products, plainto_tsquery('simple', '蓝色跑鞋') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;

pgvector 向量搜索

-- 安装 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;

-- 添加向量列
ALTER TABLE products ADD COLUMN embedding vector(1536);

-- 创建 HNSW 索引
CREATE INDEX idx_products_embedding ON products 
  USING hnsw (embedding vector_cosine_ops);

-- 向量搜索
SELECT name, 1 - (embedding <=> '[0.01,-0.02,...]'::vector) AS similarity
FROM products
ORDER BY embedding <=> '[0.01,-0.02,...]'::vector
LIMIT 20;

混合搜索:SQL 联合

-- PostgreSQL 混合搜索:BM25 + 向量
WITH bm25_results AS (
  SELECT id, ts_rank(search_vector, query) AS bm25_score
  FROM products, plainto_tsquery('simple', '蓝色跑鞋') query
  WHERE search_vector @@ query
  LIMIT 100
),
vector_results AS (
  SELECT id, 1 - (embedding <=> '[0.01,...]'::vector) AS vec_score
  FROM products
  ORDER BY embedding <=> '[0.01,...]'::vector
  LIMIT 100
)
SELECT 
  COALESCE(b.id, v.id) AS id,
  COALESCE(b.bm25_score, 0) * 0.3 + COALESCE(v.vec_score, 0) * 0.7
    AS combined_score
FROM bm25_results b
FULL OUTER JOIN vector_results v ON b.id = v.id
ORDER BY combined_score DESC
LIMIT 20;

✅ 优势

❌ 劣势

pgvector 最佳场景:已有 PostgreSQL、文档量 < 500 万、需要基本搜索 + 语义检索、不想引入新组件。适合 MVP 阶段或内部工具。一旦搜索成为核心功能,再迁到 Meilisearch/Typesense。

11 全面对比表

维度MeilisearchTypesenseElasticsearchOpenSearchVespapgvector
语言RustC++JavaJavaC++C/SQL
许可证MITGPL v3AGPL/ELv2Apache 2.0Apache 2.0PostgreSQL
启动复杂度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
混合搜索内置 ⭐手动内置 ⭐内置原生 ⭐手动 SQL
Reranker内置外部ELSER/LTRML plugin内置 ONNX
集群 HA实验性Raft ✅完善 ✅完善 ✅完善 ✅PG 复制
中文字分词插件插件ik/smartcnik/smartcn内置jieba 扩展
适用规模<10M<50M1B+1B+1B+<5M
内存占用低 ⭐
日志场景⚠️
推荐系统⚠️⚠️✅ ⭐
前端 SDK优秀 ⭐良好丰富良好基础N/A
托管服务Meilisearch CloudTypesense CloudElastic CloudAWS OpenSearchVespa Cloud各 PG 云
月成本(小规模)$0-30$0-25$50-200$50-150$100+$0

12 决策框架:我该选什么?

🛒 电商产品搜索

推荐:Meilisearch 或 Typesense

需要:typo tolerance、分面过滤、前缀搜索、高亮、混合搜索

不需要:日志分析、PB 级扩展

📚 文档/知识库搜索

推荐:Meilisearch(小规模)或 pgvector(已用 PG)

需要:语义搜索、混合搜索、中文分词

不需要:实时推荐、日志

🤖 RAG / AI Agent

推荐:pgvector(MVP)→ Vespa(规模化)

需要:向量检索 + 全文混合 + 重排序

链接:Agent 研究报告

📊 日志 / 可观测性

推荐:Elasticsearch 或 OpenSearch(AWS)

备选:Quickwit(S3-native、低成本)

不需要:typo tolerance、前缀搜索

13 实战代码示例

场景:SaaS 产品搜索 API(Node.js + Meilisearch)

// products-search.js — 完整的搜索 API
import { MeiliSearch } from 'meilisearch'

const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' })
const index = client.index('products')

// 初始化索引设置
await index.updateSettings({
  searchableAttributes: ['name', 'description', 'category', 'brand'],
  filterableAttributes: ['price', 'category', 'brand', 'inStock'],
  sortableAttributes: ['price', 'createdAt', 'rating'],
  displayedAttributes: ['id', 'name', 'price', 'category', 'brand', 'image', 'rating'],
  rankingRules: [
    'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness'
  ],
  synonyms: {
    '手机': ['移动设备', 'cellphone'],
    '笔记本': ['laptop', '笔记本电脑'],
  },
  typoTolerance: {
    enabled: true,
    minWordSizeForTypos: { oneTypo: 4, twoTypos: 8 },
    disableOnAttributes: ['id'],  // ID 字段不允许模糊
  },
  embedders: {
    openai: {
      source: 'openAi',
      model: 'text-embedding-3-small',
      apiKey: process.env.OPENAI_API_KEY,
      documentTemplate: '{{doc.name}} {{doc.category}} {{doc.description}}',
    }
  }
})

// 搜索 API
export async function searchProducts(query, filters = {}) {
  const { category, minPrice, maxPrice, hybrid = true } = filters

  const searchParams = {
    q: query,
    limit: 20,
    attributesToHighlight: ['name', 'description'],
    filter: buildFilter(category, minPrice, maxPrice),
    sort: filters.sortBy ? [`${filters.sortBy}:${filters.sortOrder || 'desc'}`] : undefined,
  }

  // 混合搜索:语义 + 全文
  if (hybrid) {
    searchParams.hybrid = {
      embedder: 'openai',
      semanticRatio: 0.6,  // 60% 语义,40% 关键词
    }
  }

  const results = await index.search(query, searchParams)
  
  return {
    hits: results.hits.map(formatHit),
    total: results.estimatedTotalHits,
    processingTime: results.processingTimeMs,
  }
}

function buildFilter(category, minPrice, maxPrice) {
  const parts = []
  if (category) parts.push(`category = "${category}"`)
  if (minPrice) parts.push(`price >= ${minPrice}`)
  if (maxPrice) parts.push(`price <= ${maxPrice}`)
  return parts.length ? parts.join(' AND ') : undefined
}

function formatHit(hit) {
  return {
    ...hit,
    _highlight: hit._formatted,  // 高亮版本
  }
}

场景:RAG 混合检索(Python + pgvector)

# rag_search.py — RAG 混合检索
import asyncpg
from openai import AsyncOpenAI

openai = AsyncOpenAI()

class RAGSearch:
    def __init__(self, db_url: str):
        self.db_url = db_url
        self.pool = None

    async def init(self):
        self.pool = await asyncpg.create_pool(self.db_url)

    async def embed(self, text: str) -> list[float]:
        resp = await openai.embeddings.create(
            model="text-embedding-3-small",
            input=text,
        )
        return resp.data[0].embedding

    async def hybrid_search(self, query: str, limit: int = 10):
        """混合搜索:全文 + 向量,RRF 合并"""
        query_vec = await self.embed(query)
        vec_str = str(query_vec).replace("[", "").replace("]", "")

        async with self.pool.acquire() as conn:
            rows = await conn.fetch("""
                WITH bm25 AS (
                    SELECT id, ts_rank(
                        fts, plainto_tsquery('simple', $1)
                    ) AS score
                    FROM documents
                    WHERE fts @@ plainto_tsquery('simple', $1)
                    LIMIT 100
                ),
                vec AS (
                    SELECT id, 1 - (embedding <=> $2::vector) AS score
                    FROM documents
                    ORDER BY embedding <=> $2::vector
                    LIMIT 100
                )
                SELECT 
                    COALESCE(b.id, v.id) AS id,
                    COALESCE(1.0/(60 + ROW_NUMBER() OVER (
                        ORDER BY b.score DESC
                    )), 0) * 0.3 +
                    COALESCE(1.0/(60 + ROW_NUMBER() OVER (
                        ORDER BY v.score DESC
                    )), 0) * 0.7
                    AS rrf_score
                FROM bm25 b
                FULL OUTER JOIN vec v ON b.id = v.id
                ORDER BY rrf_score DESC
                LIMIT $3
            """, query, f"[{vec_str}]", limit)

            # 获取完整文档
            ids = [r["id"] for r in rows]
            if not ids:
                return []
            
            docs = await conn.fetch(
                "SELECT * FROM documents WHERE id = ANY($1)", ids
            )
            return docs

14 进阶主题

中文搜索的特殊挑战

中文没有空格分词,需要专用分词器:

引擎中文分词方案安装方式
Elasticsearchik_max_word / ik_smart插件安装
OpenSearchik / smartcn插件安装
Meilisearch内置 Unicode 分段 + 字典无需配置
Typesense内置 CJK 分词无需配置
PostgreSQLpg_jieba / zhparser扩展安装
Vespa内置 CJK 分词器配置 linguistics

💡 实践中,向量搜索 + 混合检索大大缓解了中文分词的压力——语义理解不需要完美分词。

搜索结果缓存策略

# Redis 缓存热门搜索结果
import redis
import json
import hashlib

r = redis.Redis()

async def cached_search(query: str, filters: dict, ttl: int = 300):
    """搜索结果缓存:热门查询 5 分钟"""
    cache_key = f"search:{hashlib.md5(f'{query}:{filters}'.encode()).hexdigest()}"
    
    # 命中缓存
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 未命中:执行搜索
    results = await search_products(query, filters)
    
    # 写入缓存
    r.setex(cache_key, ttl, json.dumps(results))
    return results

搜索分析:追踪用户行为

# 记录搜索行为 → 后续优化排序
search_events = [
    {"query": "蓝色跑鞋", "results_count": 42, "clicked_pos": 2, "timestamp": "..."},
    {"query": "蓝色跑鞋", "results_count": 42, "clicked_pos": null, "timestamp": "..."},
    # null = 没有点击任何结果 → 可能排序不好
]

# 计算点击率 (CTR) 和 平均点击位置
# 用于:1. 发现零结果查询 2. 发现低 CTR 查询 3. A/B 测试排序策略

搜索迁移路径

🔄 从零到搜索的渐进路径
  1. Week 1-2:PostgreSQL 全文搜索 + pgvector → 验证搜索是核心功能
  2. Week 3-4:迁移到 Meilisearch → 获得 typo tolerance、高亮、分面
  3. Month 2:开启 Meilisearch 混合搜索 → 语义 + 关键词
  4. Month 3+:如果数据量增长 >10M 或需要集群 HA → 考虑 Typesense 或 ES
  5. Scale:如果需要实时 ML 推理 / 推荐系统 → Vespa

替代方案速览

方案类型何时考虑
Algolia托管 SaaS不想管任何基础设施,预算充足($1/1000 搜索)
Quickwit日志搜索S3-native 日志归档,成本敏感
Loki日志聚合Grafana 生态,只存标签不存全文
Apache Doris / StarRocksOLAP + 搜索分析为主、搜索为辅
ClickHouseOLAP全文搜索能力有限,适合日志分析
Sonic超轻量只需自动补全,内存极小
纯向量 DBLanceDB/ChromaDB/Pinecone只做 RAG 向量检索,不需要全文。详见 向量数据库模块
🔗 SaaS 知识库内
🔗 Agent 研究关联
📌 Agent 研究的关键洞察:在 build-memory.html 中,Agent Zero 使用 FAISS 做 3 区域向量检索,Hermes 使用 HRR 全息编码做记忆搜索。搜索引擎的混合搜索思想(BM25 + 向量 + 重排序)同样适用于 Agent 记忆系统——这正是 SaaS 知识库与 Agent 研究的交叉点。