Elasticsearch系列---結構化搜索


概要

結構化搜索針對日期、時間、數字等結構化數據的搜索,它們有自己的格式,我們可以對它們進行范圍,比較大小等邏輯操作,這些邏輯操作得到的結果非黑即白,要么符合條件在結果集里,要么不符合條件在結果集之外,沒有那種相似的概念。

前言

結構化搜索將會有大量的搜索實例,我們將"音樂APP"作為主要的案例背景,去開發一些跟音樂APP相關的搜索或數據分析,有助力於我們理解實戰的目標,順帶鞏固一下學習的知識。

我們將一首歌需要的字段暫定為:

name code type remark
ID id keyword 文檔ID
歌手 author text
歌曲名稱 name text
歌詞 content text
語種 language text
標簽 tags text
歌曲時長 length long 記錄秒數
喜歡次數 likes long 點擊喜歡1次,自增1
是否發布 isRelease boolean true已發布,false未發布
發布日期 releaseDate date

我們手動定義的索引mapping信息如下:

PUT /music
{
  "mappings": {
      "children": {
        "properties": {
          "id": {
            "type": "keyword"
          },
          "author_first_name": {
            "type": "text",
            "analyzer": "english"
          },
          "author_last_name": {
            "type": "text",
            "analyzer": "english"
          },
          "author": {
            "type": "text",
            "analyzer": "english",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "content": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "language": {
            "type": "text",
            "analyzer": "english",
            "fielddata": true
          },
          "tags": {
            "type": "text",
            "analyzer": "english"
          },
          "length": {
            "type": "long"
          },
          "likes": {
            "type": "long"
          },
          "isRelease": {
            "type": "boolean"
          },
          "releaseDate": {
            "type": "date"
          }
        }
      }
  }
}

我們預先導入一批數據進去:

POST /music/children/_bulk
{ "index": { "_id": 1 }}
{ "id" : "34116101-7fa2-5630-a1a4-1735e19d2834", "author_first_name":"Peter", "author_last_name":"Gymbo", "author" : "Peter Gymbo", "name": "gymbo", "content":"I hava a friend who loves smile, gymbo is his name", "language":"english", "tags":["enlighten","gymbo","friend"], "length":53, "likes": 5, "isRelease":true, "releaseDate": "2019-12-20" }
{ "index": { "_id": 2 }}
{ "id" : "34117101-54cb-59a1-9b7a-82adb46fa58d", "author_first_name":"John", "author_last_name":"Smith", "author" : "John Smith", "name": "wake me, shark me", "content":"don't let me sleep too late, gonna get up brightly early in the morning", "language":"english", "tags":["wake","early","morning"], "length":55, "likes": 8,"isRelease":true, "releaseDate": "2019-12-21" }
{ "index": { "_id": 3 }}
{ "id" : "34117201-8d01-49d4-a495-69634ae67017", "author_first_name":"Jimmie", "author_last_name":"Davis", "author" : "Jimmie Davis", "name": "you are my sunshine", "content":"you are my sunshine, my only sunshine, you make me happy, when skies are gray", "language":"english", "tags":["sunshine","happy"], "length":65,"likes": 12, "isRelease":true, "releaseDate": "2019-12-22" }
{ "index": { "_id": 4 }}
{ "id" : "55fa74f7-35f3-4313-a678-18c19c918a78", "author_first_name":"Peter", "author_last_name":"Raffi", "author" : "Peter Raffi", "name": "brush your teeth", "content":"When you wake up in the morning it's a quarter to one, and you want to have a little fun You brush your teeth", "language":"english", "tags":"teeth", "length":45,"likes": 17, "isRelease":true, "releaseDate": "2019-12-22" }
{ "index": { "_id": 5 }}
{ "id" : "1740e61c-63da-474f-9058-c2ab3c4f0b0a", "author_first_name":"Jean", "author_last_name":"Ritchie", "author" : "Jean Ritchie", "name": "love somebody", "content":"love somebody, yes I do", "language":"english", "tags":"love", "length":38, "likes": 3,"isRelease":true, "releaseDate": "2019-12-22" }

精確值查找

我們根據文檔的mapping設計,可以按ID、按日期進行查找。

根據ID搜索歌曲

GET /music/children/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "id" : "34116101-7fa2-5630-a1a4-1735e19d2834"
                }
            }
        }
    }
}

注意ID建立時,類型是指定為keyword,這樣ID在索引時不會進行分詞。如果類型為text,UUID值在索引時會分詞,這樣反而查不到結果了。

按日期搜索歌曲

GET /music/children/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "releaseDate" : "2019-12-21"
                }
            }
        }
    }
}

按歌曲時長搜索

GET /music/children/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "length" : 53
                }
            }
        }
    }
}

搜索已發布的歌曲

GET /music/children/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "isRelease" : true
                }
            }
        }
    }
}

以上3個小例子可以發現:准確值搜索對keyword、日期、數字、boolean值天然支持。

組合過濾

前面的4個小例子都是單條件過濾的,實際的需求肯定會有多個條件,不過萬變不離其宗,再復雜的搜索需求,也是由一個一個的基礎條件復合而成的,我們來看幾個簡單的組合過濾的例子。

復習一下之前學過的邏輯:

  • bool 組合多個條件,可以嵌套
  • must 必須匹配
  • should 可以匹配(類似於or,多個條件在should里)
  • must_not 必須不匹配

搜索發布日期為2019-12-20,或歌曲ID為2a8f4288-c0a9-5c9b-8f99-67339b66f4c0,但發布日期不能是2019-12-21的歌曲

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {"term":{
              "releaseDate":"2019-12-20"
            }},
            {"term":{
              "id":"2a8f4288-c0a9-5c9b-8f99-67339b66f4c0"
            }}
          ],
          "must_not": {
          "term": {
            "releaseDate":"2019-12-21"
          }
        }
        }
      }
    }
  }
}

搜索歌曲ID為2a8f4288-c0a9-5c9b-8f99-67339b66f4c0,或者是歌曲ID為34116101-7fa2-5630-a1a4-1735e19d2834而且發布日期為2019-12-20的帖子

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {"term":{
              "id":"2a8f4288-c0a9-5c9b-8f99-67339b66f4c0"
            }},
            {
              "bool": {
                "must" : [
                  {
                  "term" : {
                "id":"34116101-7fa2-5630-a1a4-1735e19d2834"
                  }},
                 { "term" : {
                    "releaseDate":"2019-12-20"
                  }}
                ]
              }
            }
          ]
        }
        }
      }
    }
  }

多值搜索

使用語法terms,可以同時搜索多個值,類似mysql的in語句。

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "terms": {
          "id": [
            "34116101-7fa2-5630-a1a4-1735e19d2834",
            "99268c7e-8308-569a-a975-bbce7d3f9a8e"
          ]
        }
      }
    }
  }
}

范圍查詢

針對Long類型和date類型的數據,是支持范圍查詢的,使用gt、lt、gte、lte來完成范圍的判斷。與mysql的>、<、>=、<=以及between...and異曲同工。

搜索時長在45-60秒之間的歌曲

對Long類型的范圍查詢,直接使用范圍表達式:

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "length": {
            "gte": 45,
            "lte": 60
          }
        }
      }
    }
  }
}

日期的范圍搜索

針對日期的范圍搜索,除了直接寫日期,加上常規的范圍表達式之外,還可以使用+1d、-1d表示對指定日期的加減,如"2019-12-21||-1d"表示"2019-12-20",也可以使用now-1d表示昨天,挺有趣。

給個示例:搜索2019-12-21前一天新發布的歌曲

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "releaseDate" :{
          "gt":"2019-12-21||-1d"
        }
      }
    }
  }
}
}

Null值處理

倒排索引在建立時,是不接受空值的,這就意味着null,[],[null]這些各種形式的null值,不無法存入倒排索引的,那這樣怎么辦?

Elasticsearch提供了兩種查詢,類似於mysql的is not null和not exists。

存在查詢

exists查詢,會返回那些指定字段有值的文檔,與mysql的is not null類似。

案例中的tags字段,就是一個選填項,有些記錄可能是null值,如果我需要查詢所有的tags值的記錄,請求如下:

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "exists": {
          "field": "tags"
        }
      }
    }
  }
}

缺失查詢

缺失查詢原來是有關鍵字missing表示,效果與exists相反,語法上與mysql的is null類似,但6.x版本就已經廢棄了,我們可以改用must not + exists實現相同的效果。

還是使用tags字段為例,查詢tags為空的文檔:

GET /music/children/_search
{
  "query": {
    "bool": {
      "must_not": {
        "exists": {
          "field": "tags"
        }
      }
    }
  }
}

filter緩存

過濾器為什么效率那么高?除了本身的設計集合來達到高效過濾之外,還將查詢結果適當地緩存化。

filter執行原理

我們了解一下Elasticsearch對過濾器的簡單操作:

  1. 根據fitler條件查找匹配的文檔,獲取document list。如果有多個過濾條件且涉及多個字段,那么就會有多個document list,document list是按倒排索引來的。
  2. 根據document list構建bitset(包含0或1的數組),匹配了是1,沒匹配上為0,如[1,0,0,0]。
  3. 迭代所有的bitset,從最稀疏的開始(可以排除到大量的文檔),取數組相同位置所有值為1的記錄。
  4. 將bitset緩存在內存中,用於提高性能。

filter比query好處是會caching,下次不用查倒排索引,filter大部分情況下在query之前執行query會計算doc對搜索條件的relevance score,還會根據這個score去排序
filter簡單過濾出想要的數據,不計算relevance score,也不排序

filter緩存

緩存條件
  1. 最近的256個filter中,某個filter超過一定次數(次數不固定),就會自動緩存這個filter對應的bitset。
  2. filter針對小segment獲取的結果,可以不緩存,segment<1000條或segment大小<index總大小的 3%。原因是數據量小,重新掃描很快,太小的segment在后台會自動合並到大的segment中,緩存意義不大
緩存更新

緩存的更新非常智能,增量更新的方式,如果有document新增或修改時,會將新文檔加入bitset,而不是刪除緩存或整個重新計算。

小結

本篇前半部分使用了大量的示例,可以快速閱讀,后面介紹了filter的過濾原理及緩存處理機制,可以了解一下,謝謝。

專注Java高並發、分布式架構,更多技術干貨分享與心得,請關注公眾號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術
Java架構社區


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM