ES搜索提示优化

618活动当天,公司的搜索提示服务请求突增,瞬间导致ES集群CPU打满。

问题

查看了一下搜索提示的代码实现,发现使用了Prefix前缀term查询,符合业务需求。

“搜索提示”功能就是在进行前缀匹配,比如有一条记录是:”耐克珍藏球鞋”,那么只有搜索下面这些搜索词才能匹配:

  • 耐克
  • 耐克珍
  • 耐克珍藏
  • 耐克珍藏球
  • 耐克珍藏球鞋

ES实现这个功能,在索引时需要按keyword索引(也就是不要分词),在查询时按照term Prefix做查询。

当执行term Prefix查询时,会拿着待搜索的term,在倒排索引中找到所有以搜索词term为前缀的倒排索引term,这是一个索引遍历+字符串比对的过程。

当请求量上升时,这种遍历匹配term前缀的大量字符串比较计算的操作打高了CPU,所以ES无法继续响应后续请求。

优化

可以想到,如果我们可以把”耐克珍藏球鞋”按前缀直接拆成上述的多个term,然后都建立倒排索引,那么搜索的时候就可以直接定位到一个term,无需再遍历倒排索引进行字符串比较。

ES内置了一个filter过滤器edge_ngram,它可以将一个term按照前缀组合出多个前缀term,比如对于term:”耐克珍藏球鞋”,经过edge_ngram后会进一步拆成下面这些term:

  • 耐克
  • 耐克珍
  • 耐克珍藏
  • 耐克珍藏球
  • 耐克珍藏球鞋

全部建立倒排索引,指向当前的文档。

当我们搜索”耐克珍藏球”时候,可以直接找到倒排中的”耐克珍藏球”,直接返回对应的文档。

实例

配置分词器

配置一个过滤器,使用内置的edge_ngram做term的前缀拆分,建议高效的倒排索引。

配置一个分析器,使用上述过滤器,tokenizer使用内置的keyword,也就是把字段整体当做一个term(经过过滤器后会变成多个前缀term)。

测试分词

“耐克珍藏球鞋”被分词后,有6个前缀term被倒排索引:

可见,当我们搜索一个属于上述前缀之一的term时,ES可以高效的完成检索。

我们插入一个文档用于测试:

搜索

索引时使用edge_ngram虽然导致倒排索引变大,但是对查询优化效果显著,是空间与时间的权衡。

现在可以直接使用term过滤,精准的在倒排中找到对应的前缀term。

搜索词”耐克珍”可以高效的被检索:

返回值:

参考

后记:确认是ES5.X使用的lucene在prefix检索时存在BUG被人攻击,相关ISSUE:https://github.com/elastic/elasticsearch/issues/24553。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~