一、为什么你的Elasticsearch搜索结果总是不准?

相信很多开发者都遇到过这样的困扰:明明数据已经存进去了,查询语法也没问题,但返回的结果就是不符合预期。这就像你去图书馆查资料,明明书就在架子上,管理员却总是给你拿错书一样让人抓狂。

造成这种情况最常见的原因是分词器配置不当。Elasticsearch默认使用standard分词器,它会把"iPhone 13 Pro"拆分成["iPhone", "13", "Pro"]三个词条。如果你搜索"13 Pro",确实能匹配到,但如果搜索"Pro 13",相关性评分就会很低。

// Java示例:演示标准分词器效果
Analyzer analyzer = new StandardAnalyzer();
TokenStream tokenStream = analyzer.tokenStream("field", "iPhone 13 Pro");
CharTermAttribute termAttr = tokenStream.addAttribute(CharTermAttribute.class);

tokenStream.reset();
while (tokenStream.incrementToken()) {
    System.out.println(termAttr.toString()); 
}
// 输出:
// iPhone
// 13
// Pro

二、分词器:搜索准确性的第一道防线

想要解决这个问题,我们需要根据业务场景选择合适的分词器。中文场景推荐使用IK分词器,它专门针对中文优化:

// Java示例:IK分词器使用
Analyzer analyzer = new IKAnalyzer();
TokenStream tokenStream = analyzer.tokenStream("field", "华为Mate50手机");

tokenStream.reset();
while (tokenStream.incrementToken()) {
    System.out.println(termAttr.toString());
}
// 输出:
// 华为
// mate
// 50
// 手机

对于商品搜索这类场景,还可以自定义词典。比如把"iPhone 13 Pro"设为不可分割的整体:

// IK词典配置示例
iPhone 13 Pro
华为 Mate50
小米 12S Ultra

三、相关性优化:让结果更符合预期

即使分词正确,搜索结果排序也可能不如人意。Elasticsearch默认使用TF-IDF算法计算相关性,但我们可以通过bool查询组合多种条件:

// Java示例:复合查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
    .must(QueryBuilders.matchQuery("title", "手机")) // 必须包含
    .should(QueryBuilders.termQuery("brand", "华为").boost(3)) // 品牌加权
    .should(QueryBuilders.rangeQuery("price").gte(2000).lte(5000).boost(2)) // 价格区间
    .minimumShouldMatch(1); // 至少满足一个should条件

SearchResponse response = client.prepareSearch("products")
    .setQuery(boolQuery)
    .get();

对于电商场景,还可以考虑:

  1. 近期销量加权
  2. 用户点击历史
  3. 库存状态
  4. 促销标识

四、高级技巧:语义理解与纠错

有时用户会输错关键词,比如把"耐克"写成"奈克"。这时可以使用模糊查询:

// Java示例:模糊查询
QueryBuilder query = QueryBuilders.matchQuery("brand", "奈克")
    .fuzziness(Fuzziness.AUTO) // 自动计算可编辑距离
    .prefixLength(1); // 前1个字符必须匹配

// 更高级的做法是使用同义词
PUT /products
{
  "settings": {
    "analysis": {
      "filter": {
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "耐克, 奈克, nike",
            "阿迪达斯, adidas"
          ]
        }
      }
    }
  }
}

五、实战建议与避坑指南

根据多年调优经验,我总结出以下黄金法则:

  1. 索引设计阶段:

    • 明确字段是否需要分词
    • 设置正确的mapping类型
    • 为高基数字段禁用doc_values
  2. 查询优化技巧:

    • 避免使用wildcard查询
    • 合理使用filter上下文
    • 控制返回字段数量
  3. 性能监控:

    // Java示例:获取搜索耗时
    SearchResponse response = client.prepareSearch("products").get();
    long took = response.getTook().getMillis(); // 获取查询耗时
    
  4. 缓存策略:

    • 合理设置request cache
    • 对静态数据使用page cache
    • 考虑使用Redis作为查询缓存

六、不同场景下的优化策略

  1. 电商搜索:

    • 商品标题需要细粒度分词
    • 品牌和型号需要精确匹配
    • 价格和销量需要范围查询
  2. 内容搜索:

    • 实现同义词扩展
    • 支持拼音搜索
    • 按相关性和时间排序
  3. 日志分析:

    • 使用constant_keyword代替keyword
    • 对IP地址使用特殊mapping
    • 禁用不需要的评分计算

七、终极解决方案:混合搜索架构

对于要求极高的场景,可以考虑:

  1. 第一层:Elasticsearch快速召回
  2. 第二层:向量引擎语义匹配
  3. 第三层:业务规则过滤
// Java示例:混合查询
QueryBuilder elasticQuery = ... // ES查询
List<Float> vector = ... // 文本向量
int radius = 200; // 搜索半径

// 使用Elasticsearch的向量搜索插件
ScriptScoreQueryBuilder scriptQuery = QueryBuilders.scriptScoreQuery(elasticQuery)
    .script(new Script("cosineSimilarity(params.query_vector, 'title_vector') + 1.0",
        ScriptType.INLINE, "expression", 
        Map.of("query_vector", vector)));

SearchResponse response = client.prepareSearch("products")
    .setQuery(scriptQuery)
    .setSize(50)
    .get();

通过以上方法,相信你的搜索体验会有质的提升。记住,搜索优化是个持续的过程,需要根据实际效果不断调整参数和策略。