elasticsearch外用与内观(四)-当搜索时,elast
Previous:elasticsearch外用与内观(三)-当搜索时,elasticsearch都在做什么(上)
搜索的结果是否准确,是由很多因素决定的,在前边最后那个搜索的例子,不知道大家有没有注意到,为什么苹果手机那条记录得分高排在前边,而苹果水果那条记录得分低排在后边:

这是因为es的打分过程是通过多个因子进行计算的:

其中有一个文本长度因子的得分规则是,文本越短,得分越高,除了文本长度这个因子,还有很多其他因子,比如搜索引擎中经典的打分算法TF*IDF,就是考虑了另外两个因子TF和IDF。
TF,就是term frequency,即“词频”,就是看这个分词在这个文档里出现的频率,为了表达的更简单,我们可以认为TF就是看这个分词在文档里出现的次数:

我们的搜索关键字是“苹果 12”,然后有一个文档的内容是“苹果Apple iPhone 12
”,那么TF苹果就等于1,因为苹果出现了一次,同样TF12也等于1。
IDF,是inverse document frequency,即“逆文档频率”,简单的说就是这个分词被文档引用次数的倒数,目的是为了说明,如果一个分词出现在越多的文档里,那么认为他的价值越低,得分也就越低。比如“的”这种常用字,几乎每个文档里都有,所以他的得分最低:

我们这个示例中,假设我们能库里的每个文档都有很多苹果关键字,那么DF苹果就是无穷大,所以IDF苹果就趋近于0,再假设12这个关键字,只在7个文档里出现,那么DF12=7,IDF12就是1/7。
计算score时,把TF苹果和IDF苹果相乘,加上TF12和IDF12相乘,最后得到分数:

TF*IDF的主要思想是:如果某个词在一个文档中出现的频率TF高,并且在其他文档中出现的少,就认为这个词所在的文档比较有价值。
当然TF*IDF在某些情况下有他的局限性,比如在一个专业资料库,某些专业名词在每个文档里都有,我们的直观感觉是专业库里搜索的结果应该更精确,但事实可能被TF * IDF的规则降低分数使得搜索效果达不到预期的效果。所以es默认已经使用BM25算法打分,这里我们了解了TF * IDF的打分过程,就可以对其他打分算法有了前置的了解。
接下来我们再了解下聚合查找的过程。
在前边的示例中,聚合查找返回的结果是这样的:

我们看到标注1这里返回的关键字是buckets,桶。
这里每一种价格就是一个桶,然后是桶对应的文档数量:

除了要留意这个桶对象,还要留意另一个对象,就是文档。
因为在聚合过程中,桶和文档的加载都是要占用内存的:

所以在聚合操作时,通过什么策略来加载桶和文档,最终对性能会产生巨大的影响。
有两种聚合模式,一种是深度优先,一种是广度优先。他们采用不同的策略来加载桶和文档。
比如,我们现在要查出销量top10的手机型号,和每个型号里销量前3的手机颜色:
{
"aggs" : {
"销量前10的型号" : {
"terms" : {
"field" : "型号",
"size" : 10
},
"aggs" : {
"每个型号前3的颜色" : {
"terms" : {
"field" : "颜色",
"size" : 3
}
}
}
}
}
}
如果用es默认的深度优先聚合模式,会把系统里所有的手机型号都作为桶,全都加载一遍,
并且每个型号桶里的所有颜色都作为桶,再加载一遍:

这时整体桶的数量就成倍数或指数级增长了,而我们只需要前10条数据。
所以我们需要把聚合模式设置为广度优先:

他的策略是逐层处理,把每层的桶加载完,进行裁剪后,再继续传递到下一层。首先加载所有型号桶,第一层处理完只保留前10个桶,其他的桶删掉:

然后继续处理下一层的桶:

这样整体桶的数量就可以大幅减少。
在实际使用过程中,一定要对自己的数据有感觉,选择适当的聚合模式,能提前裁剪的,就用广度优先,否则就用默认的深度优先。
比如按月统计那种需求,因为第一层桶的数量是固定的12个月,这样每层都不能裁剪,所以就不能用广度优先,否则每层加载的文档都要传到下一层,占用巨大的内存。