一、什麼是 ES Nested 巢狀
Elasticsearch 有很多資料型別,大致如下:
- 基本資料型別:
- string 型別。ES 7.x 中,string 型別會升級為:text 和 keyword。keyword 可以排序;text 預設分詞,不可以排序。
- 資料型別:integer、long 等
- 時間型別、布林型別、二進位制型別、區間型別等
- 複雜資料型別:
- 陣列型別:Array
- 物件型別:Object
- Nested 型別
- 特定資料型別:地理位置、IP 等
注意:tring/nested/array 型別欄位不能用作排序欄位。因此 string 型別會升級為:text 和 keyword。keyword 可以排序,text 預設分詞,不可以排序。
2.1 那什麼是 Nested 型別?
Elasticsearch 7.x 文件中,這樣寫到:
The nested type is a specialised version of the object datatype that allows arrays of objects to be indexed in a way that they can be queried independently of each other.
Nested (巢狀)型別,是特殊的物件型別,特殊的地方是索引物件陣列方式不同,允許陣列中的物件各自地進行索引。目的是物件之間彼此獨立被查詢出來。
2.2 如何使用 Nested 型別?
在 ES 的 my_index 索引中儲存 users 欄位。比如說:
{
"group" : "fans",
"users" : [
{
"name" : "John",
"age" : "23"
},
{
"name" : "Alice",
"age" : "18"
}
]
}
其實儲存看上去跟 Object 型別一樣,只不過底層原理對陣列 users 欄位索引方式不同。設定 users 欄位的索引方式 Nested 巢狀型別:
curl -X PUT "localhost:9200/my_index" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"users": {
"type": "nested"
}
}
}
}
'
二、Nested Query 應用場景或案例
比如小老弟我有一波小粉絲,users 欄位型別是 object。儲存如下:
{
"group" : "bysocket_fans",
"users" : [
{
"name" : "John",
"age" : "23"
},
{
"name" : "Alice",
"age" : "18"
}
]
}
{
"group" : "路人甲_fans",
"users" : [
{
"name" : "Alice",
"age" : "22"
},
{
"name" : "Jeff",
"age" : "18"
}
]
}
比如 18 歲大姑娘 Alice 是小老弟我的粉絲,她也可能是周杰倫的粉絲。那這邊就有一個需求,即應用場景:
如何找到 18 歲大姑娘 Alice {"name" : "Alice","age" : "18"} 關注的所有明星呢?
如果用老的查詢語句是這樣搜尋的:
GET /my_index/_search?pretty
{
"query": {
"bool": {
"must": [
{
"match": {
"users.name": "Alice"
}
},
{
"match": {
"users.age": 18
}
}
]
}
}
}
結果發現結果是不對的,路人甲 這條記錄也出現了。
因為匹配到了第一個 Alice + 第二個 Jeff 的 18。所以這種查詢不滿足這個場景
那麼需要使用 Nested 型別並用 Nested 查詢,即讓陣列中的物件各自地進行索引。目的是物件之間彼此獨立被查詢出來。
三、Nested Query 實戰
3.1 設定 Nested 型別
根據 2.2 如何使用 Nested 型別,將 users 欄位型別從 object 修改為 nested:
curl -X PUT "localhost:9200/my_index" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"users": {
"type": "nested"
}
}
}
}
'
3.2 Nested Query
修改後,對應的 Nested Query ,如下:
GET /my_index/_search?pretty
{
"query": {
"bool": {
"must": [
{
"nested": {
"path": "users",
"query": {
"bool": {
"must": [
{
"match": {
"users.name": "Alice"
}
},
{
"match": {
"users.age": 18
}
}
]
}
}
}
}
]
}
}
}
語法很簡單就是:
- key 以 "nested" 開頭
- path 就是巢狀物件陣列的欄位名
- 其他
- score_mode (可選的)匹配子物件的分數相關性分數。avg (預設,使用所有匹配子物件的平均相關性分數)
- ignore_unmapped (可選的)是否忽略 path 未對映,不返回任何文件而不是錯誤。預設為 false,如果 path 不對就報錯
這樣查詢得結果就是對的。
四、Nested Query 效能
這邊測試過,給大家一個測試報告和建議。
壓測環境:3 個 server ,6 個 ES 節點
壓測結論: 使用上小節查詢語句,50 併發情況下,導致千兆網路卡被打滿了。TPS 4000 左右,如果提高併發,就會增加 RT。所以如果高效能大流量情況下,必須用 Nested 應該從網路流量方向進行優化。二者,儘量減少大資料物件的返回
建議:泥瓦匠建議,你聽聽看
- 效能:Common Query 遠遠大於 Nested Query 遠遠大於 Parent/Child Query
- 效能優化:首先考慮減少後面兩種 Query
- 效能優化:Nested Query 業務可以優化下。比如上一小節完全可以多存一個 fanIds 陣列。搜尋兩次,第一次查確定 18 歲大姑娘 Alice 的 fanId,第二次根據 fanId 搜尋即可
- 效能優化:實在沒辦法,高效能大流量情況下,必須用 Nested 應該從網路流量方向進行優化。二者,儘量減少大資料物件的返回
(完)
參考資料: