跳到主要内容

搜索系统设计

问题

如何设计一个全文搜索系统?

答案

架构概览

数据同步方案

方案原理延迟适用场景
双写代码中同时写 DB 和 ES无延迟简单场景
MQ 异步写 DB 后发 MQ,消费者写 ES秒级推荐方案
Canal 监听监听 MySQL binlog秒级无侵入
定时任务定期全量/增量同步分钟级兜底补偿
MQ 异步同步到 ES
// 商品更新后发送 MQ
public void updateProduct(Product product) {
productMapper.update(product);
rocketMQTemplate.convertAndSend("PRODUCT_SYNC", product.getId());
}

// 消费者同步到 ES
@RocketMQMessageListener(topic = "PRODUCT_SYNC")
public void syncToES(Long productId) {
Product product = productMapper.findById(productId);
ProductDoc doc = convertToDoc(product);
elasticsearchClient.index(i -> i
.index("products")
.id(String.valueOf(productId))
.document(doc)
);
}

搜索查询

ES 多条件查询 + 高亮
public SearchResult search(String keyword, int page, int size) {
SearchResponse<ProductDoc> response = elasticsearchClient.search(s -> s
.index("products")
.query(q -> q
.bool(b -> b
.must(m -> m
.multiMatch(mm -> mm
.query(keyword)
.fields("title^3", "description") // title 权重 x3
)
)
.filter(f -> f.term(t -> t.field("status").value(1)))
)
)
.highlight(h -> h
.fields("title", hf -> hf.preTags("<em>").postTags("</em>"))
)
.from((page - 1) * size)
.size(size)
.sort(so -> so.score(sc -> sc.order(SortOrder.Desc))),
ProductDoc.class
);

return buildResult(response);
}

搜索优化

优化点方案
分词IK 分词器(中文)、同义词词典
相关性boost 权重、function_score 自定义打分
性能深分页用 search_after、提前终止
纠错suggest API、拼音分词

常见面试问题

Q1: ES 和 MySQL 数据不一致怎么办?

答案

  • MQ 异步同步保证最终一致性
  • 定时任务全量校验补偿
  • Canal 监听 binlog 无侵入同步

Q2: ES 深分页怎么处理?

答案

  • from + size 超过 10000 性能急剧下降
  • search_after:基于上一页最后一条的排序值翻页
  • scroll(已废弃,改用 PIT + search_after)

Q3: 如何提升搜索相关性?

答案

  • 字段权重 boost(标题 > 描述)
  • function_score 结合业务指标(销量、评分)
  • 同义词词典、停用词过滤
  • 用户行为数据优化排序(点击率、转化率)

相关链接