技术探索与实践:一次从零到一的全文搜索方案搭建

爬虫不想爬
2025-06-25 23:08
阅读 557

开篇:为什么是这次技术实践?

开篇:为什么是这次技术实践?

在我们团队刚接手一个内容型产品的时候,有一个看似“简单”的需求摆在了我面前:在海量文章中,实现高效、准确的全文检索功能。当时的我想,这应该不难吧?Elasticsearch 不就是干这个的吗?但现实狠狠地给我上了一课。

这篇文章将带你回顾那次从0到1的技术探索过程。你会看到我是如何在一个缺乏文档和经验沉淀的项目里,一步步调研、选型、设计、开发并部署一套全文搜索系统的全过程。

也许你正在或即将面对类似的挑战,希望这篇基于真实项目背景的文章,能为你提供一些有价值的参考。


问题描述:业务场景与原始问题分析

问题描述:业务场景与原始问题分析

背景介绍

我们服务的是一个知识分享平台,用户可以在上面发布图文教程、经验总结等类型的长文内容。随着内容数量的增长(已达20w+),搜索响应速度变慢、关键词匹配不够准确等问题开始频繁出现。

原有的搜索系统采用的是MySQL的LIKE %xxx%进行模糊查找,虽然初期还能满足基本使用,但存在几个明显的问题:

  • 查询效率低,当数据量增大时延迟严重;
  • 没有分词支持,无法处理复杂语义;
  • 缺乏相关性排序机制;
  • 程序逻辑混杂,后期维护困难。

我们的目标非常明确:快速构建一个高效的全文搜索系统,同时保证高可用性、扩展性和易用性


解决方案:技术选型与架构设计

技术选型考量

作为有着5年工作经验的工程师,我深知“技术选型”远不是找个流行框架那么简单,而是要结合实际业务、现有基础设施、团队熟悉程度以及未来的可扩展性来综合权衡。

我当时考虑了以下几个主流选择:

方案 特点 适用性
MySQL 全文索引 集成方便,适合轻量级场景 功能有限,难以支撑高并发搜索
Sphinx 中文支持不错,性能好 社区活跃度下降,后续维护难度大
Solr 成熟企业级搜索方案 复杂配置多,学习成本较高
Elasticsearch 实时性强,生态丰富,分布式友好 当前主流首选

最终我们选择了 Elasticsearch(以下简称 ES) 作为全文搜索引擎。

原因在于:

  1. 项目未来会往大数据方向发展,ES 支持横向扩展;
  2. 官方对中文的支持较好(IK Analyzer 插件);
  3. 前端团队有使用过其 RESTful API 接口的经验;
  4. 可以和 Kafka、Logstash 等做日志分析集成,为未来留下空间。

📌 小插曲回忆:记得当时我和后端小伙伴还争论了好几次“要不要自己写个倒排索引”,后来想想,还是老老实实用现成轮子吧,毕竟我们不是来做学术研究的 😅。

整体架构图

大致的系统流程如下:

数据库(MySQL) -> 数据同步服务(如Canal / 自定义定时任务) 
    ↓
Elasticsearch Indexing 
    ↓
REST API 查询接口 
    ↓
前端调用展示结果

其中,我们通过定时任务每天凌晨增量更新一次数据。为了保证实时性,也接入了 Binlog 解析组件实现近实时的数据同步。


代码实践:从同步到查询的关键代码片段

下面我会分享几个核心模块的实际代码,方便你参考使用。

1. 将数据同步到 Elasticsearch 的 Java 示例代码

我们采用了 Spring Boot + Elasticsearch RestHighLevelClient 来进行交互(注意:该客户端已不再推荐,现在可以使用新的 Java Client 或直接走 HTTP 请求):

// 伪代码:批量导入数据到ES
public void syncToES(List<Article> articles) {
    BulkRequest bulkRequest = new BulkRequest();
    
    for (Article article : articles) {
        IndexRequest indexRequest = new IndexRequest("article_index");
        indexRequest.id(String.valueOf(article.getId()));
        indexRequest.source(JSON.toJSONString(article), XContentType.JSON);
        
        bulkRequest.add(indexRequest);
    }
    
    try {
        elasticsearchRestTemplate.getClient().bulk(bulkRequest, RequestOptions.DEFAULT);
    } catch (IOException | ElasticsearchException e) {
        log.error("ES 批量写入失败", e);
    }
}

2. 基于 IK 分词器的查询示例

ES 默认英文分词很强大,但对中文就不够智能了。我们选用 IK Analyzer 插件来进行中文分词处理。

创建映射时指定 analyzer:

{
  "mappings": {
    "properties": {
      "title": { "type": "text", "analyzer": "ik_max_word" },
      "content": { "type": "text", "analyzer": "ik_max_word" }
    }
  }
}

然后是一个简单的搜索请求:

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("content", keyword);
searchSourceBuilder.query(queryBuilder);

SearchRequest request = new SearchRequest("article_index");
request.source(searchSourceBuilder);

try {
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 解析结果 ...
} catch (IOException | ElasticsearchException e) {
    // 错误处理
}

踩坑经验:那些只有做过才知道的“陷阱”

1. 初次上线后的查询异常

我们在首次上线后发现部分关键词查不出结果。排查发现是默认的 standard analyzer 对中文进行了按字拆分,而我们需要的是按词进行切分。

解决方案:更换成 IK 分词器,并根据业务自定义了一些停用词和同义词库。

⚠️ 提醒:分词方式的选择严重影响召回效果,不要盲目使用默认分词器!

2. 内存不足导致频繁 Full GC

在大量数据导入过程中,曾发生 OOM 并且触发频繁 Full GC,影响了整体服务稳定性。

解决办法

  • 合理设置 _bulk 批次大小(建议每次控制在 1MB~5MB 以内)
  • 升级 ES 实例内存配置
  • 使用线程池限制并发写入数

3. 查询超时与分页深翻陷阱

在用户进行深度分页(如第100页)时,出现了明显的查询超时问题。这是因为默认的 from-size 分页机制在深层页时会导致性能陡降。

应对策略

  • 使用 search_after 替代传统分页
  • 在前端限制最大页码(如不超过50页)

效果总结:上线后的变化

系统上线后,整体性能和用户体验提升明显:

指标 上线前 上线后
首条搜索平均耗时 800ms+ 60ms~120ms
查准率 <60% >90%
系统响应波动 明显不稳定 稳定在±15ms
维护成本 高(SQL 修改频繁) 低(仅需维护 Mapping 和同步逻辑)

更重要的是,ES 的加入为我们后续做相关性排序、自动摘要生成等功能奠定了基础,提升了整个产品的智能化水平。


经验分享:几点真诚建议

技术概念图解-1

如果你正打算或正在着手类似的工程实践,我有几个真实的建议送给你:

✅ 做好业务建模

搜索的内容是什么?标题重要,还是正文更关键?是否需要对时间维度加权?

这些问题一定要和产品经理一起梳理清楚,决定了后续的字段设计、权重设置。

✅ 控制数据质量

ES 虽强大,但也无法弥补源头数据的质量缺陷。比如重复内容、乱码、缺失值等问题,在进入 ES 前都应该做好清洗。

✅ 关注运维层面

ES 的集群健康状态、索引快照备份、节点扩缩容策略都需要提前规划。否则一旦出事,修复起来会非常麻烦。

✅ 尽早引入测试环境

我们一开始没搭测试环境,直到正式上线才发现一堆问题。后来痛定思痛,专门搭了一个模拟环境用于压测和回归验证。


结语:技术探索,永远在路上

从最开始的“看起来好像不难”到最后的“还好没放弃”,整个过程不仅是一次技术的尝试,更是我作为一个阅读工程师的成长体验。

每一次遇到瓶颈、踩坑、再爬出来,都会让我对工程落地有更深的理解。

或许有一天我们会用 VectorDB 代替 Elasticsearch,也可能会接入 RAG 模块实现更复杂的语义搜索,但不管怎样,“动手做出来”永远比“只停留在想法”更有价值

希望我的这段经历对你有所帮助。如果你也在做类似的事情,欢迎留言交流,我们一起成长 💪。


如果你喜欢这种实战向的文章风格,欢迎关注我,后续将持续输出更多来自一线项目的技术实践。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝