背景介紹
需要對索引中的做聚合寝蹈,但是聚合的條件會比較復(fù)雜讼呢,并非從單一字段進(jìn)行聚合帽馋。參考如下數(shù)據(jù)結(jié)構(gòu)
{
//空格分詞字段
"feild_a": "1 2 3 4 5",
//keyword字段
"feild_b": "2"
}
要做的事:我們有 1 2 3 …… 等id,需要統(tǒng)計(jì)索引里面谬哀,a和b字段分別含有1 2 3對應(yīng)的數(shù)量刺覆。文字難以表述的需求我們轉(zhuǎn)化成數(shù)據(jù)結(jié)構(gòu)來看
{
"data": [{
"id": "1",
"count": 1
}, {
"id": "2",
"count": 2
}, {
"id": "3",
"count": 1
}]
}
如果只有上面那一條數(shù)據(jù)的話,那么應(yīng)該得出下面這個(gè)統(tǒng)計(jì)結(jié)果史煎。
問題分析
從表面剖析這個(gè)需求的話谦屑,似乎是個(gè)多字段的聚合。相對于單字段聚合來說問題還是比較棘手的篇梭。并不能簡單的通過單字段的聚合來解決問題氢橙,我們先從最簡單的情況開始處理問題。以統(tǒng)計(jì)1 2 3這3個(gè)id來舉例子恬偷。
1.單字段情況下聚合
假設(shè)只需要對一個(gè)字段聚合悍手,比如b字段,b字段是keyword類型袍患,需要考慮的情況最為簡單坦康,當(dāng)要對b字段聚合時(shí)語句很好寫,如下即可
{
"from": 0,
"size": 0,
"query": {
"bool": {
"must": [{
"bool": {
"should": [{
"terms": {
"field_a": ["1", "2", "3"],
"boost": 1.0
}
}, {
"terms": {
"field_b": ["1", "2", "3"],
"boost": 1.0
}
}],
"adjust_pure_negative": true,
"minimum_should_match": "1",
"boost": 1.0
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
},
"aggregations": {
"my_agg": {
"terms": {
"field": "field_b"
}
}
}
}
這是完整的query诡延,后面的查詢會省略掉query部分滞欠。query部分的用處也很明顯:只把需要做聚合的部分過濾出來做聚合,我們需要統(tǒng)計(jì)的數(shù)據(jù)就在這部分中肆良,而不是整個(gè)索引庫筛璧。這樣有兩個(gè)好處:
1.提高效率逸绎,減少需要聚合的數(shù)據(jù)的數(shù)量
2.剔除需要考慮的意外情況,降低語句的復(fù)雜度
而聚合部分就非常簡單了夭谤,僅僅對field_b聚合即可棺牧,但是很遺憾,離我們最終目標(biāo)很遠(yuǎn)沮翔,這樣只能統(tǒng)計(jì)出b字段的數(shù)據(jù)分布情況陨帆。
2.多字段情況的聚合
相對于上面的那種曲秉,接下來把另外一個(gè)字段也考慮進(jìn)來看看采蚀。所以我們寫下了這樣的請求語句:
"aggregations": {
"my_agg1": {
"terms": {
"field": "tag_brand_id"
}
},
"my_agg2": {
"terms": {
"field": "brand_cid_array"
}
}
}
勉強(qiáng)的可以看到確實(shí)也是“統(tǒng)計(jì)了兩個(gè)字段的情況”,但是是分開的承二,意味著要自己去解析返回結(jié)果并做計(jì)算來得到最終的返回結(jié)果榆鼠。這確實(shí)是很令人惡心的事,那還有沒有其他辦法呢亥鸠。但是觀察語句的結(jié)構(gòu)發(fā)現(xiàn)妆够,似乎并沒有過多可以更改的余地,所以需要尋求其他靈活的解決辦法负蚊。
3.script agg的聚合
簡單的單聚合無法表達(dá)出多字段聚合的需求神妹,在谷歌過后我尋找到了這樣一種解決方案:使用script,即腳本來描述我的需求家妆。下面這段agg就是為了表達(dá)我想要根據(jù)我的需求靈活處理的一個(gè)方式:
"aggregations": {
"my_agg1": {
"terms": {
"script": " if (doc['field_a'].values.contains('1') || doc['field_b'].values.contains('1')){1};if (doc['field_a'].values.contains('2') || doc['field_b'].values.contains('2')){2};
if (doc['field_a'].values.contains('3') || doc['field_b'].values.contains('3')){3};"
}
}
}
這一段腳本的作用很明顯鸵荠,就是告訴es:當(dāng)a字段或者b字段包括1的時(shí)候,扔到桶1伤极;當(dāng)a字段或者b字段包括2的時(shí)候蛹找,扔到桶2;……以此類推哨坪∮辜玻看上去確實(shí)似乎完全解決了開頭提出來的問題,驗(yàn)證后效率還能接受当编,不是特別慢届慈。但是正當(dāng)我沾沾自喜以為解決了問題的時(shí)候,隨手驗(yàn)證了另外一個(gè)case忿偷,就直接冷水潑頭了:
a字段和b字段是可能包含同一個(gè)id比如2金顿,但是對于統(tǒng)計(jì)結(jié)果來說要求算作一條。
用上面這個(gè)腳本并無法體現(xiàn)出這個(gè)區(qū)別牵舱,而且還會有一個(gè)問題:
請求123和請求321時(shí)會返回不同統(tǒng)計(jì)結(jié)果
因?yàn)閕felse語句的關(guān)系串绩,和||的性質(zhì),在滿足條件1后便會扔到桶1芜壁,而無法在去后續(xù)條件中判斷礁凡。這個(gè)腳本有很明顯的bug存在高氮。但是painless畢竟是腳本,可以使用的API和關(guān)鍵字都非常有限顷牌,寫的復(fù)雜了還會很嚴(yán)重影響效率剪芍,無奈這個(gè)方案也只能pass,即使它看上去差點(diǎn)解決了我的問題窟蓝。
4.filter agg的聚合
在重新看了官方文檔后罪裹,我發(fā)現(xiàn)了agg中的一個(gè)用法,filter agg运挫。
filter agg的用法其實(shí)很簡單状共,但是全意外的和我的需求很契合。之前忽視掉這個(gè)用法的主要原因是看到的示例都是對單字段做聚合谁帕。那如何同時(shí)聚合多個(gè)字段呢峡继?從API入手驗(yàn)證是否可以使用比較靈活的寫法
public KeyedFilter(String key, QueryBuilder filter) {
if (key == null) {
throw new IllegalArgumentException("[key] must not be null");
}
if (filter == null) {
throw new IllegalArgumentException("[filter] must not be null");
}
this.key = key;
this.filter = filter;
}
這是es提供的javaapi中filter agg的構(gòu)造函數(shù),key就是過濾名稱匈挖,filter就是過濾條件碾牌。而且很友好的是,filter類型為QueryBuilder儡循,也就是說舶吗,可以做成比較復(fù)雜的過濾方式。
"aggregations": {
"batch_count": {
"filters": {
"filters": {
"1": {
"bool": {
"should": [{
"term": {
"field_a": {
"value": "1",
"boost": 1.0
}
}
}, {
"term": {
"field_b": {
"value": "1",
"boost": 1.0
}
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
},
"2": {
"bool": {
"should": [{
"term": {
"field_a": {
"value": "2",
"boost": 1.0
}
}
}, {
"term": {
"field_b": {
"value": "2",
"boost": 1.0
}
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
},
"3": {
"bool": {
"should": [{
"term": {
"field_a": {
"value": "3",
"boost": 1.0
}
}
}, {
"term": {
"field_b": {
"value": "3",
"boost": 1.0
}
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
}
},
"other_bucket": false,
"other_bucket_key": "-1"
}
}
}
這就是最后成型的agg塊
問題總結(jié)
agg模塊的開發(fā)是比較麻煩的择膝,首先性能問題比較困擾誓琼,其次語句編寫遠(yuǎn)沒有query模塊的靈活。這次順利解決需求调榄,記錄踊赠。