前言
丑話說(shuō)在前頭,說(shuō)實(shí)在這篇筆記寫(xiě)的不是很好,確實(shí)很多沒(méi)有實(shí)操。
閱讀導(dǎo)航
系列上一篇文章:《【ES專題】ElasticSearch 高級(jí)查詢語(yǔ)法Query DSL實(shí)戰(zhàn)》
系列下一篇文章:《【ES專題】ElasticSearch集群架構(gòu)剖析》
前置知識(shí)
- 理解ES的核心概念,最最重要的是【索引】和【文檔】
- 理解基本的Query DSL語(yǔ)法
特別提醒
文中出現(xiàn)的term
關(guān)鍵詞即【詞項(xiàng)】;document
即【文檔】。
有時(shí)候筆記做懵了可能會(huì)把他們串著用
筆記正文
一、分詞器詳解
稍微了解了一點(diǎn)ES之后,相信大家都會(huì)知道【分詞】這個(gè)動(dòng)作對(duì)ES的影響有多大,可以說(shuō)是核心中的核心了。沒(méi)有分詞,沒(méi)有倒排索引,也許就沒(méi)有ES了。那么,什么是【分詞】,什么時(shí)候【分詞】,【分詞】的過(guò)程是怎樣的呢?
下面,我們來(lái)稍微來(lái)了解下ES中【分詞】的相關(guān)組件。
1.1 基本概念
【分詞】這一行為是由【分詞器】完成的。
分詞器官方稱之為文本分析器,顧名思義,是對(duì)文本進(jìn)行分析處理的一種手段,基本處理邏輯為:按照預(yù)先制定的分詞規(guī)則,把原始文檔分割成若干更小粒度的詞項(xiàng),粒度大小取決于分詞器規(guī)則。
這些所謂的規(guī)則在哪里定義呢?看看你的es安裝目錄下的plugin/config就知道了。
1.2 分詞發(fā)生的時(shí)期
分詞器的處理過(guò)程發(fā)生在Index Time
和Search Time
兩個(gè)時(shí)期。
-
Index Time
:索引時(shí)刻。文檔寫(xiě)入并創(chuàng)建倒排索引時(shí)期,其分詞邏輯取決于映射參數(shù)analyzer
還記得【索引】的三個(gè)語(yǔ)義吧?這邊的【索引】即【寫(xiě)入文檔】
-
Search Time
:查詢時(shí)刻。搜索發(fā)生時(shí)期,對(duì)搜索的詞語(yǔ)做分詞
1.3 分詞器的組成
- 切詞器(Tokenizer):用于定義切詞(分詞)邏輯
- 詞項(xiàng)過(guò)濾器(Token Filter):用于對(duì)分詞之后的單個(gè)詞項(xiàng)的處理邏輯
- 字符過(guò)濾器(Character Filter):用于對(duì)分詞之前處理單個(gè)字符
注意:分詞器不會(huì)對(duì)源數(shù)據(jù)造成任何影響,分詞僅僅是對(duì)倒排索引或者搜索詞的行為
1.3.1 切詞器:Tokenizer
這個(gè)概念從名字上來(lái)理解就很清晰了。
tokenizer 是分詞器的核心組成部分之一,其主要作用是分詞,或稱之為切詞,主要用來(lái)對(duì)原始文本進(jìn)行細(xì)粒度拆分。拆分之后的每一個(gè)部分稱之為一個(gè) Term(詞項(xiàng))??梢园亚性~器理解為預(yù)定義的切詞規(guī)則。官方內(nèi)置了很多種切詞器,默認(rèn)的切詞器位 standard。
使用關(guān)鍵詞是:
tokenizer
1.3.2 詞項(xiàng)過(guò)濾器:Token Filter
詞項(xiàng)過(guò)濾器用來(lái)處理切詞完成之后的詞項(xiàng)。例如把大小寫(xiě)轉(zhuǎn)換,刪除停用詞或同義詞處理等。官方同樣預(yù)置了很多詞項(xiàng)過(guò)濾器,基本可以滿足日常開(kāi)發(fā)的需要。當(dāng)然也是支持第三方也自行開(kāi)發(fā)的。
使用關(guān)鍵詞filter
示例輸入
# 字母小寫(xiě)過(guò)濾器
GET _analyze
{
"filter" : ["lowercase"],
"text" : "WWW ELASTIC ORG CN"
}
# 字母大寫(xiě)過(guò)濾器
GET _analyze
{
"tokenizer" : "standard",
"filter" : ["uppercase"],
"text" : ["www.elastic.org.cn","www elastic org cn"]
}
示例輸出
停用詞:概念
在ES官方中,還有一個(gè)很經(jīng)典的【詞項(xiàng)過(guò)濾器】,他就是所謂的:停用詞過(guò)濾器,同義詞過(guò)濾器。
在分詞完成之后,應(yīng)該過(guò)濾掉的詞項(xiàng),即停用詞(停用詞可以自定義)
英文停用詞(english):
a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with
中文停用詞:的,啊,嗯,咦
等語(yǔ)氣詞
停用詞:示例輸入
# 使用停用詞過(guò)濾文本
GET _analyze
{
"tokenizer": "standard",
"filter": ["stop"],
"text": ["What are you doing"]
}
當(dāng)然,還可以自定義停用詞:
### 自定義 filter
DELETE test_token_filter_stop
PUT test_token_filter_stop
{
"settings": {
"analysis": {
"filter": {
"my_filter": {
"type": "stop",
"stopwords": [
"www"
],
"ignore_case": true
}
}
}
}
}
GET test_token_filter_stop/_analyze
{
"tokenizer": "standard",
"filter": ["my_filter"],
"text": ["What www WWW are you doing"]
}
同義詞:概念
同義詞定義規(guī)則:
-
a, b, c => d
:這種方式,a、b、c 會(huì)被 d 代替 -
a, b, c, d
:這種方式下,a、b、c、d 是等價(jià)的
同義詞:示例輸入
# 定義good, nice的同義詞excellent
PUT test_token_filter_synonym
{
"settings": {
"analysis": {
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": [ "good, nice => excellent" ] //good, nice, excellent
}
}
}
}
}
GET test_token_filter_synonym/_analyze
{
"tokenizer": "standard",
"filter": ["my_synonym"],
"text": ["good"]
}
1.3.3 字符過(guò)濾器:Character Filter
分詞之前的預(yù)處理,過(guò)濾無(wú)用字符
使用關(guān)鍵詞char_filter
使用語(yǔ)法
PUT <index_name>
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "<char_filter_type>"
}
}
}
}
}
type:使用的字符過(guò)濾器類型名稱,可配置以下值
- html_strip
- mapping
- pattern_replace
示例一:HTML 標(biāo)簽過(guò)濾器
HTML標(biāo)簽過(guò)濾器(HTML Strip Character Filter)會(huì)去除 HTML 標(biāo)簽和轉(zhuǎn)義 HTML 元素,如 、&
PUT test_html_strip_filter
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "html_strip", // html_strip 代表使用 HTML 標(biāo)簽過(guò)濾器
"escaped_tags": [ // 當(dāng)前僅保留 a 標(biāo)簽
"a"
]
}
}
}
}
}
GET test_html_strip_filter/_analyze
{
"tokenizer": "standard",
"char_filter": ["my_char_filter"],
"text": ["<p>I'm so <a>happy</a>!</p>"]
}
注意:參數(shù)escaped_tags
指示需要保留的 html 標(biāo)簽
示例二:字符映射過(guò)濾器
字符映射過(guò)濾器(Mapping Character Filter),通過(guò)定義映射替換為規(guī)則,把特定字符替換為指定字符。比如將某些關(guān)鍵詞替換為*
PUT test_html_strip_filter
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "mapping", // mapping 代表使用字符映射過(guò)濾器
"mappings": [ // 數(shù)組中規(guī)定的字符會(huì)被等價(jià)替換為 => 指定的字符
"滾 => *",
"垃 => *",
"圾 => *"
]
}
}
}
}
}
GET test_html_strip_filter/_analyze
{
//"tokenizer": "standard",
"char_filter": ["my_char_filter"],
"text": "你就是個(gè)垃圾!滾"
}
示例三:正則替換過(guò)濾器
正則替換過(guò)濾器:Pattern Replace Character Filter。跟前面的字符映射沒(méi)太大區(qū)別,只不過(guò)這里使用了正則表達(dá)式來(lái)匹配值
PUT text_pattern_replace_filter
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "pattern_replace", // pattern_replace 代表使用正則替換過(guò)濾器
"pattern": """(\d{3})\d{4}(\d{4})""", // 正則表達(dá)式
"replacement": "$1****$2"
}
}
}
}
}
GET text_pattern_replace_filter/_analyze
{
"char_filter": ["my_char_filter"],
"text": "您的手機(jī)號(hào)是18868686688"
}
1.4 倒排索引的數(shù)據(jù)結(jié)構(gòu)
關(guān)于倒排索引的數(shù)據(jù)結(jié)構(gòu)其實(shí)在一開(kāi)始介紹的時(shí)候已經(jīng)講過(guò)了,只不過(guò)是以表格的形式。為了讓大家有個(gè)更清晰的認(rèn)知,這邊再來(lái)一張網(wǎng)上截圖給大伙看看。
如上圖所示,我們?cè)?code>goods產(chǎn)品表中存儲(chǔ)了很多商品數(shù)據(jù)。ES會(huì)在這些數(shù)據(jù)寫(xiě)入的時(shí)候?yàn)樗麄兘ⅰ居疫叀克镜牡古潘饕?br> 當(dāng)用戶在電商網(wǎng)站搜索【小米旗艦手機(jī)】的時(shí)候,會(huì)對(duì)搜索詞做【分詞】處理,接著在倒排索引表中匹配查詢。
為了進(jìn)一步提升索引的效率,ES 在 term 的基礎(chǔ)上利用 term 的前綴或者后綴構(gòu)建了 term index, 用于對(duì) term 本身進(jìn)行索引(索引的索引),ES 實(shí)際的索引結(jié)構(gòu)如下圖所示:
這樣當(dāng)我們?nèi)ニ阉髂硞€(gè)關(guān)鍵詞時(shí),ES 首先根據(jù)它的【前綴或者后綴】迅速縮小關(guān)鍵詞的在 term dictionary 中的范圍,大大減少了磁盤IO的次數(shù)。
詞項(xiàng)-字典的類型有多種,主要如下:
- 單詞詞典(Term Dictionary) :記錄所有文檔的單詞,記錄單詞到倒排列表的關(guān)聯(lián)關(guān)系
- 常用字典數(shù)據(jù)結(jié)構(gòu):https://www.cnblogs.com/LBSer/p/4119841.html
- 倒排列表(Posting List):記錄了單詞對(duì)應(yīng)的文檔結(jié)合,由倒排索引項(xiàng)組成。倒排索引項(xiàng)如下:
- 文檔ID
- 詞頻TF–該單詞在文檔中出現(xiàn)的次數(shù),用于相關(guān)性評(píng)分
- 位置(Position)-單詞在文檔中分詞的位置。用于短語(yǔ)搜索(match phrase query)
- 偏移(Offset)-記錄單詞的開(kāi)始結(jié)束位置,實(shí)現(xiàn)高亮顯示
用偽代碼表示如下:
class 倒排索引表PostingList {
String 文檔id;
Integer 詞頻;
Integer 位置position;
Integer 起始偏移量startOffset;
Integer 結(jié)束偏移量endOffset;
}
Elasticsearch 的JSON文檔中的每個(gè)字段,都有自己的倒排索引。當(dāng)然也可以指定對(duì)某些字段不做索引,這樣做有如下優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):節(jié)省存儲(chǔ)空間
- 缺點(diǎn):字段無(wú)法被搜索
*二、相關(guān)性解釋
我們前面學(xué)習(xí)【全文檢索】的時(shí)候說(shuō)過(guò):全文檢索查詢旨在基于【相關(guān)性】搜索和匹配文本數(shù)據(jù)的。由此可見(jiàn),這個(gè)相關(guān)性對(duì)我們ES有多重要。
我們?cè)谑褂冒俣炔樵兊臅r(shí)候,我想我們通常關(guān)心的正是【搜索結(jié)果的相關(guān)性】。相關(guān)性通常關(guān)注的內(nèi)容如下:
- 是否可以找到所有相關(guān)的內(nèi)容
- 有多少不相關(guān)的內(nèi)容被返回了
- 文檔的打分是否合理
- 結(jié)合業(yè)務(wù)需求,平衡結(jié)果排名
那么什么是【相關(guān)性】呢?
2.1 基本概念
搜索的相關(guān)性算分(打分),描述了一個(gè)文檔和查詢語(yǔ)句匹配的程度。ES 會(huì)對(duì)每個(gè)匹配查詢條件的結(jié)果(文檔)進(jìn)行打分_score
。打分的本質(zhì)是排序,需要把最符合用戶需求的文檔排在前面。
注意,打分的目標(biāo)是【文檔】,不是【索引】;但是【打分】的最小粒度是【文檔中的字段】。即:每次打分都是先對(duì)【文檔中的字段】打分,然后累加起來(lái),才是【文檔】的打分。
比如我們有如下倒排索引表:
顯而易見(jiàn),這時(shí)候我們?nèi)ゲ樵儭綣AVA多線程設(shè)計(jì)模式】的時(shí)候,文檔id
為2,3
的文檔的算分更高。
2.2 相關(guān)性算法
ES 5之前,默認(rèn)的相關(guān)性算分采用TF-IDF
,現(xiàn)在采用BM 25
。但由于BM25
是對(duì)TF-IDF
算法的改進(jìn),所以無(wú)論如何還是要先介紹一下TF-IDF
。
2.2.1 TF-IDF
TF-IDF,全稱:Term frequency–inverse document frequency,詞頻-逆文檔頻率。是一種用于信息檢索與數(shù)據(jù)挖掘的常用加權(quán)技術(shù),被認(rèn)為是信息檢索領(lǐng)域最重要的發(fā)明,除了在信息檢索,在文獻(xiàn)分類和其他相關(guān)領(lǐng)域有著非常廣泛的應(yīng)用。
對(duì)于TF-IDF
一個(gè)比較恰當(dāng)?shù)慕忉屓缦拢?/p>
- 某個(gè)詞項(xiàng)在某個(gè)【文檔】中出現(xiàn)的頻率越高,那么該字詞對(duì)當(dāng)前文檔而言,就越重要,它可能會(huì)是文章的關(guān)鍵詞
-
若詞項(xiàng)在整個(gè)【索引】中出現(xiàn)的頻率越高,那么字詞的重要性就越低,如
我們
這個(gè)詞項(xiàng)在文章article
索引中
Luccen中TF-IDF評(píng)分公式TF-IDF
即是兩者相乘:【詞頻】*【逆文檔頻率】,具體公式如下:
看了上面的公式是不是一臉懵逼?哈,其實(shí)對(duì)于沒(méi)有中文解釋的函數(shù)我們不需要關(guān)心了,但是可以適當(dāng)猜一下。另外,不管上面的公式怎樣,但是我們可以得出一個(gè)結(jié)論:score=A*B*C*D
,那么A/B/C/D
越大,score
越大。即:score
與A/B/C/D
正相關(guān)增長(zhǎng)。
公式解讀(注意函數(shù)的入?yún)ⅲ?/strong>
-
q
:文中出現(xiàn)的q
即query
的首字母,表示搜索的文本 -
t
:文中出現(xiàn)的t
即term
詞項(xiàng)的首字母 -
d
:文中出現(xiàn)的d
即document
文檔的首字母 -
coord(q, d)
:協(xié)調(diào)因子。其值取決于查詢中【詞項(xiàng)】的數(shù)量和文檔中匹配的【詞項(xiàng)】的數(shù)量 -
queryNorms(q, d)
:進(jìn)行分?jǐn)?shù)矯正,使最大最小值處于一個(gè)較為合理的區(qū)間,讓其差距不是很大
下面才是核心函數(shù)
-
tf(t in d)
:tf
即TF
函數(shù),它是一個(gè)函數(shù),計(jì)算詞項(xiàng)在某文檔中的詞頻。它認(rèn)為:【檢索詞在文檔中出現(xiàn)的頻率越高,相關(guān)性也越高】。計(jì)算公式如下:
詞頻(TF) = 詞項(xiàng)在文檔中出現(xiàn)的次數(shù) / 文檔的總詞數(shù)
-
idf(t)^2
:idf
即IDF
,它是一個(gè)函數(shù),計(jì)算詞項(xiàng)在整個(gè)索引中逆向文本頻率。它認(rèn)為:【檢索詞在索引中出現(xiàn)的頻率越高,相關(guān)性越低】。計(jì)算公式如下:
逆向文本頻率(IDF)= log (索引中文檔數(shù)量 / (包含該詞的文檔數(shù)+1))
-
boost(t)
:ES提供給我們的縮放函數(shù),相當(dāng)于是提供了一個(gè)能讓我們干預(yù)評(píng)分結(jié)果的窗口,它是作用在【文檔中的字段】上的。后面【2.4 Boosting Query】會(huì)介紹 -
norm(t, d)
:字段長(zhǎng)度歸一值( field-length norm),很拗口。它認(rèn)為【檢索詞出現(xiàn)在長(zhǎng)度較短的字段中時(shí),比出現(xiàn)在長(zhǎng)度較長(zhǎng)的字段中時(shí)的相關(guān)性要更高】
詞項(xiàng)【Java】如果出現(xiàn)在長(zhǎng)度為10的字段title上,比出現(xiàn)在長(zhǎng)度為100的字段content上,相關(guān)性要高
公式(注意,是字段,不是文檔,也不是索引):字段長(zhǎng)度歸一值 = 1 / (所在字段總詞數(shù)的平方)
還有一個(gè)很重要的符號(hào)以及規(guī)則
還有一個(gè)很重要的符號(hào)以及規(guī)則
還有一個(gè)很重要的符號(hào)以及規(guī)則
公式中有個(gè)符號(hào)大家可能比較陌生,它是累加的意思。
如何理解這個(gè)累加?那就是:
- 一個(gè)搜索詞匹配文檔的分?jǐn)?shù)
=
每個(gè)【詞項(xiàng)】在文檔的得分總和 (累加) - 【詞項(xiàng)】在文檔中得分
=
【詞項(xiàng)】在文檔每個(gè)【字段】的得分總和
實(shí)在搞不懂結(jié)合【2.4 Boosting Query】的示例分析理解下
以上三個(gè)因素——詞頻(term frequency)、逆向文本頻率(inverse document frequency)和字段長(zhǎng)度歸一值(field-length norm),就是在索引時(shí)打分的重要組成部分!當(dāng)然這個(gè)三個(gè)因素會(huì)在索引文檔的時(shí)候一并記錄進(jìn)去。
2.2.2 BM25
BM25
就是對(duì) TF-IDF
算法的改進(jìn),對(duì)于 TF-IDF
算法,TF(t)
部分的值越大,整個(gè)公式返回的值就會(huì)越大。BM25
就針對(duì)這點(diǎn)進(jìn)行來(lái)優(yōu)化,隨著 TF(t)
的逐步加大,該算法的返回值會(huì)趨于一個(gè)數(shù)值。這么說(shuō)可能有點(diǎn)抽象,來(lái)個(gè)圖看看就知道了
看圖說(shuō)話,褐色的為TF-IDF
的變化增長(zhǎng)曲線;藍(lán)色的為BM25
的增長(zhǎng)曲線。
BM 25的公式
這個(gè)我就不過(guò)多解讀了,沒(méi)必要,你知道BM25
改進(jìn)的點(diǎn)是什么就好了。理解了TF-IDF
基本就能搞懂ES的【打分】原理了。另外也要記住以下幾點(diǎn):
- 優(yōu)化后的公式其實(shí)可以看成是
score = tf * idf * boost
了。在BM25
算法中削弱了norms
函數(shù)的作用,默認(rèn)值為0.75 - 這個(gè)
BM25
才是目前使用的打分公式 - 上面的
k
叫做飽和度,默認(rèn)1.2
- 上面的
N
為【索引總文檔數(shù)】(曾出現(xiàn)在IDF
中) - 在
BM25
算法中,boost
函數(shù)公式為基數(shù) * 縮放因數(shù)
,基數(shù)默認(rèn)值為2.2
,縮放因子
默認(rèn)為1
,后面我們修改的值實(shí)際上為后面的縮放因子
公式簡(jiǎn)記:score = 字段累加(詞頻 * 逆文檔頻率 * boost)
公式簡(jiǎn)記:score = 字段累加(詞頻 * 逆文檔頻率 * boost)
公式簡(jiǎn)記:score = 字段累加(詞頻 * 逆文檔頻率 * boost)
*2.3 通過(guò)Explain API查看TF-IDF
不同于Mysql的explain
用來(lái)查看查詢語(yǔ)句的性能分析,ES提供的explain
主要是用來(lái)查詢ES更關(guān)注的相關(guān)性,即TF-IDF
的三項(xiàng)因素。
關(guān)鍵詞:explain
示例數(shù)據(jù):
1)首先批量寫(xiě)入一些測(cè)試數(shù)據(jù),注意它們的內(nèi)容content
PUT /test_score/_bulk
{"index":{"_id":1}}
{"content":"we use Elasticsearch to power the search"}
{"index":{"_id":2}}
{"content":"we like elasticsearch"}
{"index":{"_id":3}}
{"content":"Thre scoring of documents is caculated by the scoring formula"}
{"index":{"_id":4}}
{"content":"you know,for search"}
示例輸入:
2)再來(lái)做一下簡(jiǎn)單的查詢:在content
上全文檢索elasticsearch
這個(gè)值
GET /test_score/_search
{
"explain": true,
"query": {
"match": {
"content": "elasticsearch"
}
}
}
我們稍微分析一下查詢的過(guò)程:
-
elasticsearch
無(wú)法進(jìn)一步分詞,所以搜索的詞項(xiàng)是elasticsearch
- 在文檔的
content
字段搜索elasticsearch
,只有id=1
和id=2
的文檔會(huì)被搜索到 - 再然后【打分】
-
explain
給我們分析整個(gè)打分過(guò)程
示例輸出:
#! Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security.
{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.8713851,
"hits" : [
{
"_shard" : "[test_score][0]",
"_node" : "MTTjaVXrS0qu3V7iOYC3kA",
"_index" : "test_score",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.8713851,
"_source" : {
"content" : "we like elasticsearch"
},
"_explanation" : {
"value" : 0.8713851,
"description" : "weight(content:elasticsearch in 1) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 0.8713851,
"description" : "score(freq=1.0), computed as boost * idf * tf from:",
"details" : [
{
"value" : 2.2,
"description" : "boost",
"details" : [ ]
},
{
"value" : 0.6931472,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 2,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 4,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
},
{
"value" : 0.5714286,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 1.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 3.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 6.0,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
]
}
]
}
},
{
"_shard" : "[test_score][0]",
"_node" : "MTTjaVXrS0qu3V7iOYC3kA",
"_index" : "test_score",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.6489038,
"_source" : {
"content" : "we use Elasticsearch to power the search"
},
"_explanation" : {
"value" : 0.6489038,
"description" : "weight(content:elasticsearch in 0) [PerFieldSimilarity], result of:",
"details" : [
{
"value" : 0.6489038,
"description" : "score(freq=1.0), computed as boost * idf * tf from:",
"details" : [
{
"value" : 2.2,
"description" : "boost",
"details" : [ ]
},
{
"value" : 0.6931472,
"description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
"details" : [
{
"value" : 2,
"description" : "n, number of documents containing term",
"details" : [ ]
},
{
"value" : 4,
"description" : "N, total number of documents with field",
"details" : [ ]
}
]
},
{
"value" : 0.42553192,
"description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
"details" : [
{
"value" : 1.0,
"description" : "freq, occurrences of term within document",
"details" : [ ]
},
{
"value" : 1.2,
"description" : "k1, term saturation parameter",
"details" : [ ]
},
{
"value" : 0.75,
"description" : "b, length normalization parameter",
"details" : [ ]
},
{
"value" : 7.0,
"description" : "dl, length of field",
"details" : [ ]
},
{
"value" : 6.0,
"description" : "avgdl, average length of field",
"details" : [ ]
}
]
}
]
}
]
}
}
]
}
}
感興趣的朋友可以自己根據(jù)公式算一算分?jǐn)?shù),哈哈
2.4 Boosting Query
Boosting是控制相關(guān)度的一種手段,是ES提供給我們的【一個(gè)能干預(yù)評(píng)分結(jié)果】的窗口??梢酝ㄟ^(guò)指定字段的boost值影響查詢結(jié)果。很顯然,boost
的值對(duì)結(jié)果有如下影響:
- 當(dāng)
boost > 1
時(shí),打分的權(quán)重相關(guān)性提升 - 當(dāng)
0 < boost <1
時(shí),打分的權(quán)重相關(guān)性降低 - 當(dāng)
boost <0
時(shí),貢獻(xiàn)負(fù)分
boost
函數(shù)公式為基數(shù) * 縮放因數(shù)
,基數(shù)默認(rèn)值為2.2
,縮放因子
默認(rèn)為1
,我們修改的值實(shí)際上為后面的縮放因子
應(yīng)用場(chǎng)景
希望包含了某項(xiàng)內(nèi)容的結(jié)果不是不出現(xiàn),而是排序靠后
示例數(shù)據(jù):
1)首先還是先插入一些示例數(shù)據(jù)。注意title
和content
,下面兩條記錄的title
跟content
內(nèi)容剛好互換
POST /blogs/_bulk
{"index":{"_id":1}}
{"title":"Apple iPad","content":"Apple iPad,Apple iPad"}
{"index":{"_id":2}}
{"title":"Apple iPad,Apple iPad","content":"Apple iPad"}
示例查詢輸入:
2)然后做一下簡(jiǎn)單的查詢,在字段title
和content
上查詢apple,ipad
。使用復(fù)合查詢的bool-should
,表示只要有一個(gè)符合條件即可返回
GET /blogs/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "apple,ipad"
}
}
},
{
"match": {
"content": {
"query": "apple,ipad"
}
}
}
]
}
}
}
你們猜上面的搜索,兩個(gè)文檔的得分是否一樣?哈,其實(shí)稍微運(yùn)算一下就可以看出來(lái),是一樣的。根據(jù)【簡(jiǎn)記版公式】,apple
跟ipad
詞項(xiàng)的得分如下:
apple
詞項(xiàng)得分
= 在字段title上的得分 + 在content字段上的得分
= title(詞頻 * 逆文檔頻率 * boost) + content(詞頻 * 逆文檔頻率 * boost)
= title[ (詞項(xiàng)在文檔出現(xiàn)的次數(shù) / 文檔的總次數(shù)) * log(索引文檔總數(shù)/(包含該詞項(xiàng)的文檔數(shù)+1)) * boost ] + content[ (詞項(xiàng)在文檔出現(xiàn)的次數(shù) / 文檔的總次數(shù)) * log(索引文檔總數(shù)/(包含該詞項(xiàng)的文檔數(shù)+1)) * boost ]
= title[(3/6) + (2/(2+1)) * 2.2 ] + content[(3/6) + (2/(2+1)) * 2.2 ]ipad
詞項(xiàng)得分
= 在字段title上的得分 + 在content字段上的得分
= title(詞頻 * 逆文檔頻率 * boost) + content(詞頻 * 逆文檔頻率 * boost)
= title[ (詞項(xiàng)在文檔出現(xiàn)的次數(shù) / 文檔的總次數(shù)) * log(索引文檔總數(shù)/(包含該詞項(xiàng)的文檔數(shù)+1)) * boost ] + content[ (詞項(xiàng)在文檔出現(xiàn)的次數(shù) / 文檔的總次數(shù)) * log(索引文檔總數(shù)/(包含該詞項(xiàng)的文檔數(shù)+1)) * boost ]
= title[(3/6) + (2/(2+1)) * 2.2 ] + content[(3/6) + (2/(2+1)) * 2.2 ]
示例查詢輸出:
看,得分都是0.8806269
。但是我稍微改一下,得分就變得不一樣了。
修改后查詢輸入:注意,修改了content字段上的boost縮放因子
GET /blogs/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": {
"query": "apple,ipad",
"boost": 1
}
}
},
{
"match": {
"content": {
"query": "apple,ipad",
"boost": 5
}
}
}
]
}
}
}
修改后示例輸出:
案例:要求蘋(píng)果公司的產(chǎn)品信息優(yōu)先展示
POST /news/_bulk
{"index":{"_id":1}}
{"content":"Apple Mac"}
{"index":{"_id":2}}
{"content":"Apple iPad"}
{"index":{"_id":3}}
{"content":"Apple employee like Apple Pie and Apple Juice"}
GET /news/_search
{
"query": {
"bool": {
"must": {
"match": {
"content": "apple"
}
}
}
}
}
利用must not排除不是蘋(píng)果公司產(chǎn)品的文檔
GET /news/_search
{
"query": {
"bool": {
"must": {
"match": {
"content": "apple"
}
},
"must_not": {
"match":{
"content": "pie"
}
}
}
}
}
但是生產(chǎn)環(huán)境有時(shí)候不能這么簡(jiǎn)單粗暴的直接排除掉的。所以這個(gè)時(shí)候可以考慮boosting query
的negative_boost
。
- negative_boost 對(duì) negative部分query生效
- 計(jì)算評(píng)分時(shí),boosting部分評(píng)分不修改,negative部分query乘以negative_boost值
- negative_boost取值:0-1.0,舉例:0.3
通過(guò)negative_boost
反向取一個(gè)分?jǐn)?shù)
GET /news/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"content": "apple"
}
},
"negative": {
"match": {
"content": "pie"
}
},
"negative_boost": 0.2
}
}
}
三、單字符串多字段查詢
【單字符串多字段】指的是一種查詢方式,它有三種不同的查詢策略:
-
最佳字段(Best Fields)
:在多個(gè)字段上查詢的時(shí)候,選擇評(píng)分最高的字段(默認(rèn)是累加) -
多數(shù)字段(Most Fields)
:處理英文內(nèi)容時(shí)的一種常見(jiàn)的手段是,在主字段( English Analyzer),抽取詞干,加入同義詞,以匹配更多的文檔。相同的文本,加入子字段(Standard Analyzer),以提供更加精確的匹配。其他字段作為匹配文檔提高相關(guān)度的信號(hào),匹配字段越多則越好 -
混合字段(Cross Fields)
:對(duì)于某些實(shí)體,例如人名,地址,圖書(shū)信息。需要在多個(gè)字段中確定信息,單個(gè)字段只能作為整體的一部分。希望在任何這些列出的字段中找到盡可能多的詞
3.1 最佳字段查詢Dis Max Query
Dis Max Query:將搜索詞任意詞項(xiàng)與查詢匹配的文檔作為結(jié)果返回,采用字段上最匹配的評(píng)分最終評(píng)分返回max(a,b)
(屬于復(fù)合查詢的一種,既然是復(fù)合查詢,那肯定存在復(fù)合查詢的關(guān)鍵詞)
關(guān)鍵詞:dis_max
示例數(shù)據(jù):
1)先準(zhǔn)備數(shù)據(jù)
DELETE /blogs
PUT /blogs/_doc/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /blogs/_doc/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
2)然后做一個(gè)簡(jiǎn)單的多字段查詢,查詢內(nèi)容如下:
POST /blogs/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
正常來(lái)說(shuō),id=2
的文檔才是我們最關(guān)注的,換句話來(lái)說(shuō)它的得分應(yīng)該是我們預(yù)想中比較高的,畢竟它的body
里面就有我們要搜索的brown fox
完整的詞項(xiàng)。但是查詢出來(lái)的結(jié)果可能有點(diǎn)出乎大家意料,為什么?
其實(shí)也不難理解。因?yàn)樗阉髟~會(huì)被拆分成brown
跟fox
詞項(xiàng),然而id=1
的title
跟body
的都含有brown
詞項(xiàng),所以,在評(píng)分的過(guò)程中,id=1
的得分就可能比id=2
的高了。
總的來(lái)說(shuō),結(jié)果之所以出乎意料的原因還是沒(méi)有認(rèn)識(shí)到title
跟body
是競(jìng)爭(zhēng)關(guān)系,不應(yīng)該將分?jǐn)?shù)簡(jiǎn)單疊加,而是應(yīng)該找到單個(gè)最佳匹配的字段的評(píng)分。
3.1.1 使用最佳字段查詢dis max query
示例輸入數(shù)據(jù):
POST /blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
示例輸出數(shù)據(jù):
上述例子是把title
跟body
當(dāng)作了競(jìng)爭(zhēng)關(guān)系,只能二選一。但有時(shí)候,我們又希望是【主從關(guān)系】,而不是完全把另一個(gè)字段拋棄掉,這個(gè)時(shí)候就可以通過(guò)tie_breaker
參數(shù)調(diào)整
3.1.2 通過(guò)tie_breaker參數(shù)調(diào)整
Tier Breaker是一個(gè)介于0-1之間的浮點(diǎn)數(shù)。0代表使用最佳匹配;1代表所有語(yǔ)句同等重要。
- 獲得最佳匹配語(yǔ)句的評(píng)分_score 。
- 將其他匹配語(yǔ)句的評(píng)分與tie_breaker相乘
- 對(duì)以上評(píng)分求和并規(guī)范化
最終得分=最佳匹配字段+其他匹配字段*tie_breaker
POST /blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
],
"tie_breaker": 0.1
}
}
}
3.2 Multi Match Query
這個(gè)在上一篇文章已經(jīng)稍微提過(guò)了,這邊說(shuō)到了單字符多字段查詢了,就再說(shuō)一下吧。因?yàn)樗举|(zhì)上就是屬于【單字符多字段查詢】。Multi Match Query
有三種不同的策略,分別是:Best Fields
、Most Fields
、Cross Fields
。
3.2.1 Best Fields:最佳字段搜索(默認(rèn))
嘿嘿嘿,雖然這個(gè)搜索改了個(gè)名字,但是從意思上不難看出來(lái),這不就是前面的Dis Max Query嗎?是的,一樣一樣的。best_fields
策略獲取最佳匹配字段的得分, final_score = max(其他匹配字段得分, 最佳匹配字段得分)
采用best_fields
查詢,并添加參數(shù)tie_breaker=0.1
,可以起到調(diào)控的效果,final_score = 其他匹配字段得分 * 0.1 + 最佳匹配字段得分
關(guān)鍵字:multi_match
、tie_breaker
、best_fields
、most_fields
、cross_fields
示例查詢輸入:
這里直接演示tie_breaker
了,不加的時(shí)候跟dis_max
沒(méi)啥兩樣,就不演示了。使用示例如下:
POST /blogs/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "Brown fox",
"fields": ["title","body"],
"tie_breaker": 0.2
}
}
}
輸出也不貼了,這里最關(guān)鍵的還是理解tie_breaker
對(duì)打分的影響
3.2.2 Most Fields:使用多數(shù)字段搜索
most_fields
策略獲取全部匹配字段的累計(jì)得分(綜合全部匹配字段的得分),這個(gè)不就是默認(rèn)的評(píng)分公式嘛
POST /blogs/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "Brown fox",
"fields": ["title","body"],
"tie_breaker": 0.2
}
}
}
3.2.3 Cross Field:跨字段搜索
搜索內(nèi)容在多個(gè)字段中都顯示,類似bool + must/should
組合。怎么理解呢?(em…還記得bool關(guān)鍵詞的must/should有什么特點(diǎn)吧?不記得翻我上一篇文章咯)
是因?yàn)?code>cross_fileds支持operator
關(guān)鍵詞,所以篩選記錄的邏輯,在operator=and
的時(shí)候跟bool=must
一樣,即字段上都要包含關(guān)鍵字;operator=or
的時(shí)候跟bool=should
一樣,即字段中至少有一個(gè)包含關(guān)鍵字
示例數(shù)據(jù):
DELETE /address
PUT /address
{
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
PUT /address/_bulk
{ "index": { "_id": "1"} }
{"province": "湖南","city": "長(zhǎng)沙"}
{ "index": { "_id": "2"} }
{"province": "湖南","city": "常德"}
{ "index": { "_id": "3"} }
{"province": "廣東","city": "廣州"}
{ "index": { "_id": "4"} }
{"province": "湖南","city": "邵陽(yáng)"}
示例查詢輸入一:operator=and
GET /address/_search
{
"query": {
"multi_match": {
"query": "湖南常德",
"type": "cross_fields",
"operator": "and",
"fields": ["province","city"]
}
}
}
# 上面等價(jià)于下面的查詢
GET /address/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"province": "湖南常德"
}
},
{
"match": {
"city": "湖南常德"
}
}
]
}
}
}
示例查詢輸入二:operator=or
GET /address/_search
{
"query": {
"multi_match": {
"query": "湖南常德",
"type": "cross_fields",
"operator": "or",
"fields": ["province","city"]
}
}
}
# 上面等價(jià)于下面的查詢
GET /address/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"province": "湖南常德"
}
},
{
"match": {
"city": "湖南常德"
}
}
]
}
}
}
四、ElasticSearch聚合操作
Elasticsearch除搜索以外,提供了針對(duì)ES 數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析的功能。聚合(aggregations)可以讓我們極其方便的實(shí)現(xiàn)對(duì)數(shù)據(jù)的統(tǒng)計(jì)、分析、運(yùn)算。例如:
- 什么品牌的手機(jī)最受歡迎?
- 這些手機(jī)的平均價(jià)格、最高價(jià)格、最低價(jià)格?
- 這些手機(jī)每月的銷售情況如何?
4.1 使用場(chǎng)景
聚合查詢可以用于各種場(chǎng)景,比如商業(yè)智能、數(shù)據(jù)挖掘、日志分析等等。
- 電商平臺(tái)的銷售分析:統(tǒng)計(jì)每個(gè)地區(qū)的銷售額、每個(gè)用戶的消費(fèi)總額、每個(gè)產(chǎn)品的銷售量等,以便更好地了解銷售情況和趨勢(shì)。
- 社交媒體的用戶行為分析:統(tǒng)計(jì)每個(gè)用戶的發(fā)布次數(shù)、轉(zhuǎn)發(fā)次數(shù)、評(píng)論次數(shù)等,以便更好地了解用戶行為和趨勢(shì),同時(shí)可以將數(shù)據(jù)按照地區(qū)、時(shí)間、話題等維度進(jìn)行分析。
- 物流企業(yè)的運(yùn)輸分析:統(tǒng)計(jì)每個(gè)區(qū)域的運(yùn)輸量、每個(gè)車輛的運(yùn)輸次數(shù)、每個(gè)司機(jī)的行駛里程等,以便更好地了解運(yùn)輸情況和優(yōu)化運(yùn)輸效率。
- 金融企業(yè)的交易分析:統(tǒng)計(jì)每個(gè)客戶的交易總額、每個(gè)產(chǎn)品的銷售量、每個(gè)交易員的業(yè)績(jī)等,以便更好地了解交易情況和優(yōu)化業(yè)務(wù)流程。
- 智能家居的設(shè)備監(jiān)控分析:統(tǒng)計(jì)每個(gè)設(shè)備的使用次數(shù)、每個(gè)家庭的能源消耗量、每個(gè)時(shí)間段的設(shè)備使用率等,以便更好地了解用戶需求和優(yōu)化設(shè)備效能。
4.2 基本語(yǔ)法
聚合查詢的語(yǔ)法結(jié)構(gòu)與其他查詢相似,通常包含以下部分:
- 查詢條件:指定需要聚合的文檔,可以使用標(biāo)準(zhǔn)的 Elasticsearch 查詢語(yǔ)法,如 term、match、range 等等。
- 聚合函數(shù):指定要執(zhí)行的聚合操作,如 sum、avg、min、max、terms、date_histogram 等等。每個(gè)聚合命令都會(huì)生成一個(gè)聚合結(jié)果。
- 聚合嵌套:聚合命令可以嵌套,以便更細(xì)粒度地分析數(shù)據(jù)。
GET <index_name>/_search
{
"aggs": {
"<aggs_name>": { // 聚合名稱需要自己定義
"<agg_type>": {
"field": "<field_name>"
}
}
}
}
- aggs_name:聚合函數(shù)的名稱,需要自定義
- agg_type:聚合種類,比如是桶聚合(terms)或者是指標(biāo)聚合(avg、sum、min、max等)
- field_name:字段名稱或者叫域名。
4.3 聚合的分類
-
Metric Aggregation
:—些數(shù)學(xué)運(yùn)算,可以對(duì)文檔字段進(jìn)行統(tǒng)計(jì)分析,類比Mysql中的 min(), max(), sum() 操作。
SELECT MIN(price), MAX(price) FROM products
#Metric聚合的DSL類比實(shí)現(xiàn):
{
"aggs":{
"avg_price":{
"min":{
"field":"price"
}
}
}
}
-
Bucket Aggregation
: 一些滿足特定條件的文檔的集合放置到一個(gè)桶里,每一個(gè)桶關(guān)聯(lián)一個(gè)key,類比Mysql中的group by操作
SELECT size COUNT(*) FROM products GROUP BY size
#bucket聚合的DSL類比實(shí)現(xiàn):
{
"aggs": {
"by_size": {
"terms": {
"field": "size"
}
}
}
-
Pipeline Aggregation
:對(duì)其他的聚合結(jié)果進(jìn)行二次聚合
4.4 Metric Aggregation
- 單值分析︰只輸出一個(gè)分析結(jié)果
- min, max, avg, sum
- Cardinality(類似distinct Count)
- 多值分析:輸出多個(gè)分析結(jié)果
- stats(統(tǒng)計(jì)), extended stats
- percentile (百分位), percentile rank
- top hits(排在前面的示例)
查詢員工的最低最高和平均工資:關(guān)鍵詞【max】、【min】、【avg】
#多個(gè) Metric 聚合,找到最低最高和平均工資
POST /employees/_search
{
"size": 0,
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
},
"min_salary": {
"min": {
"field": "salary"
}
},
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
}
對(duì)salary進(jìn)行統(tǒng)計(jì):關(guān)鍵詞【stats】
# 一個(gè)聚合,輸出多值
POST /employees/_search
{
"size": 0,
"aggs": {
"stats_salary": {
"stats": {
"field":"salary"
}
}
}
}
cardinate對(duì)搜索結(jié)果去重:關(guān)鍵詞【cardinality】
POST /employees/_search
{
"size": 0,
"aggs": {
"cardinate": {
"cardinality": {
"field": "job.keyword"
}
}
}
}
4.5 Bucket Aggregation
按照一定的規(guī)則,將文檔分配到不同的桶中,從而達(dá)到分類的目的。ES提供的一些常見(jiàn)的 Bucket Aggregation。
- Terms,需要字段支持filedata
- keyword 默認(rèn)支持fielddata
- text需要在Mapping 中開(kāi)啟fielddata,會(huì)按照分詞后的結(jié)果進(jìn)行分桶
- 數(shù)字類型
- Range / Data Range
- Histogram(直方圖) / Date Histogram
- 支持嵌套: 也就在桶里再做分桶
桶聚合可以用于各種場(chǎng)景,例如:
- 對(duì)數(shù)據(jù)進(jìn)行分組統(tǒng)計(jì),比如按照地區(qū)、年齡段、性別等字段進(jìn)行分組統(tǒng)計(jì)。
- 對(duì)時(shí)間序列數(shù)據(jù)進(jìn)行時(shí)間段分析,比如按照每小時(shí)、每天、每月、每季度、每年等時(shí)間段進(jìn)行分析。
- 對(duì)各種標(biāo)簽信息分類,并統(tǒng)計(jì)其數(shù)量。
1)使用示例:獲取job的分類信息
# 對(duì)keword 進(jìn)行聚合
GET /employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword"
}
}
}
}
聚合可配置屬性有:
- field:指定聚合字段
- size:指定聚合結(jié)果數(shù)量
- order:指定聚合結(jié)果排序方式
默認(rèn)情況下,Bucket聚合會(huì)統(tǒng)計(jì)Bucket內(nèi)的文檔數(shù)量,記為_(kāi)count,并且按照_count降序排序。我們可以指定order屬性,自定義聚合的排序方式:
GET /employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword",
"size": 10,
"order": {
"_count": "desc"
}
}
}
}
}
2)使用示例:限定聚合范圍
#只對(duì)salary在10000元以上的文檔聚合
GET /employees/_search
{
"query": {
"range": {
"salary": {
"gte": 10000
}
}
},
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword",
"size": 10,
"order": {
"_count": "desc"
}
}
}
}
}
注意:對(duì) Text 字段進(jìn)行 terms 聚合查詢,會(huì)失敗拋出異常
mployees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job"
}
}
}
}
解決辦法:對(duì) Text 字段打開(kāi) fielddata,支持terms aggregation
PUT /employees/_mapping
{
"properties" : {
"job":{
"type": "text",
"fielddata": true
}
}
}
# 對(duì) Text 字段進(jìn)行分詞,分詞后的terms
POST /employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job"
}
}
}
}
3)使用示例:Range & Histogram聚合
- 按照數(shù)字的范圍,進(jìn)行分桶
- 在Range Aggregation中,可以自定義Key
Range 示例:按照工資的 Range 分桶
Salary Range分桶,可以自己定義 key
POST employees/_search
{
"size": 0,
"aggs": {
"salary_range": {
"range": {
"field":"salary",
"ranges":[
{
"to":10000
},
{
"from":10000,
"to":20000
},
{
"key":">20000",
"from":20000
}
]
}
}
}
}
Histogram示例:按照工資的間隔分桶
#工資0到10萬(wàn),以 5000一個(gè)區(qū)間進(jìn)行分桶
POST employees/_search
{
"size": 0,
"aggs": {
"salary_histrogram": {
"histogram": {
"field":"salary",
"interval":5000,
"extended_bounds":{
"min":0,
"max":100000
}
}
}
}
}
top_hits應(yīng)用場(chǎng)景: 當(dāng)獲取分桶后,桶內(nèi)最匹配的頂部文檔列表
# 指定size,不同工種中,年紀(jì)最大的3個(gè)員工的具體信息
POST /employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword"
},
"aggs":{
"old_employee":{
"top_hits":{
"size":3,
"sort":[
{
"age":{
"order":"desc"
}
}
]
}
}
}
}
}
}
嵌套聚合示例
# 嵌套聚合1,按照工作類型分桶,并統(tǒng)計(jì)工資信息
POST employees/_search
{
"size": 0,
"aggs": {
"Job_salary_stats": {
"terms": {
"field": "job.keyword"
},
"aggs": {
"salary": {
"stats": {
"field": "salary"
}
}
}
}
}
}
# 多次嵌套。根據(jù)工作類型分桶,然后按照性別分桶,計(jì)算工資的統(tǒng)計(jì)信息
POST employees/_search
{
"size": 0,
"aggs": {
"Job_gender_stats": {
"terms": {
"field": "job.keyword"
},
"aggs": {
"gender_stats": {
"terms": {
"field": "gender"
},
"aggs": {
"salary_stats": {
"stats": {
"field": "salary"
}
}
}
}
}
}
}
}
4.6 Pipeline Aggregation
支持對(duì)聚合分析的結(jié)果,再次進(jìn)行聚合分析。
Pipeline 的分析結(jié)果會(huì)輸出到原結(jié)果中,根據(jù)位置的不同,分為兩類:
- Sibling - 結(jié)果和現(xiàn)有分析結(jié)果同級(jí)
- Max,min,Avg & Sum Bucket
- Stats,Extended Status Bucket
- Percentiles Bucket
- Parent -結(jié)果內(nèi)嵌到現(xiàn)有的聚合分析結(jié)果之中
- Derivative(求導(dǎo))
- Cumultive Sum(累計(jì)求和)
- Moving Function(移動(dòng)平均值 )
1)min_bucket示例
# 平均工資最低的工種
POST employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
},
"min_salary_by_job":{
"min_bucket": {
"buckets_path": "jobs>avg_salary"
}
}
}
}
- min_salary_by_job結(jié)果和jobs的聚合同級(jí)
- min_bucket求之前結(jié)果的最小值
- 通過(guò)bucket_path關(guān)鍵字指定路徑
2)Stats示例
# 平均工資的統(tǒng)計(jì)分析
POST employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
},
"stats_salary_by_job":{
"stats_bucket": {
"buckets_path": "jobs>avg_salary"
}
}
}
}
3)percentiles示例
# 平均工資的百分位數(shù)
POST employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
},
"percentiles_salary_by_job":{
"percentiles_bucket": {
"buckets_path": "jobs>avg_salary"
}
}
}
}
4)Cumulative_sum示例
#Cumulative_sum 累計(jì)求和
POST employees/_search
{
"size": 0,
"aggs": {
"age": {
"histogram": {
"field": "age",
"min_doc_count": 0,
"interval": 1
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
},
"cumulative_salary":{
"cumulative_sum": {
"buckets_path": "avg_salary"
}
}
}
}
}
}
4.7 聚合的作用范圍
ES聚合分析的默認(rèn)作用范圍是query的查詢結(jié)果集,同時(shí)ES還支持以下方式改變聚合的作用范圍:
- Filter
- Post Filter
- Global
#Query
POST employees/_search
{
"size": 0,
"query": {
"range": {
"age": {
"gte": 20
}
}
},
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword"
}
}
}
}
#Filter
POST employees/_search
{
"size": 0,
"aggs": {
"older_person": {
"filter":{
"range":{
"age":{
"from":35
}
}
},
"aggs":{
"jobs":{
"terms": {
"field":"job.keyword"
}
}
}},
"all_jobs": {
"terms": {
"field":"job.keyword"
}
}
}
}
#Post field. 一條語(yǔ)句,找出所有的job類型。還能找到聚合后符合條件的結(jié)果
POST employees/_search
{
"aggs": {
"jobs": {
"terms": {
"field": "job.keyword"
}
}
},
"post_filter": {
"match": {
"job.keyword": "Dev Manager"
}
}
}
#global
POST employees/_search
{
"size": 0,
"query": {
"range": {
"age": {
"gte": 40
}
}
},
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword"
}
},
"all":{
"global":{},
"aggs":{
"salary_avg":{
"avg":{
"field":"salary"
}
}
}
}
}
}
4.8 排序
指定order,按照count和key進(jìn)行排序:
- 默認(rèn)情況,按照count降序排序
- 指定size,就能返回相應(yīng)的桶
#排序 order
#count and key
POST employees/_search
{
"size": 0,
"query": {
"range": {
"age": {
"gte": 20
}
}
},
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword",
"order":[
{"_count":"asc"},
{"_key":"desc"}
]
}
}
}
}
#排序 order
#count and key
POST employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword",
"order":[ {
"avg_salary":"desc"
}]
},
"aggs": {
"avg_salary": {
"avg": {
"field":"salary"
}
}
}
}
}
}
#排序 order
#count and key
POST employees/_search
{
"size": 0,
"aggs": {
"jobs": {
"terms": {
"field":"job.keyword",
"order":[ {
"stats_salary.min":"desc"
}]
},
"aggs": {
"stats_salary": {
"stats": {
"field":"salary"
}
}
}
}
}
}
4.8 ES聚合分析不精準(zhǔn)原因分析
ElasticSearch在對(duì)海量數(shù)據(jù)進(jìn)行聚合分析的時(shí)候會(huì)損失搜索的精準(zhǔn)度來(lái)滿足實(shí)時(shí)性的需求。
Terms聚合分析的執(zhí)行流程:
不精準(zhǔn)的原因: 數(shù)據(jù)分散到多個(gè)分片,聚合是每個(gè)分片的取 Top X,導(dǎo)致結(jié)果不精準(zhǔn)。ES 可以不每個(gè)分片Top X,而是全量聚合,但勢(shì)必這會(huì)有很大的性能問(wèn)題。
4.9 Elasticsearch 聚合性能優(yōu)化
4.9.1 啟用 eager global ordinals 提升高基數(shù)聚合性能
適用場(chǎng)景:高基數(shù)聚合 。高基數(shù)聚合場(chǎng)景中的高基數(shù)含義:一個(gè)字段包含很大比例的唯一值。
global ordinals 中文翻譯成全局序號(hào),是一種數(shù)據(jù)結(jié)構(gòu),應(yīng)用場(chǎng)景如下:
- 基于 keyword,ip 等字段的分桶聚合,包含:terms聚合、composite 聚合等。
- 基于text 字段的分桶聚合(前提條件是:fielddata 開(kāi)啟)。
- 基于父子文檔 Join 類型的 has_child 查詢和 父聚合。
global ordinals 使用一個(gè)數(shù)值代表字段中的字符串值,然后為每一個(gè)數(shù)值分配一個(gè) bucket(分桶)。
global ordinals 的本質(zhì)是:?jiǎn)⒂?eager_global_ordinals 時(shí),會(huì)在刷新(refresh)分片時(shí)構(gòu)建全局序號(hào)。這將構(gòu)建全局序號(hào)的成本從搜索階段轉(zhuǎn)移到了數(shù)據(jù)索引化(寫(xiě)入)階段。
創(chuàng)建索引的同時(shí)開(kāi)啟:eager_global_ordinals。
PUT /my-index
{
"mappings": {
"properties": {
"tags": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
注意:開(kāi)啟 eager_global_ordinals 會(huì)影響寫(xiě)入性能,因?yàn)槊看嗡⑿聲r(shí)都會(huì)創(chuàng)建新的全局序號(hào)。為了最大程度地減少由于頻繁刷新建立全局序號(hào)而導(dǎo)致的額外開(kāi)銷,請(qǐng)調(diào)大刷新間隔 refresh_interval。
動(dòng)態(tài)調(diào)整刷新頻率的方法如下:
PUT my-index/_settings
{
"index": {
"refresh_interval": "30s"
}
該招數(shù)的本質(zhì)是:以空間換時(shí)間。
4.9.1 插入數(shù)據(jù)時(shí)對(duì)索引進(jìn)行預(yù)排序
- Index sorting (索引排序)可用于在插入時(shí)對(duì)索引進(jìn)行預(yù)排序,而不是在查詢時(shí)再對(duì)索引進(jìn)行排序,這將提高范圍查詢(range query)和排序操作的性能。
- 在 Elasticsearch 中創(chuàng)建新索引時(shí),可以配置如何對(duì)每個(gè)分片內(nèi)的段進(jìn)行排序。
- 這是 Elasticsearch 6.X 之后版本才有的特性。
PUT /my_index
{
"settings": {
"index":{
"sort.field": "create_time",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"create_time":{
"type": "date"
}
}
}
}
注意:預(yù)排序?qū)⒃黾?Elasticsearch 寫(xiě)入的成本。在某些用戶特定場(chǎng)景下,開(kāi)啟索引預(yù)排序會(huì)導(dǎo)致大約 40%-50% 的寫(xiě)性能下降。也就是說(shuō),如果用戶場(chǎng)景更關(guān)注寫(xiě)性能的業(yè)務(wù),開(kāi)啟索引預(yù)排序不是一個(gè)很好的選擇。
4.9.3 使用節(jié)點(diǎn)查詢緩存
節(jié)點(diǎn)查詢緩存(Node query cache)可用于有效緩存過(guò)濾器(filter)操作的結(jié)果。如果多次執(zhí)行同一 filter 操作,這將很有效,但是即便更改過(guò)濾器中的某一個(gè)值,也將意味著需要計(jì)算新的過(guò)濾器結(jié)果。
例如,由于 “now” 值一直在變化,因此無(wú)法緩存在過(guò)濾器上下文中使用 “now” 的查詢。
那怎么使用緩存呢?通過(guò)在 now 字段上應(yīng)用 datemath 格式將其四舍五入到最接近的分鐘/小時(shí)等,可以使此類請(qǐng)求更具可緩存性,以便可以對(duì)篩選結(jié)果進(jìn)行緩存。
PUT /my_index/_doc/1
{
"create_time":"2022-05-11T16:30:55.328Z"
}
#下面的示例無(wú)法使用緩存
GET /my_index/_search
{
"query":{
"constant_score": {
"filter": {
"range": {
"create_time": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
}
}
# 下面的示例就可以使用節(jié)點(diǎn)查詢緩存。
GET /my_index/_search
{
"query":{
"constant_score": {
"filter": {
"range": {
"create_time": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}
上述示例中的“now-1h/m” 就是 datemath 的格式。
如果當(dāng)前時(shí)間 now 是:16:31:29,那么range query 將匹配 my_date 介于:15:31:00 和 15:31:59 之間的時(shí)間數(shù)據(jù)。同理,聚合的前半部分 query 中如果有基于時(shí)間查詢,或者后半部分 aggs 部分中有基于時(shí)間聚合的,建議都使用 datemath 方式做緩存處理以優(yōu)化性能。
4.9.4 使用分片請(qǐng)求緩存
聚合語(yǔ)句中,設(shè)置:size:0,就會(huì)使用分片請(qǐng)求緩存緩存結(jié)果。size = 0 的含義是:只返回聚合結(jié)果,不返回查詢結(jié)果。
GET /es_db/_search
{
"size": 0,
"aggs": {
"remark_agg": {
"terms": {
"field": "remark.keyword"
}
}
}
}
4.9.5 拆分聚合,使聚合并行化
Elasticsearch 查詢條件中同時(shí)有多個(gè)條件聚合,默認(rèn)情況下聚合不是并行運(yùn)行的。當(dāng)為每個(gè)聚合提供自己的查詢并執(zhí)行 msearch 時(shí),性能會(huì)有顯著提升。因此,在 CPU 資源不是瓶頸的前提下,如果想縮短響應(yīng)時(shí)間,可以將多個(gè)聚合拆分為多個(gè)查詢,借助:msearch 實(shí)現(xiàn)并行聚合。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-793569.html
#常規(guī)的多條件聚合實(shí)現(xiàn)
GET /employees/_search
{
"size": 0,
"aggs": {
"job_agg": {
"terms": {
"field": "job.keyword"
}
},
"max_salary":{
"max": {
"field": "salary"
}
}
}
}
# msearch 拆分多個(gè)語(yǔ)句的聚合實(shí)現(xiàn)
GET _msearch
{"index":"employees"}
{"size":0,"aggs":{"job_agg":{"terms":{"field": "job.keyword"}}}}
{"index":"employees"}
{"size":0,"aggs":{"max_salary":{"max":{"field": "salary"}}}}
學(xué)習(xí)總結(jié)
感謝
感謝大佬【止步前行】的文章《ElasticSearch之score打分機(jī)制原理》文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-793569.html
到了這里,關(guān)于【ES專題】ElasticSearch搜索進(jìn)階的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!