簡述ElasticSearch裏面複雜關係數據的存儲方式

在傳統的數據庫裏面,對數據關係描述無外乎三種,一對一,一對多和多對多的關係,若是有關聯關係的數據,一般咱們在建表的時候會添加主外鍵來創建數據聯繫,而後在查詢或者統計時候經過join來還原或者補全數據,最終獲得咱們須要的結果數據,那麼轉化到ElasticSearch裏面,如何或者怎樣來處理這些帶有關係的數據。數據庫

咱們都知道ElasticSearch是一個NoSQL類型的數據庫,自己是弱化了對關係的處理,由於像lucene,es,solr這樣的全文檢索框架對性能要求都是比較高的,一旦出現join這樣的操做,性能會很是差,因此在使用搜索框架時,咱們應該避免把搜索引擎當作關係型數據庫用。json

固然,現實數據確定是有關係的,那麼在es裏面是如何處理和管理這些帶有關係的數據呢?api

你們都知道,es天生對json數據支持的很是完美,只要是標準的json結構的數據,不管多麼複雜,不管是嵌套多少層,都能存儲到es裏面,進而可以查詢和分析,檢索。在這種機制上,es處理和管理關係主要有三種方式:數組

一,使用objcet和array[object]的字段類型自動存儲多層結構的json數據

這是es默認的機制,也就是咱們並無設置任何mapping,直接向es服務端插入一條複雜的json數據,也能成功插入,並能支持檢索,(能這樣操做是由於es默認用的是動態mapping,只要插入的是標準的json結構就會自動轉換,固然咱們也能控制mapping類型,es裏面有動態mapping和靜態maping,靜態mapping還分嚴格類型,弱類型,通常類型,在此再也不展開,有興趣的能夠從官網瞭解下)以下面一條數據:數據結構

{
  "name" : "Zach",
  "car" : [
    {
      "make" : "Saturn",
      "model" : "SL"
    },
    {
      "make" : "Subaru",
      "model" : "Imprezza"
    }
  ]
}

最終轉化成的存儲結構是下面這樣的:app

{
  "name" : "Zach",
  "car.make" : ["Saturn", "Subaru"]
  "car.model" : ["SL", "Imprezza"]
}

由於es的底層lucene是天生支持多值域的存儲,因此在上面看起來像數組的結構,其實在es裏面存儲的就是這個字段多值域。框架

而後檢索的時候.符號就能檢索相對應的內容。這樣的一條數據,其實已經包含了數據和關係,看起來像一對多的關係,一我的擁有多輛汽車。但實際上並不能算嚴格意義上的關係,由於lucene底層是扁平化存儲的,這樣以來多個汽車的數據實際都是存到一塊兒的混雜的,你沒辦法單獨獲取到這我的某一輛汽車的數據,由於整條數據都是一個總體,不管什麼操做整條數據都會返回。curl

二,使用nested[object]類型,存儲擁有多級關係的數據

在方案一里面,咱們指出了array存儲的數組對象,並非嚴格意義的關係,由於第二層的數據是沒有分離的,若是想要分離,就必須使用nested類型來顯式定義數據結構。只有這樣,第二層的多個汽車數據纔是獨立的互不影響,也就是說能夠單獨獲取或查詢某一輛汽車的數據。elasticsearch

一樣的json數據:ide

{
  "name" : "Zach",
  "car" : [
    {
      "make" : "Saturn",
      "model" : "SL"
    },
    {
      "make" : "Subaru",
      "model" : "Imprezza"
    }
  ]
}

在方案1裏面,最終到es裏面會存儲一條數據,在第二種類型裏面,而若是聲明瞭car類型是nested,那麼最終存儲到es的數量會顯示3,這裏解釋一下3是怎麼來的 = 1個root文檔+2個汽車文檔,nested聲明類型,每個實例都是一個新的document,因此在查詢的時候纔可以獨立進行查詢,而且性能還不錯,由於es底層會把整條數據存在同一個shard的lucene的sengment裏面,缺點是更新的代價比較大,每個子文檔的更新都要重建整個結構體的索引,因此nested適合不常常update的嵌套多級關係的場景。

nested類型的數據,須要用其指定的查詢和聚合方法才能生效,普通的es查詢只能查詢1級也就是root級的屬性,嵌套的屬性是不能查的,若是想要查,必須用嵌套查詢或者聚合才行。

嵌套應用有兩種模式:

第一種:嵌套查詢

每一個查詢都是單個文檔內生效,包括排序,

第二種:嵌套聚合或者過濾

對同一層級的全部文檔都是全局生效,包括過濾排序

三,parent/children 父子關係

parent/children 模式與nested很是相似,可是應用場景側重點有所不一樣。

在使用parent/children管理關聯關係時,es會在每一個shard的內存中維護一張關係表,在檢索時,經過has_parent和has_child過濾器來獲得關聯的數據,這種模式下父文檔與子文檔也是獨立的,查詢性能會比nested模式稍低,由於父文檔和子文檔在插入的時候會經過route使得他們都分佈在同一個shard裏面,但並不保證在同一個lucene的sengment索引段裏面,因此檢索性能稍低,除此以外,每次檢索es都須要從內存的關係表裏面獲得數據關聯的信息,也須要花費必定的時間,相比nested的優點在於,父文檔或者子文檔的更新,並不影響其餘的文檔,因此對於更新頻繁的多級關係,使用parent/children模式,最爲合適不過。

父文檔的mapping type:

{
  "mappings":{
    "person":{
      "name":{
        "type":"string"
      }
    }
  }
}

子文檔的mapping type:

{
  "homes":{
    "_parent":{
      "type" : "person"
    },
    "state" : {
      "type" : "string"
    }
  }
}

插入數據時,須要先插入父文檔:

curl -XPUT localhost:9200/test/person/zach/ -d'
{
  "name" : "Zach"
}

而後插入子文檔時,須要加上路由字段:

$ curl -XPOST localhost:9200/homes?parent=zach -d'
{
  "state" : "Ohio"
}
$ curl -XPOST localhost:9200/test/homes?parent=zach -d'
{
  "state" : "South Carolina"
}

最終,父文檔zach就關聯上了兩個子文檔,在查詢時候能夠經過parent/children特定查詢來獲取數據。

總結:

方法一:

(1)簡單,快速,性能較高

(2)對維護一對一的關係比較擅長

(3)不須要特殊的查詢

方法二:

(1)因爲底層存儲在同一個lucene的sengment裏,因此讀取和查詢性能對比方法三更快

(2)更新單個子文檔,會重建整個數據結構,因此不適合更新頻繁的嵌套場景

(3)能夠維護一對多和多對多的存儲關係

方法三:

(1)多個關係數據,存儲徹底獨立,可是存在同一個shard裏面,因此讀取和查詢性能比方法二稍低

(2)須要額外的內存,維護管理關係列表

(3)更新文檔不影響其餘的子文檔,因此適合更新頻繁的場景

(4)排序和評分操做比較麻煩,須要額外的腳本函數支持

參考文檔:

https://www.elastic.co/blog/managing-relations-inside-elasticsearch

相關文章
相關標籤/搜索