〈四〉ElasticSearch的認識:基礎原理的補充


想一想咱們漏了什麼

這篇文章已是第四篇了,前面不少都只是講了基礎的使用,沒有講到內層的原理,因此這裏就要補一下原理知識了。node

回顧

先回顧一下咱們前面學過了什麼,再想一想咱們漏了什麼。
第一篇咱們認識了ElasticSearch,大概知道了ElasticSearch的做用--搜索,也瞭解了一些倒排索引和分詞器的知識(須要補充),而後學習瞭如何搭建環境(須要補充一下關於基礎的集羣知識),而後講了一些基礎的ElasticSearch概念(從新講述,加深瞭解)。
第二篇講了索引和文檔的CRUD,講建立索引的時候,沒有講mapping,講文檔的時候沒有講文檔的數據類型和元數據,這些都須要補充。
第三篇講了文檔的搜索,主要是語法方面的問題,但相關度分數是怎麼計算出來的,咱們並無講。git




補回

因此下面將對前面漏了的基礎知識進行補充:github

  • Json文檔的數據格式?【咱們以前只會弄一個簡單的json,而不知道里面有什麼區別】
  • 集羣的基礎認識?【咱們以前說了ElasticSearch是一個分佈式的系統,但咱們以前只講瞭如何啓動,如何使用kibana操做ElasticSearch,並無講ElasticSearch的集羣式怎麼創建的】
  • 節點是怎麼提供服務的?【咱們以前只知道直接發請求,這個請求ElasticSearch是怎麼處理的,咱們並不知道】
  • 索引的mapping?【以前建立索引的時候,沒有說清楚mapping,mapping受文檔的數據類型影響,mapping影響查詢方式和分詞方式】
  • 相關度分數score是怎麼算出來的?【咱們知道score是相關度分數,但咱們並不知道這個是怎麼算出來的】
  • 分詞器的原理【第一篇講了一點,如今加深】





集羣的創建

咱們說了,ElasticSearch是分佈式的,但咱們以前只說瞭如何啓動ElasticSearch,而後使用Kibana操做ElasticSearch,並無講ElasticSearch的集羣式如何創建的,因此下面將會講一下集羣的知識。但如何管理集羣會單獨作成一篇來說。web




集羣發現機制

首先,每個ElasticSearch服務端就是一個集羣節點,當咱們啓動一個elasticsearch時,就至關於啓動了一個集羣節點。
那麼,多個節點之間如何創建聯繫呢?當咱們啓動一個節點的時候,這個節點會自動建立一個集羣,集羣的名稱默認爲elasticsearch,當咱們再次啓動一個節點的時候,它首先會嘗試尋找名稱爲elasticsearch的集羣,而後加入其中,(全部的都是先嚐試尋找,沒有再本身建立)。【集羣名稱能夠自行配置,下面講】
當節點加入到集羣中後,這個集羣就算是創建起來了。算法




配置文件

上面講了集羣名稱能夠自行配置,下面講一下怎麼配置。
ElasticSearch的配置文件是config/elasticsearch.yml,裏面有如下幾個配置項:
【左邊是配置項,右邊是值,修改配置也就是修改右邊的值】數據庫

  • 集羣名稱:cluster.name: my-application
  • 索引存儲位置:path.data: /path/to/data
  • 日誌存儲位置:path.logs: /path/to/logs
  • 綁定的IP地址:network.host: 192.168.0.1
  • 綁定的http端口:http.port: 9200
  • 是否容許使用通配符來標識索引:action.destructive_requires_name,true爲禁止。




健康狀態

當集羣創建後,咱們可使用命令來查看集羣狀態:json

  • GET /_cluster/health:以json方式顯示集羣狀態
  • GET /_cat/health?v:行列式顯示集羣狀態

    返回結果解析
  • epoch:時間戳
  • timestamp:時間
  • cluster:集羣名
  • status:狀態
    • 集羣狀態解析看下面。
  • node.total:集羣中的節點數
  • node.data:集羣中的數據節點數
  • shards:集羣中總的分片數量
  • pri:主分片數量
  • relo:副本分片數量
  • init:初始化中的分片數?【不肯定,英文是這樣的:number of initializing nodes 】
  • unassign:沒有被分配的分片的數量
  • pending_tasks:待處理的任務數
  • max_task_wait_time:最大任務等待時間
  • activeds_percent:active的分片數量




集羣狀態解析
集羣狀態受當前的primary shared和replica shared的數量影響。
當每一個索引的primary shared和replica shared都是active的時候,狀態爲green;【什麼是active?shard是位於節點上的,一個shard被分配到了運行的節點上,那麼此時就是active的,若是shard沒有分配到節點上,那麼就是inactive】
當每一個索引的primary shared都是active的,但replica shared不徹底是active的時候,狀態爲yellow;
當每一個索引的primary shared不徹底是active的時候,此時發生了數據丟失,狀態爲red。api

爲何如今是yellow?
當咱們第一次啓動的時候,kibana會默認爲咱們建立一個名爲kibana的索引,這個索引的primary shard和replica shard都爲1,因爲此時只有一個節點,因此會優先分配primary shard,而忽略replica shard(不要忘了基於備份的安全性的shard排斥考慮:主分片和副本分片不能位於同一個節點上),因此此時就符合了「每一個索引的primary shared都是active的,但replica shared不全是active的」,因此此時是yellow.
你能夠嘗試啓動多一個elasticsearch節點來調整狀態。【固然,我以爲你學到這裏了,此時replica shard的數量可能已經變了,因此這裏只啓動多一個可能已經不夠了,具體的下面「分片的管理」講】數組




補充:

  • 集羣的知識還有不少,這裏講集羣只是講了個開頭,對於深層的集羣管理並無涉及,將留到後面集羣管理篇講。




小節總結

上面講了當啓動了一個elasticsearch以後,這個節點先尋找集羣,沒有的話就本身建立一個,而後後續的其餘節點也會加入這個節點,從而自動實現了自動集羣化部署。





分片的管理

以前講了一些分片的知識,但並無具體用到,可能有些人已經忘了這個概念。這裏從新講一下。




梳理

文檔的數據是存儲到索引中的,而索引的數據是存儲在分片上的,而分片是位於節點上的。

分片shard有主分片primary shard和複製分片replica shard兩種,其中主分片是主要存儲的分片,能夠進行讀寫操做;副本分片是主分片的備份,能夠進行讀操做(不支持寫操做)。
查詢能夠在主分片或副本分片上進行查詢,這樣能夠提供查詢效率。【但數據的修改只發生在主分片上。】

分片是面向索引的,分片上的數據屬於同一個索引,在咱們建立索引的時候,能夠指定主分片和副本分片的數量,默認是5個主分片,5個副本分片。
索引的replica shard的數量能夠修改,但primary shard的數量不能夠修改。【一個Primary Shard能夠有多個Replica Shard,默認建立是1個。】

修改replica shard數量:
PUT /douban/_settings
{
    "number_of_replicas":1
}

爲了保證數據的不丟失,一般來講Replica Shard不能與其對應的Primary Shard處於同一個節點中。【由於萬一這個節點損壞了,那麼存儲在這個節點上的原數據(primary shard)和備份數據(replica shard)就所有丟失了】,可是能夠和其餘primary shard的replica shard放在一塊兒

replica shard是primary shard的副本分片,負責承載必定的讀請求,當primary shard都掛掉後,其中一個replica shard會變成primary shard來維持寫功能。




分片的均衡分配

分片是分配到節點上的,但它會默認地均衡分配。
所謂均衡分配,以狀況舉例:【priX表明主分片XX,repX表明副本分片XX】
1.索引有1個priA,1個repA,但當前只有一個節點A,那麼priA分配到A節點;repA沒有分配。優先分配主分片。
2.索引有1個priA,1個repA,當前有兩個節點A和B,那麼priA分配到A節點;repA分配到B節點(priA分配到B節點也有可能)。
3.索引當前有2個priA,4個repA,有3個節點,那麼如今每臺節點上有兩個分片(但要考慮主分片不能與本身的副本分片同在一個節點上,在下面的「 主副分片的排斥」中由一個例子)。
當後續新增節點的時候,會自動再次從新均衡分配。




主副分片的排斥

考慮到備份的安全性,不該該讓主分片和副本分片位於一個節點上,否則可能徹底丟失數據。(好比你爲了不丟失鑰匙,你給你家的門準備兩條鑰匙,你有一個錢包和一個揹包,結果你把兩條鑰匙都放到錢包中,某天你錢包丟了,因而你回不了家了。。。因此你應該把鑰匙分開放。)

對應的排斥狀況就是:
1.索引有1個priA,1個repA,但當前只有一個節點A,那麼priA分配到A節點;repA沒有分配。
2.索引有1個priA,1個repA,當前有兩個節點A和B,那麼priA分配到A節點;repA分配到B節點(priA分配到B節點也有可能)。
【請注意,主分片與副本分片不能在一塊兒,但副本分片和副本分片能存放在一塊兒】
在上面的「分片的均衡分配」沒有提到主副分片的排斥的問題,下面再舉一個例子【下述的分配結果還有多是其餘的】:




容錯性:

既然提到了備份的安全性,那麼就不得不提一下容錯性了。節點是有可能宕機的,宕機後,那麼這個節點的數據起碼會暫時性的丟失,那麼對於不一樣狀況下,最多能夠宕機多少個節點呢?下面舉例:【pri = x表明主分片數量爲X,rep是副本分片】
1.若是你只有一個節點,那麼容錯性爲0,你不能宕機,宕機不徹底意味着數據徹底丟失,但暫停服務仍是有的。
2.若是你有兩個節點,pri = 2,rep = 2,那麼此時分片分配應該是[P1R2, P2R1],此時容錯性爲一個,由於某個節點上有完整的兩個分片的數據【此處一提,假設丟失了P2R1,那麼R1R2中的R2會升級成primary shard來保持寫功能】
3.後面的本身嘗試算一下吧!要綜合均衡分片和排斥性來考慮。




數據路由

每個document會存儲到惟一的一個primary shared和其副本分片replica shard 中,咱們是怎麼根據ID來知道這個document存儲在哪一個分片的呢?
ElasticSearch會自動根據document的id值來進行計算,最終得出一個小於分片數量的數值,這個數值就是分片在ElasticSearch中的序號,最終得出應該存儲到哪一個分片上。

相似原理舉例,假設我如今有4個主分片,那麼我給它們標序號0,1,2,3。如今進來一個ID爲15的數據,我通過一系列計算以後,15入參以後假設獲得243這個數值,而後咱們使用243來對4求餘,餘數是3,因此就把這個數據放在序號爲3的分片上。
依據這個原理,存儲數據的時候就知道把數據放在哪一個分片上;讀取數據的時候也知道從哪一個分片上讀取數據。
【稍微提一下,在ElasticSearch全文搜索完成以後,此時內部獲得的是ID組成的數組,內部再會根據ID來查找數據】




對於集羣健康狀態的影響

上面說了,集羣狀態受當前的primary shared和replica shared的數量影響。

  • 當每一個索引的primary shared和replica shared都是active的時候,狀態爲green;
  • 當每一個索引的primary shared都是active的,但replica shared不徹底是active的時候,狀態爲yellow;
  • 當每一個索引的primary shared不徹底是active的時候,此時發生了數據丟失,狀態爲red。
    如今學會了「分片的均衡分配」和「主副分片的排斥」後,你應該能分析出要啓動多少個節點才能改變集羣的健康狀態。
    下面舉個例子,以變green爲例:

    當前節點中只有一個索引A,索引A當前有2個priA,4個repA,那麼此時須要兩個節點才能夠變green,
    一個的時候就不說了,此時全部副本分片都inactive;兩個節點的時候,因爲每一個主分片有兩個副本分片,此時的分配是[P1-R11-R12,P2-R21-R22]




小節總結:

本節從新講了主分片和副本分片的功能,主分片和副本分片都有存儲數據的功能,一個索引的數據平分到多個主分片上,副本分片拷貝對應主分片的數據;而後講了ElasticSearch對於分片的自動均衡分配,和主分片和副本分片不能存儲在一塊兒的問題。而後講了ElasticSearch如何根據ID來判斷數據存儲在哪一個分片上。最後講了節點上分片的分配對於集羣健康狀態的影響。





請求的處理




提供服務

  • 節點是提供服務的單位,請求是發到節點上的。
  • 節點接收到請求後,會對請求進行處理:
    • 讀請求:
      • 當接受讀請求,若是是根據ID來獲取數據的讀請求,那麼它會選擇有這個ID的數據的分片來獲取數據(能夠根據ID來判斷該分片上是否有這份數據);若是是搜索類的請求,首先會根據索引表來搜索,得出相關文檔的ID,而後根據ID從這個索引的相關分片來獲取數據(若是有2個pri,2個rep,那麼搜索的分片多是p1r二、p2r一、p1p2,r1r2,只要能完整地獲取索引的全部數據便可)。
    • 寫請求:
      • 當接收寫請求,節點會選擇根據ID來計算應該把這個數據存儲到哪一個主分片上,而後經過主分片來修改數據,主分片修改完成後,會將數據同步到副本分片上。【因爲存在一個同步過程,同步完成以前,某個分片上可能不存在剛剛插入的數據,但機率較小,由於同步是極快的(NRT)】
  • 【再次提醒,主分片有讀和寫的能力,副本分片只能夠讀,因此數據的更新都發生在主分片上】




協調節點cordination node

索引的數據是存儲在節點上的,當一個請求發到節點上的時候,可能這個節點上並無這個索引的數據,那麼這個時候就須要把請求轉發給另外一個節點了,這時候本來的節點就是一個「協調節點」。




請求分發的負載均衡

一個索引存儲在多個主分片和副本分片上,索引的數據會平均分配到每個主分片中,而後每個副本分片拷貝對應主分片的數據。
對於數據存儲,數據是存儲在分片上的,而分片位於節點上,在上面說了分片會均衡分配到每個節點上,這樣也保證了節點上的數據量是平均的。

除此以外,對於讀取某一個主分片及其副本分片上的數據的時候,會使用輪詢算法來將讀請求平均分配(大概意思就是,假設如今對於主分片1有三個副本分片,那麼總數爲4,假設分別編號一、二、三、4,那麼可能地,第一次請求交給了1,那麼第二次請求要交給2,第三次要交給3。。。以此類推,超過4則從頭開始)。




補充:

  • 除了上面的內容,還有一些與集羣比較相關的內容,好比某節點宕機後會發生什麼。這些會留到集羣管理篇再講。




小節總結

本節簡單說了一下讀寫操做的流程,ID類的讀請求直接根據ID來查找文檔,搜索類的讀請求先搜索索引表,再根據ID來查找文檔;寫數據請求會根據ID來計算應該把這個數據存儲到哪一個主分片上,而後經過主分片來修改數據;最後講了一下對於請求分發的負載均衡,一方面經過數據量的平均分配來均衡,一方面使用輪詢算法來下降單個分片的處理壓力。





文檔的元數據




你在查詢文檔的時候,你可能看到返回結果中有以下的內容:

上面的幾個前綴有_的就是元數據。

  • _index:表明當前document存放到哪一個index中。
  • _type:表明當前document存放在index的哪一個type中。
  • _id:表明document的惟一標識,與index和type一塊兒,能夠惟一標識和定位一個document。【在前面咱們都是手動指定的,其實能夠不手動指定,那樣會隨機產生要給惟一的字符串做爲ID】
  • _version:是當前document的版本,這個版本用於標識這個document的「更新次數」(新建、刪除、修改都會增長版本)
  • _source:返回的結果是查詢出來的當前存儲在索引中的完整的document數據。以前在搜索篇中講到了,咱們可使用_source來指定返回docuemnt的哪些字段。

元數據與具體的文檔數據無關,每個文檔都有這些數據。





文檔的數據類型




對於web中的json,數據格式主要有字符串數值數組{}這幾種。
好比:

{
    "name": "neo",
    "age": 18,
    "tool": ["clothes", "computer", "gun"],
    "gf": {
        "feature": "beauty"
    }
}

ElasticSearch並非這樣的,由於它要考慮分詞,就算是字符串,它也要考慮裏面的數據是否是日期類型的,日期類型一般不會分詞。




數據類型

ElasticSearch主要有如下這幾種數據類型:

  • 字符類:
    • text:是存儲字符串的類型,在elasticsearch中存儲會分詞的字符串數據通常用text
    • keyword:也是存儲字符串的類型,在elasticsearch中用於存儲不會分詞的、結構化的字符串數據
    • string:string在5.x以前可使用,如今已被text和keyword取代。
  • 整數類型:
    • integer
    • long
    • short
    • byte
  • 浮點數類型:
    • double
    • float
  • 日期類型:date
  • 布爾類型:boolean
  • 數組類型:array
  • 對象類型:object
    【除了上述的類型以外,還有一些例如half_float、scaled_float、binary、ip等等類型,因爲不是很是基礎的內容,因此這裏不講,有興趣的能夠自查。】




補充:

  • 數據類型自己是沒有多少重要的知識點,重要的是與數據類型緊密相關的mapping。由於mapping存儲的就是索引的結構信息,下面小節將講述mapping。





mapping

mapping負責維護index中文檔的結構,包括文檔的字段名、字段數據類型、分詞器、字段是否進行分詞等。這些屬性會對咱們的搜索形成影響。




dynamic mapping

在前面,其實咱們都沒有定義過mapping,直接就是插入數據了。其實這時候ElasticSearch會幫咱們自動定義mapping,這個mapping會依據文檔的數據來自動生成。
此時,若是數據是字符串的,會認爲是text類型,而且默認進行分詞;若是數據是日期類型類(字符串裏面的數據是日期格式的),那麼這個字段會認爲是date類型的,是不分詞的;若是數據是整數,那麼這個字段會認爲是long類型的數據;若是數據是小數,那麼這個字段會認爲是float類型的;若是是true或者false,會認爲是boolean類型的。




舉例:

咱們插入如下數據【若是你用了這個索引,那麼能夠自定義一個索引,避免我隨意建立的數據污染了你的測試數據】:

PUT /test/person/1
{
  "name":"suke",
  "age":18,
  "tall":178.5,
  "birthdate":"2018-01-01"
}

而後查看索引,其中mapping定義了咱們剛剛定義的字段的信息:

GET /test




建立mapping

【以前在第二篇中有提到,能夠經過查看索引來查看mapping】
語法:

【[]表明裏面的內容是可選的,非必須的】
PUT /index/
{
    "settings": {
        "index": {
            "number_of_shards": 3,
            "number_of_replicas": 1
        }
    },
    "mappings": {
        "type名": {
            "properties": {
                "字段名": {
                    "type": "數據類型",
                    // 是否索引,不索引則不將這個字段列入索引表,沒法對這個字段進行搜索
                    ["index": "是否索引,值爲true或者fasle",]
                    // 是否新舊keyword保留原始數據
                    ["fields": {
                        "keyword": {
                            "type": "keyword",【這個是保留原始數據採用的數據類型,也可使用text,通常用keyword】
                            "ignore_above": 256【超過多少字符就忽略,不創建keyword】
                        }
                    },]
                    // (選擇什麼分詞器來對這個字段進行分詞)
                    ["analyzer": "分詞器名稱"
                    ,]
                }
            }
        }
    }
}

【內容補充:在舊版中,index的值有not_analyzed這樣的值,如今只有true或false,它的原意是不分詞,如今是不索引,在舊版中,不分詞則會保留原始數據,在新版中使用keyword來保留原始數據】
舉例:

1.建立一個mapping,只定義數據類型:【定義了每一個字段的數據類型後,插入數據的時候並無說要嚴格遵循,數據類型的做用是提早聲明字段的數據類型,例如在以前第二篇說type的時候,就提到了多個type中的字段其實都會彙總到mapping中,若是不提早聲明,那麼可能致使由於使用dynamic mapping而使得數據類型定義出錯。好比在type1中birthdate字段】
> 第二篇中這樣說的:當咱們直接插入document的時候,若是不指定document的數據結構,那麼ElastciSearch會基於dynamic mapping來自動幫咱們聲明每個字段的數據類型,好比"content":"hello world!"會被聲明成字符串類型,"post_date":"2017-07-07"會被認爲是date類型。若是咱們首先在一個type中聲明瞭content爲字符串類型,再在另一個type中聲明成日期類型,這會報錯,由於對於index來講,這個content已經被聲明成字符串類型了。
PUT /test0101
{
  "settings": {
    "index":{
      "number_of_shards":3,
      "number_of_replicas":1
    }
  },
  "mappings": {
    "person":{
      "properties": {
        "name":{
          "type": "text"
        },
        "age":{
          "type": "long"
        },
        "birthdate":{
          "type":"date"
        }
      }
    }
  }
}

2.設置某個字段不進行索引【設置後,你能夠嘗試對這個字段搜索,會報錯!】
PUT /test0102
{
  "settings": {
    "index":{
      "number_of_shards":3,
            "number_of_replicas":1
    }
  },
  "mappings": {
    "person":{
      "properties": {
        "name":{
          "type": "text",
          "index": "false"
        },
        "age":{
          "type": "long"
        },
        "birthdate":{
          "type":"date"
        }
      }
    }
  }
}
測試:
PUT /test0102/person/1
{
  "name":"Paul Smith",
  "age":18,
  "birthdate":"2018-01-01"
}
GET /test0102/person/_search
{
  "query": {
    "match": {
      "name": "Paul"
    }
  }
}

3.給某個字段增長keyword

PUT /test0103
{
  "settings": {
    "index":{
      "number_of_shards":3,
            "number_of_replicas":1
    }
  },
  "mappings": {
    "person":{
      "properties": {
        "name":{
          "type": "text",
          "index": "false",
          "fields": {
            "keyword": {
                            "type": "keyword",
                            "ignore_above": 256
                        }
          }
        },
        "age":{
          "type": "long"
        },
        "birthdate":{
          "type":"date"
        }
      }
    }
  }
}
測試:
PUT /test0103/person/1
{
  "name":"Paul Smith",
  "age":18,
  "birthdate":"2018-01-01"
}
【注意這裏是不能使用name來搜索的,要使用name.keyword來搜索,並且keyword是對原始數據進行**不分詞**的搜索的,因此你搜單個詞是找不到的。】
GET /test0103/person/_search
{
  "query": {
    "match": {
      "name.keyword": "Paul Smith"
    }
  }
}




修改mapping

mapping只能新增字段,不能修改原有的字段。

// 給索引test0103的類型person新增一個字段height
PUT /test0103/_mapping/person
{
  "properties": {
    "height":{
      "type": "float"
    }
  }
}




查看mapping

1.以前說過了,能夠經過查看索引來查看mapping:GET /index,例如GET /test0103/_mapping
2.經過GET /index/_mapping,例子:GET /test0103/_mapping
3.你也能夠附加type來查看指定type包裹的mapping。GET /test0103/_mapping/person




keyword

上面定義mapping的時候有定義一個keyword,這是什麼?有什麼用?
ElasticSearch對於一些類型的字段,例如text類型的字段,默認是會進行分詞的。但若是咱們並不想分詞呢?一些數據咱們想要很是精確地查找,而且只找到咱們搜索的數據的時候,這個字段是不該該分詞的。那麼咱們可使用keyword來存儲完整的原有的數據,keyword會做爲一個索引詞,而後咱們針對字段.keyword來搜索。【例子上面已經舉例了】

理論上,這個不分詞的字段的數據應該是比較簡短的,太長的話可能就沒有必要不分詞了。因此在定義keyword的時候還能夠提供"ignore_above": 數值來限制超過多少個字符就不使用原有數據建立keyword.
如今版本中,對於默認分詞的字段,如今會默認附加一個keyword。




mapping對分詞的影響

1.數據類型的影響:

以date和text類型存儲的「日期」類型的數據的分詞差別化爲例:
date和string這二者都是使用雙引號包裹的,與其餘數值型的不分詞的數據類型不同。因此以他們兩個爲例。

PUT /test0104
{
  "settings": {
    "index":{
      "number_of_shards":3,
            "number_of_replicas":1
    }
  },
  "mappings": {
    "test":{
      "properties": {
        "post_date":{
          "type": "text"
        },
        "birthdate":{
          "type":"date"
        }
      }
    }
  }
}
PUT /test0104/test/1
{
  "post_date":"2019-09-30",
  "birthdate":"2018-08-29"
}
PUT /test0104/test/2
{
  "post_date":"2018-09-30",
  "birthdate":"2017-08-29"
}
PUT /test0104/test/3
{
  "post_date":"2017-09-30",
  "birthdate":"2016-08-29"
}

//測試1:結果是ID:1
GET /test0104/test/_search
{
  "query": {
    "match": {
      "post_date": "2019"
    }
  }
}
// 測試2:結果是ID=1,2,3的文檔,理論上text存儲的日期格式的沒有分詞區別
GET /test0104/test/_search
{
  "query": {
    "match": {
      "post_date": "30"
    }
  }
}

//測試3:結果無,【單搜索08或2017,2018,2016也是無,】
GET /test0104/test/_search
{
  "query": {
    "match": {
      "birthdate": "29"
    }
  }
}
//測試4, 結果是ID=2的文檔
GET /test0104/test/_search
{
  "query": {
    "match": {
      "birthdate": "2017-08-29"
    }
  }
}

2.不索引的影響:
不索引的時候不會進行分詞,甚至不能用於搜索。【低版本對於properties中的index設置不同,5.x如下是不分詞,5.x以上是不索引,不索引就不能夠用於搜索】
3.keyword的影響:
keyword適用於不分詞的搜索的狀況,在keyword中的數據不會分詞。
4.其餘:
mapping還能夠設置分詞器來使用不一樣的分詞器來分詞。

// 你可使用格式相似以下的代碼來測試mapping中某個字段的分詞結果,若是是不容許分詞的,則會報Analysis requests are only supported on tokenized fields錯誤。
GET /test0104/_analyze
{
  "field": "post_date",
  "text": "2017-09-30" 
}




補充:

  • dynamic mapping策略是關於自動建立mapping的策略,定義了趕上某某數據的時候把它看成什麼類型,好比能夠定義存入"2017-09-30"的時候認爲是text而不是date,這些內容可能會留到後面再講,也有可能後面再補充到這裏。
  • 複合數據類型比較特殊,可能會留到後面再講,也有可能後面再補充到這裏。




小節總結:

本節介紹了什麼是mapping,mapping負責管理索引的數據結構和字段的分詞等一些配置。在直接存儲數據的時候,會dynamic mapping;而後介紹瞭如何建立mapping,如何修改mapping,如何查看mapping;而後介紹了keyword這個保留原數據的一個特殊的字段。





相關度分數

相關度分數的具體算法咱們其實並不須要關心。但可能仍是須要大概瞭解一下計算的方式。

爲何說不須要關心呢?由於實際上相關度是由索引詞直接決定的,分析好哪些是用於搜索的詞就能夠大體分析出相關度排序了。
固然,這只是大概的,由於內部可能由於索引詞的重複性問題會下降某個詞的score,但差距通常不會太大。

  • 在咱們進行搜索的時候,你能夠看到一個score,這個就是相關度分數,在默認排序中相關度分數最高的會被排在最前面,這個分數是ElasticSearch根據你搜索的內容,使用內部算法計算出的一個數值。
  • 內部算法主要是指TF算法和IDF算法。




TF算法

TF算法,全稱Term frequency,索引詞頻率算法。
意義就像它的名字,會根據索引詞的頻率來計算,索引詞出現的次數越多,分數越高
例子以下:

搜索hello
有兩份文檔:A文檔:hello world!,B文檔:hello hello hello
結果是B文檔的score大於A文檔。

搜索hello world
有兩份文檔:A文檔:hello world!,B文檔:hello,are you ok?
結果是A文檔的score大於B文檔。

要根據索引詞來綜合考慮。




IDF算法

IDF算法全稱Inverse Document Frequency,逆文本頻率。
搜索文本的詞在整個索引的全部文檔中出現的次數越多,這個詞所佔的score的比重就越低。

例子以下:

搜索hello world,其中索引中hello出現次數1000次,world出現100次。
有三份文檔:A文檔hello,are you ok?,B文檔The world is interesting!,C文檔hello world!
結果是:C>B>A
因爲hello出現頻率高,因此單個hello獲得的score比不上world。




Field-length norm算法。

  • 這個算法elasticsearch並無單獨列出來,但也有生效
  • field越長,相關度就越低,數據長度會拉低相關度。

例子以下:

搜索hello world!
有兩份文檔:A文檔hello world!,B文檔hello world,I'm xxx!
結果是:A>B




三種算法的綜合:

下面屬於理論分析,並不真實這樣計算
TF算法針對在Field中,索引詞出現的頻率;
IDF算法針對在整個索引中的索引詞出現的頻率;
Field-length norm算法針對Field的長度。
那麼能夠這樣分析,因爲Field-length norm算法並不直接針對score,因此它是最後起做用的,它理論上相似於一個除數。而TF和IDF是平等的,IDF計算出每個索引詞的score量,TF來計算整個文檔中索引詞的score的加和。
也就是以下的計算:
1.IDF:計算索引詞的單位score,好比hello=0.1,world=0.2,
2.TF:計算整個文檔的sum(score),hello world!I'm xxx.獲得0.1+0.2=0.3
3.Field-length norm:將sum(score)/對應Field的長度,得出的結果就是score。




score計算API

elasticsearch提供了測試計算score的API,語法相似以下:

GET /index/type/_search?explain=true
{
  "query": {
    "match": {
      "搜索字段": "搜索值"
    }
  }
}
例子:
GET /douban/book/_search?explain=true
{
  "query": {
    "match": {
      "book_name": "Story"
    }
  }
}

返回結果數據解析:
_explanation是score的緣由,_explanation的格式是先列出分數,而後detail部分解釋分數,內層也是。




小節總結

這節介紹了相關度分數的計算方式,能夠大體瞭解一下score是怎麼得出來得,TF是索引詞頻率算法,是對索引詞分數的加和;IDF是基於整個索引對每一個索引詞的計算分數;Field-length用於下降數據長的文檔的相關度分數。





分詞器




什麼是分詞器?
分詞器負責對document進行處理,以提升搜索效率。
分詞器一般由分解器tokenizer和詞元過濾器token filter組成。

分詞器對數據的分詞處理:爲了提升索引的效率,ElasticSearch會數據進行處理,處理方式主要有字符過濾、詞轉換、詞拆分
字符過濾:過濾一些特殊字符,例如&||、html標籤,由於這些詞一般搜索意義不大。

詞轉換:把一些意義相同的詞統一轉成一個詞,(同詞義轉換)好比mom,mother統一轉成mom;(大小寫轉換)he,He統一歸爲He;還處理一些詞意義不大的詞(停用詞清除),好比英文的「the」,「to」,這些詞使用頻率很高,但沒有具體意義。

詞拆分:進行數據的拆分,拆分紅詞,好比把good morning,mom拆分紅good,morining,mom。另外,詞拆分並不徹底是按照數據的最小單位分解的,某一些分詞器會把一些詞進行組合,由於一些詞的組合起來纔有索引的意義,好比中文的一些詞一般要組合起來纔有意義,好比「大」和「家」要組成「你們」纔有比較具體的意義,這是爲了確保索引詞的最小單位是有意義的(好比英文mom的最小單位是m,o,m,內部的分詞器要可以區分出mom整個是有意義的才能夠確保是採用mom做爲索引,而不是採用m和o,也正是由於這個問題,因此英文分詞器不能用於中文分詞器)。【分詞器有不少個,默認的分詞器是不能適當對中文數據分詞的,它只能把一個個數據按最小的單位拆分,由於英文分詞器不能分清楚怎麼把詞拆分纔有意義,因爲配置分詞器是一個較爲靠後的知識點,因此前期將以英文數據爲測試數據。




常見的內置分詞器:

每個分詞器的都有一些本身的規則,好比一些分詞器會把a當成停用詞,有些則不會;有些分詞器會去掉標點符號,有些則不會;有些分詞器能識別英文詞,但識別不了中文詞。要按須要來選擇分詞器。

  • 標準分詞器standard analyzer:最基礎的分詞器。能夠把詞進行拆分、
  • 簡單分詞器simple analyzer:分詞、作字符過濾、不作詞轉換。
  • 空格分詞器whitespace analyzer:僅僅根據空格來劃分詞,不會作字符過濾也不會作詞轉換。
  • 英文分詞器english:根據英文標準來分詞,進行字符過濾,進行詞轉換(去除英文中的停用詞(a,the),同義詞轉換等)
  • 小寫lowercase tokenizer:把英文全轉成小寫再分詞
  • 字母分詞器letter tokenizer:根據字母來分詞




分詞示例

可使用下面的格式的命令來測試不一樣分詞器的分詞效果:

GET /_analyze
{
  "analyzer": "分詞器",
  "text": "要測試的文本"
}
// 舉例:
GET /_analyze
{
  "analyzer": "simple",
  "text": "I am a little boy,I have 5$.dogs,long-distance,<html></html>"
}

示例:
原句:I am a little boy,I have 5$.dogs,long-distance,<html></html>

  • 標準分詞器standard analyzer:i,am,a,little,boy,i,have,5,dogs,long,distance,html,html 【對於拆分紅同一個詞的,會造成同一個索引詞】【這個分詞器,作了小寫、去掉特殊字符等操做】
  • 簡單分詞器simple analyzer:i,am,a,little,boy,i,have,dogs,long,distance,html,html 【這個分詞器作了小寫、去掉特殊字符和數字等操做】
  • 空格分詞器whitespace analyzer:I,am,a,little,boy,I,have,5$.dogs,long-distance,<html></html>【根據空格分詞】
  • 英文分詞器英文分詞器english::i,am,littl,boi,i,have,5,dog,long,distanc,html,html【語言分詞器會比較特殊,會作一部分的形式轉換,有些時候會盲目地切分單詞,好比er和e這些常見後綴,會被切掉,因此little變成了littl。】

    可能有人不是很懂分詞器的做用,這裏再次重談一下:
    若是使用的分詞器是standard,那你輸入的loves會認爲是loves,loved會認爲是loved;
    而english會把loves認爲是love,loved認爲是love.
    因此在english分詞器中,loves和loved的搜索用的是love的索引詞搜索,而standard中用的是loves和loved的索引詞,從搜索效率來講,english的搜索纔是咱們想要的,它比較靈活。
    並且有些分詞器會幫你把詞組合起來,好比「中國人」這個詞不應被拆分紅「中」「國」「人」】




修改分詞器

修改分詞器就是修改mapping中的analyzer,mapping中某個字段一但建立就不能針對這個字段修改,因此只能刪除再修改或新增。

PUT /test0106
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "test":{
      "properties": {
        "content":{
          "type": "text",
          "analyzer": "standard"
        },
        "detail":{
          "type": "text",
          "analyzer": "english"
        }
      }
    }
  }
}




中文分詞器

在前面,咱們都是使用英文做爲字段的數據,由於ElasticSearch默認狀況下沒法很好地對中文進行分詞,它默認只能一個個字地分詞,因此它會把「中國人」這個詞拆分紅「中」「國」「人」。
因此咱們須要使用「插件」來對ElasticSearch來進行擴展。
咱們經常使用的中文分詞器就是IK分詞器。

如何安裝

1.去github上下載IK的zip文件,下載的插件版本要與當前使用的elasticsearch版本一致,好比你是elasticsearch6.2.3,就下載6.2.3的【對於沒有本身版本的,這時候可能須要下載新的elasticsearch,有些人說能夠經過修改pom.xml來強行適配,但仍是存在一些問題的。】。github-IK
2.把zip文件中的elasticsearch文件夾解壓到elasticsearch安裝目錄\plugins中,而且重命名文件夾爲ik-analyzer
3.而後重啓elasticsearch




使用

IK分詞器提供了兩種分詞器ik_max_word和ik_smart

  • ik_max_word: 會將文本作最細粒度的拆分,好比北京天安門廣場會被拆分爲北京天安門廣場天安門天安廣場,會嘗試各類在IK中可能的組合;
  • ik_smart: 會作最粗粒度的拆分,好比會將北京天安門廣場拆分爲北京天安門廣場
  • 通常都會使用ik_max_word,只有在某些確實只須要最粗粒度的時候才使用ik_smart。
// 測試一:比較標準分詞器和IK分詞器的區別:
// 結果:標準分詞器只會一個個字地拆分
GET /_analyze
{
  "analyzer": "standard",
  "text": "北京天安門廣場"
}

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "北京天安門廣場"
}
GET /_analyze
{
  "analyzer": "ik_smart",
  "text": "北京天安門廣場"
}
// 測試二:給某個字段的mapping配置分詞器爲ik_max_word,插入測試數據,並進行搜索
PUT /test0107 
{
  "mappings": {
    "test": {
      "properties": {
        "content": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}

PUT /test0107/test/1
{
  "content":"北京天安門廣場"
}

PUT /test0107/test/2
{
  "content":"上海世博會"
}

PUT /test0107/test/3
{
  "content":"北京鳥巢"
}

PUT /test0107/test/4
{
  "content":"上海外灘"
}

// 開始搜索:
GET /test0107/test/_search
{
  "query": {
    "match": {
      "content": "北京"
    }
  }
}
GET /test0107/test/_search
{
  "query": {
    "match": {
      "content": "鳥巢"
    }
  }
}
GET /test0107/test/_search
{
  "query": {
    "match": {
      "content": "世博會"
    }
  }
}
// 這條搜索是搜索不出結果的,由於分詞的結果沒有「京」字
GET /test0107/test/_search
{
  "query": {
    "match": {
      "content": "北"
    }
  }
}




補充:

  • 上面說了「分詞器一般由分解器tokenizer和詞元過濾器token filter組成。」,因此其實咱們也能夠本身經過組合分解器和過濾器來成造成一個分詞器。這裏有興趣自查的。【有可能後面某天會補充】
  • 上面提到了中文分詞器,其實這已經涉及到了「插件」的內容了,這個內容會後面的篇章再講。




小節總結:

本節從新解釋了分詞器的做用(字符過濾,詞轉換,詞拆分),介紹了幾種常見的內置的分詞器(standard,english這些),並舉了一些分詞的例子,以及如何修改字段的分詞器(mapping的analyzer),最後還介紹了怎麼安裝支持中文分詞的ik分詞器。





文檔的ID

文檔的ID實際上是能夠不指定的,不指定的時候會隨機生成惟一的一串字符串,

手動指定和不指定的區別:
手動指定一般適用於一些將數據庫的數據轉存到ElasticSearch的場景,由於這時候ID有特殊意義,讓數據庫的數據與ElasticSearch的數據關聯起來,(有時候可能須要同時查數據庫和ElasticSearch,那麼能夠直接根據數據庫中的ID來查ElasticSearch中的數據)。
不指定的時候(這時候一般ID是沒有特殊意義的),ElasticSearch會自動地幫咱們生成一個ID值,自動生成的ID長度爲20個字符,這個ID它能確保是不會重複的,就算是在分佈式併發狀況下也不會發生衝突。【因爲此時的ID是隨機字符串,因此根據ID來查詢數據會比較麻煩】


這裏講的仍是比較偏應用方面的基礎知識,後面可能還會補充偏底層的一些基礎知識,好比底層寫入流程和NRT這些。

相關文章
相關標籤/搜索