天天看點

Dynamic mapping — Elastic Stack 實戰手冊1 處由于我們對整個 mapping 設定了dynamic:false,是以school屬性沒有自動建立。

Dynamic mapping — Elastic Stack 實戰手冊1 處由于我們對整個 mapping 設定了dynamic:false,是以school屬性沒有自動建立。
https://developer.aliyun.com/topic/download?id=1295 · 更多精彩内容,請下載下傳閱讀全本《Elastic Stack實戰手冊》 https://developer.aliyun.com/topic/download?id=1295 https://developer.aliyun.com/topic/es100 · 加入創作人行列,一起交流碰撞,參與技術圈年度盛事吧 https://developer.aliyun.com/topic/es100

創作人:駱潇龍

通常來說,搜尋資料一般需要經過 3 個步驟:

  • 定義資料(建表建索引)
  • 錄入資料
  • 搜尋資料

在現實使用中,定義資料往往是比較繁瑣,并且有大量的重複操作。

Elasticsearch 本着讓使用者使用更友善快捷的原則,針對這個問題做了很多工作,使定義資料的方式更加抽象靈活,多個雷同的字段可使用 1 個配置完成。

比較有代表性的2個功能分别是:

  • 索引模闆(index template):可以根據規則自動建立索引。
  • 動态映射(dynamic mapping):自動将新字段添加到映射中。

本小節我們着重介紹動态映射(dynamic mapping)

根據官方的定義動态映射可以自動檢測和添加新字段(field)到映射(mapping)中。動态映射可以通過基礎屬性自動發現(Dynamic field mappings)以及複雜屬性動态生成(Dynamic templates)2個方式實作此功能。

動态字段映射(Dynamic field mappings)

在預設情況下,當索引一個文檔時有字段是在映射中沒有配置的,那麼 Elasticsearch 将會根據該屬性的類型,自動将其增加到映射中。該功能可以通過配置

dynamic

來控制打開。

該配置可以接受以下 3 種選擇:

  1. ture:預設配置,新字段将會自動加入映射中,并自動推斷字段的類型。
  2. false:新字段不會增加到映射中,是以不能被搜尋,但是内容依然會儲存在

    _source

    中。如無特殊需要建議都配置為 false,這樣可以避免寫入流程經過 master 節點,進而提高性能。
  3. strict:索引文檔時如果發現有新字段則報錯,整個文檔都不會被索引。

該配置可以在建立 mapping 時在根層配置,表示對所有屬性适用。也可以每個内嵌對象(inner object)中配置,表示僅對該對象适用。

示例如下

# 建立 test-dynamic-mapping
PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic": false, # 1
    "properties": {
      "person":{
        "dynamic": true, # 2
        "properties": {
          "name":{
            "type":"keyword"
          }
        }
      },
      "company":{
        "dynamic": "strict",  # 3
        "properties": {
          "company_id":{
            "type":"keyword"
          }
        }
      }
    }
  }
}           
  1. 在 #1 處的配置索引

    test-dynamic-mapping

    整體是不自動增加字段的
  2. 在 #2 處對于内嵌對象

    person

    我們設定它可以自動發現字段
  3. 在 #3 處對于内嵌對象

    company

    我們設定它發現新字段會報錯
# 插入文檔
PUT test-dynamic-mapping/_doc/1
{
  "school":"test school", # 1
  "person":{
    "name":"tom",
    "age":"12" # 2
  },
  "company":{
    "company_id":"c001"
  }
}           
  1. 傳入文檔的根層有個未定義的

    school

    字段
  2. 在 person 對象中增加 age 字段
# 再次檢視索引mapping
GET test-dynamic-mapping
{
  "test-dynamic-mapping" : {
    "mappings" : {
      "dynamic" : "false",
      "properties" : { # 1
        "company" : {
          "dynamic" : "strict",
          "properties" : {
            "company_id" : {
              "type" : "keyword"
            }
          }
        },
        "person" : {
          "dynamic" : "true",
          "properties" : {
            "name" : {
              "type" : "keyword"
            },
            "age" : { # 2
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
    ………………
  }
}           
  1. 1 處由于我們對整個 mapping 設定了

    dynamic:false

    ,是以

    school

    屬性沒有自動建立。

  2. 由于内嵌對象

    person

    dynamic:true

    ,是以自動增加了

    sex

    屬性,該屬性派生出 2 個字段索引

    person.age

    其字段類型是

    text

    以及

    person.age.keyword

    keyword

# 再次檢視索引 mapping
GET test-dynamic-mapping
{
  "test-dynamic-mapping" : {
    "mappings" : {
      "dynamic" : "false",
      "properties" : { # 1
        "company" : {
          "dynamic" : "strict",
          "properties" : {
            "company_id" : {
              "type" : "keyword"
            }
          }
        },
        "person" : {
          "dynamic" : "true",
          "properties" : {
            "name" : {
              "type" : "keyword"
            },
            "age" : { # 2
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
    ………………
  }
}           
  1. 索引新文檔時增加

    company.company_name

  2. 由于

    company

    對象

    dynamic:strict

    ,是以建立文檔的請求傳回了 1 個

    strict_dynamic_mapping_exception

    錯誤

對于JSON 中的字段 遵循以下映射方式發現新屬性。

JSON data type Elasticsearch data type
null 不添加
true 或 false boolean 類型
帶小數的數字,如1.1 float 類型
整數,如 3 long 類型
數組 ES 不特殊處理數組類型
字元串 如果配置了自動識别且通過則可被識别為 date、float、long 類型如果未配置則會識别為 text 類型且增加 keyword 子屬性使用 keyword 類型

對于 JSON 中的字元串字段,我們可以通過配置

date_detection: true

numeric_detection: true

嘗試将它們轉化成數值類型或時間類型,

date_detection

預設為 true,

numeric_detection

預設為 false。

在識别數字時,所有整型字元串會識别成

long

型,帶小數的字元串會識别成

float

類型。預設情況下

yyyy/MM/dd HH:mm:ss

yyyy/MM/dd

epoch_millis

格式的字元串會識别成

date

類型。

# 建立測試索引
PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic": true,
    "numeric_detection": true, # 1
    "properties": {
      "field1":{
        "type": "keyword"
      }
    }
  }
}
# 插入資料
PUT test-dynamic-mapping/_doc/1
{
  "date":"2021/05/01", # 2
  "float":"1.1", # 3
  "long":"1" # 4
}
# 檢視 mapping 變化
GET test-dynamic-mapping
{
  "test-dynamic-mapping" : {
     "mappings" : {
      "dynamic" : "true",
      "numeric_detection" : true,
      "properties" : {
        "date" : {    # 5
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
        },
        "field1" : {
          "type" : "keyword"
        },
        "float" : {   #6
          "type" : "float"
        },
        "long" : {   # 7
          "type" : "long"
        }
      }
    }
  }
}           
  1. 在 #1 處在建立索引時設定字元串可以自動識别為數值類型
  2. 在 #2 處 date 字段條是符合時間格式的字元串
  3. 在 #3 處 float 字段是符合小數格式的字元串
  4. 在 #4 處 long 字段是符合整型格式的字元串
  5. 在 #5 處 date 字段加入 mapping 并被自動識别成了 date 類型
  6. 在 #6 處 float 字段加入 mapping 并被自動識别成了 float 類型
  7. 在 #7 處 long 字段加入 mapping 并被自動識别成了 long 類型

Elasticsearch 識别日期字元串的格式,是可以通過

dynamic_date_formats

來配置。該字段支援使用

y

m

d

h

等字元自定義格式,具體方式與 Java 中

DateTimeFormatter

對象實作的規則相同。

同時還能配置大量國際标準的時間格式比如:epoch_millis、basic_date、basic_date_time、strict_date_optional_time_nanos 等,

所有可選項可以參照官方文檔: https://www.elastic.co/guide/en/elasticsearch/reference/7.10/mapping-date-format.html
# 建立測試索引
PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic": true,
    "dynamic_date_formats": ["MM/dd/yyyy"]  # 識别MM/dd/yyyy格式的時間
    "properties": {
      "field1":{
        "type": "keyword"
      }
    }
  }
}
# 插入資料
PUT test-dynamic-mapping/_doc/1
{
  "date":"09/25/2015",
  "date1":"2015/09/25"
}
# 檢視mapping變化
{
  "test-dynamic-mapping" : {
    "mappings" : {
      "dynamic" : "true",
      "dynamic_date_formats" : [
        "MM/dd/yyyy"
      ],
      "properties" : {
        "date" : {          # 符合MM/dd/yyyy格式的字元串識别為了date類型
          "type" : "date",
          "format" : "MM/dd/yyyy"
        },
        "date1" : {        # 符合預設yyyy/MM/dd 格式的字元串識未正确識别
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "field1" : {
          "type" : "keyword"
        }
      }
    }
  }
}           

動态模闆(Dynamic templates)

Elasticsearch 的動态字段映射(Dynamic field mappings)雖然使用簡單,但往往不滿足現實的業務場景,比如對于整型字段,往往用不着 long 類型,使用 integer 類型就足夠了;對于字元串類型的字段,我們希望細化分詞方式,而不是使用預設分詞,以及對于不同字段采用不同的分詞方式等。這時可以使用動态模闆(Dynamic templates)功能來實作上述需求。

動态模闆允許你在建立 mapping 時,設定自定義規則。當新字段滿足該規則時,則按照預先的配置來建立字段。

Elasticsearch 允許使用者通過3個角度來定義規則:新字段的資料類型,屬性名和路徑。建立 mapping 時可以通過

dynamic_templates

字段配置多個動态模闆。

模闆的整體結構如下:

{
  "mappings":{
    "dynamic_templates": [
      {
        "templateName":{   #1
          ……比對規則……        # 2
          "mapping": { ... } #3      
        }
      }
     ]
  }
}           
  1. 在 #1 處定義了動态模闆的名稱,每個動态模闆都需要配置名字,本例中配置的模闆名稱為

    templateName

  2. 在 #2 處可以使用

    match_mapping_type

    match

    unmatch

    match_pattern

    path_match

    path_unmatch

    來配置該模闆的比對規則,規則可以是多個,規則之間是

    的關系
  3. 在 #3 處配置的是符合該規則的字段使用的 mapping 配置,此處與正常建立字段相同,主要需要配置

    type

    analyzer

比對規則

下面我們通過幾個例子來說明一下比對規則中的各個關鍵字如何使用。

match_mapping_type

match_mapping_type

用于按照資料類型比對,當使用者想對 JSON 中具有某種資料類型的字段設定做特殊配置時,可以用此種比對方式。該字段可配置的資料類型有如下幾種:

  • boolean,比對值是 true 或 false 的字段。
  • date,當字元串開啟了時間類型識别且字元串符合預設日期格式則會被比對
  • double,比對含有小數的字段
  • long,比對值是整型的字段
  • object,比對值是對象的字段
  • string,比對值是字元串的字段
  • *,表示所有資料類型即比對所有字段

之前我們提到 Elasticsearch 會自動将整型字段自動建立為

long

型,如果我們知道文檔中所有數值都不會超過

int

範圍,那麼我們可以用如下配置,讓是以非小數的數值字段自動建立為

integer

PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic_templates": [
      {
        "test_float": {
          "match_mapping_type": "long", # 值是整型的字段會被比對
          "mapping": {
            "type": "integer"  # 字段 type 統一設為 integer
          }
        }
      }
    ]
  }
}           

match 、unmatch

在生産使用中最多的場景,是根據字段的名稱進行比對。這時就可以用

match

unmatch

這兩種比對方式。

match

比對的是符合設定的所有字段,

unmatch

比對的是不符合某種配置的所有字段。在設定比對規則時可以使用

*

表 0 個或多個字元。

比如下面這個模闆就表示所有屬性名以

long_

開頭且不以

_text

結尾的字段配置其

type

long

PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic_templates": [
      {
        "test_float": {
          "match": "long_*", # 屬性名以 long_ 開頭
          "unmatch": "*_test", # 屬性名不以 _test 結尾
          "mapping": {
            "type": "long"  # 字段 type 設為 long
          }
        }
      }
    ]
  }
}           

match_pattern

僅僅使用通配符,可能不能滿足我們多變的比對需求,那麼我們可以将

match_pattern

設為

regex

,這時

match

字段就可以用正規表達式了。

比如下面這個模闆就表示所有以

profit_

開頭,後跟至少 1 位數字的屬性,将它們的

type

keyword

PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic_templates": [
      {
        "test_float": {
          "match_pattern": "regex",  # match 使用正規表達式
          "match": "^profit_\d+$"  # 标準正則
          "mapping": {
            "type": "keyword"  # 字段 type 設為 keyword
          }
        }
      }
    ]
  }
}           

path_match 、 path_unmatch

在 Elasticsearch 中存儲的文檔允許有内嵌對象,當還有多層内嵌對象時,屬性一般有路徑的概念。屬性的路徑也可以作為比對的條件。這個配置的用法與

match

unmatch

雷同,但需要注意的是

match

unmatch

僅作用于最後一級的屬性名。

如下模闆表示設定

person

内嵌對象除了

age

外其它所有以

long_

開頭的字段新增時類型設為

text

PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic_templates": [
      {
        "test_float": {
          "match_pattern": "long_",  # 以 long_ 開頭
          "path_match": "person.*",  # 内嵌對象 person 所有字段
          "path_unmatch": "*.age"    # 排除 age 字段
          "mapping": {
            "type": "text"  # 字段 type 設為 text
          }
        }
      }
    ]
  }
}           

其它技巧及注意事項

在日常生産中難免有這樣的需求,字段是什麼類型就将類型設為什麼,字段名是什麼就用什麼解析器。對于這種需求我們在配置動态模闆的

mapping

時,可以使用占位符

{name}

表示字段名,用

{dynamic_type}

表示識别出的字段類型 。

比如下面 2 個模闆一起表示的意思是,所有新增字元串類型字段,其解析器是字段的名稱,所有其他類型字段新增時,類型就設為識别的字段類型,但是

doc_value

設為 false.

PUT test-dynamic-mapping
{
  "mappings": {
    "dynamic_templates": [
      {
        "named_analyzers": {  # 字段名即是該字段的解析器名稱
          "match_mapping_type": "string", # 比對所有 string 類型
          "match": "*",  # 比對任意屬性名
          "mapping": {
            "type": "text",
            "analyzer": "{name}"  # 解析器是字段名
          }
        }
      },
      {
        "no_doc_values": {  
         # 比對所有類型,但比對string的在前,是以
實際比對除string的其他所有字段
          "match_mapping_type":"*", 
          "mapping": {
            "type": "{dynamic_type}", # 類型直接作為type
            "doc_values": false
  
          }
        }
      }
    ]
  }
}

PUT test-dynamic-mapping/_doc/1
{
  "english": "Some English text",  # 該字段是新字段,會在mapping中新增會用english解析器
  "count":   5  # 該字段的類型會是 long
, doc_values為false
}           

在使用動态模闆時,還有以下幾點需要注意。

  1. 所有

    null

    值及空數組屬于無效值,不會被任何動态模闆比對,在索引文檔時,隻有字段第一次有有效值時,才會與各動态模闆比對,找出比對的模闆建立新字段。
  2. 規則比對時,按照動态模闆配置的順序依次對比,使用最先比對成功的模闆,這就意味着如果有字段同時符合 2 個動态模闆,那麼會使用在

    dynamic_templates

    數組中靠前的那個。每個動态模闆的比對方式至少應包含

    match

    path_match

    match_mapping_type

    中的一個,

    unmatch

    path_unmatch

    不能單獨使用。
  3. mapping

    dynamic_templates

    字段是可以在運作時修改的,每次修改會整體替換

    dynamic_templates

    的所有值而非追加。

比如下面的請求就是将映射

test-dynamic-mapping

原來的動态模闆配置删除,并配一個名為

newTemplate

的動态模闆。

PUT test-dynamic-mapping/_mapping
{
  "dynamic_templates": [
    {
      "newTemplate": {
        "match": "abc*",
        "mapping": {
          "type": "keyword"
        }
      }
    }
  ]