探究ES suggest search
问题背景
项目中存在的问题
1.项目中主要使用ES进行数据的模糊搜索以及建议搜索,但在查询数据量较大的索引时会出现偶现的慢查询。
2.在进行建议搜索时,用户如果输入错误单词无法对其进行有效纠正并进行纠正推荐。
3.多个模块复用同一个mapping文件,在某一模块搜索时,建议搜索常常会给出并不属于本模块的数据。
现有建议查询方案
由于用户在输入的同时要返回建议结果,而用户输入的数据往往不会形成一个完整的单词,所以使用match来进行搜索是无法搜索到结果的。
在项目中我们主要通过ES来做建议查询和模糊搜索。搜索的语句还是比较直接的,在建议搜索和模糊搜索都是直接使用query加通配符直接来进行查询的,这样做的目的是尽可能匹配相似的搜索结果。
ES查询代码如下:
假设用户输入为test_pro_00,其merchant_id为4
{
"query": {
"bool": {
"must": [
{
"term": {
"merchant_id": 4
}
}
],
"should": [
{
"match": {
"product_name": {
"query": "test_pro_00"
}
}
},
{
"match": {
"product_name.keyword": {
"boost": 2,
"query": "test_pro_00"
}
}
},
{
"wildcard": {
"product_name": {
"wildcard": "*test_pro_00*"
}
}
}
]
}
},
"size": 10
}
这种思路在建议搜索时由于请求频繁对查询结果的返回速度要求很高,很容易就会遇到性能的问题。对于模糊搜索,在需要两个字段进行模糊匹配时也会查询比较慢。且无法根据不同模块进行查询分类。
方案选型
建议搜索,有两种方案来解决:
1.可以使用 Fuzzy query来进行建议搜索。其核心思想是使用编辑距离来模糊匹配用户的输入。
2.使用suggest search解决方案。其核心思想是构建在内存中构建FST树,从而提升查询速度。
Fuzzy query劣势
1.Fuzzy query的查询是构建在term之上的,而建议搜索所遇到的更多场景则是用户并没有进行完整输入。比如用户在建议搜索时,希望查询到的是Adam,用户在搜索框只输入了A,而Fuzzy query需要根据这一个字母来进行模糊匹配,其效率是很低的。
2.Fuzzy query会进行编辑距离的计算,其性能是很低的,并不能满足建议搜索这一场景中,频繁请求,高速返回的这一需求。
所以,我们认为suggest search更适合建议搜索这一场景。
suggest search原理
针对建议搜索这一问题,Elastic提出了suggest search解决方案。
suggest search查询的原理主要基于Levenshtein Distance算法和FST算法,这里做一个简单的讲解。
Levenshtein Distance算法
Levenshtein Distance算法常用于计算两个字符串间的差异,在suggest search中主要用于对用户输入的纠正。下面讲解一个LD算法的流程来讲解其原理。
算法过程
- str1或str2的长度为0返回另一个字符串的长度。 if(str1.length0) return str2.length; if(str2.length0) return str1.length;
- 初始化(n+1)*(m+1)的矩阵d,并让第一行和列的值从0开始增长。
- 扫描两字符串(n*m级的),如果:str1[i] == str2[j],用temp记录为0,否则temp记为1。然后在矩阵d[i,j]赋于d[i-1,j]+1 、d[i,j-1]+1、d[i-1,j-1]+temp三者的最小值。
- 扫描完后,返回矩阵的最后一个值dnm即是它们的距离。
计算相似度公式:1-它们的距离/两个字符串长度的最大值。
为了直观表现,我将两个字符串分别写到行和列中,实际计算中不需要。我们用字符串“ivan1”和“ivan2”举例来看看矩阵中值的状况:
1、第一行和第一列的值从0开始增长
i | v | a | n | 1 | ||
---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | |
i | 1 | |||||
v | 2 | |||||
a | 3 | |||||
n | 4 | |||||
2 | 5 |