ElasticSearch 索引設計 在MySQL中資料庫設計非常重要,同樣在ES中資料庫設計也是非常重要的 概述 我們創建索引就像創建表結構一樣,必須非常慎重的,索引如果創建不好後面會出現各種各樣的問題 索引設計的重要性 索引創建後,索引的分片只能通過_split和_shrink介面對其進行成倍的 ...
ElasticSearch 索引設計
在MySQL中資料庫設計非常重要,同樣在ES中資料庫設計也是非常重要的
概述
我們創建索引就像創建表結構一樣,必須非常慎重的,索引如果創建不好後面會出現各種各樣的問題
索引設計的重要性
索引創建後,索引的分片只能通過
_split
和_shrink
介面對其進行成倍的增加和縮減
主要是因為es的數據是通過_routing
分配到各個分片上面的,所以本質上是不推薦去改變索引的分片數量的,因為這樣都會對數據進行重新的移動。
還有就是索引只能新增欄位,不能對欄位進行修改和刪除,缺乏靈活性,所以每次都只能通過_reindex
重建索引了,還有就是一個分片的大小以及所以分片數量的多少嚴重影響到了索引的查詢和寫入性能,所以可想而知,設計一個好的索引能夠減少後期的運維管理和提高不少性能,所以前期對索引的設計是相當的重要的。
基於時間的Index設計
Index設計時要考慮的第一件事,就是基於時間對Index進行分割,即每隔一段時間產生一個新的Index
這樣設計的目的
因為現實世界的數據是隨著時間的變化而不斷產生的,切分管理可以獲得足夠的靈活性和更好的性能
如果數據都存儲在一個Index中,很難進行擴展和調整,因為Elasticsearch中Index的某些設置在創建時就設定好了,是不能更改的,比如Primary Shard的個數。
而根據時間來切分Index,則可以實現一定的靈活性,既可以在數據量過大時及時調整Shard個數,也可以及時響應新的業務需求。
大多數業務場景下,客戶對數據的請求都會命中在最近一段時間上,通過切分Index,可以儘可能的避免掃描不必要的數據,提高性能。
時間間隔
根據上面的分析,自然是時間越短越能保持靈活性,但是這樣做就會導致產生大量的Index,而每個Index都會消耗資源來維護其元信息的,因此需要在靈活性、資源和性能上做權衡
- 常見的間隔有小時、天、周和月:先考慮總共要存儲多久的數據,然後選一個既不會產生大量Index又能夠滿足一定靈活性的間隔,比如你需要存儲6個月的數據,那麼一開始選擇“周”這個間隔就會比較合適。
- 考慮業務增長速度:假如業務增長的特別快,比如上周產生了1億數據,這周就增長到了10億,那麼就需要調低這個間隔來保證有足夠的彈性能應對變化。
如何實現分割
切分行為是由客戶端(數據的寫入端)發起的,根據時間間隔與數據產生時間將數據寫入不同的Index中,為了易於區分,會在Index的名字中加上對應的時間標識
創建新Index這件事,可以是客戶端主動發起一個創建的請求,帶上具體的Settings、Mappings等信息,但是可能會有一個時間錯位,即有新數據寫入時新的Index還沒有建好,Elasticsearch提供了更優雅的方式來實現這個動作,即Index Template
分片設計
所謂分片設計,就是如何設定主分片的個數
看上去只是一個數字而已,也許在很多場景下,即使不設定也不會有問題(ES7預設是1個主分片一個副本分片),但是如果不提前考慮,一旦出問題就可能導致系統性能下降、不可訪問、甚至無法恢復,換句話說,即使使用預設值,也應該是通過足夠的評估後作出的決定,而非拍腦袋定的。
限制分片大小
單個Shard的存儲大小不超過30GB
Elastic專家根據經驗總結出來大家普遍認為30GB是個合適的上限值,實踐中發現單個Shard過大(超過30GB)會導致系統不穩定。
其次,為什麼不能超過30GB?主要是考慮Shard Relocate過程的負載,我們知道,如果Shard不均衡或者部分節點故障,Elasticsearch會做Shard Relocate,在這個過程中會搬移Shard,如果單個Shard過大,會導致CPU、IO負載過高進而影響系統性能與穩定性。
評估分片數量
單個Index的
Primary Shard個數 = k * 數據節點個數
在保證第一點的前提下,單個Index的Primary Shard個數不宜過多,否則相關的元信息與緩存會消耗過多的系統資源,這裡的k,為一個較小的整數值,建議取值為1,2等,整數倍的關係可以讓Shard更好地均勻分佈,可以充分的將請求分散到不同節點上。
小索引設計
對於很小的Index,可以只分配1~2個Primary Shard的
有些情況下,Index很小,也許只有幾十、幾百MB左右,那麼就不用按照第二點來分配了,只分配1~2個Primary Shard是可以,不用糾結。
使用索引模板
就是把已經創建好的某個索引的參數設置(settings)和索引映射(mapping)保存下來作為模板,在創建新索引時,指定要使用的模板名,就可以直接重用已經定義好的模板中的設置和映射
Elasticsearch基於與索引名稱匹配的通配符模式將模板應用於新索引,也就是說通過索引進行匹配,看看新建的索引是否符合索引模板,如果符合,就將索引模板的相關設置應用到新的索引,如果同時符合多個索引模板呢,這裡需要對參數priority進行比較,這樣會選擇priority大的那個模板進行創建索引。
在創建索引模板時,如果匹配有包含的關係,或者相同,則必須設置priority為不同的值,否則會報錯,索引模板也是只有在新創建的時候起到作用,修改索引模板對現有的索引沒有影響,同樣如果在索引中設置了一些設置或者mapping都會覆蓋索引模板中相同的設置或者mapping
索引模板的用途
索引模板一般用在時間序列相關的索引中。
也就是說, 如果你需要每間隔一定的時間就建立一次索引,你只需要配置好索引模板,以後就可以直接使用這個模板中的設置,不用每次都設置settings和mappings.
創建索引模板
COPYPUT _index_template/logstash-village
{
"index_patterns": [
"logstash-village-*" // 可以通過"logstash-village-*"來適配創建的索引
],
"template": {
"settings": {
"number_of_shards": "3", //指定模板分片數量
"number_of_replicas": "2" //指定模板副本數量
},
"aliases": {
"logstash-village": {} //指定模板索引別名
},
"mappings": { //設置映射
"dynamic": "strict", //禁用動態映射
"properties": {
"@timestamp": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis||yyyy-MM-dd HH:mm:ss"
},
"@version": {
"doc_values": false,
"index": "false",
"type": "integer"
},
"name": {
"type": "keyword"
},
"province": {
"type": "keyword"
},
"city": {
"type": "keyword"
},
"area": {
"type": "keyword"
},
"addr": {
"type": "text",
"analyzer": "ik_smart"
},
"location": {
"type": "geo_point"
},
"property_type": {
"type": "keyword"
},
"property_company": {
"type": "text",
"analyzer": "ik_smart"
},
"property_cost": {
"type": "float"
},
"floorage": {
"type": "float"
},
"houses": {
"type": "integer"
},
"built_year": {
"type": "integer"
},
"parkings": {
"type": "integer"
},
"volume": {
"type": "float"
},
"greening": {
"type": "float"
},
"producer": {
"type": "keyword"
},
"school": {
"type": "keyword"
},
"info": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
}
模板參數
下麵是創建索引模板的一些參數
參數名稱 | 參數介紹 |
---|---|
index_patterns | 必須配置,用於在創建期間匹配索引名稱的通配符(*)表達式數組 |
template | 可選配置,可以選擇包括別名、映射或設置配置 |
composed_of | 可選配置,組件模板名稱的有序列表。組件模板按指定的順序合併,這意味著最後指定的組件模板具有最高的優先順序 |
priority | 可選配置,創建新索引時確定索引模板優先順序的優先順序。選擇具有最高優先順序的索引模板。如果未指定優先順序,則將模板視為優先順序為0(最低優先順序) |
version | 可選配置,用於外部管理索引模板的版本號 |
_meta | 可選配置,關於索引模板的可選用戶元數據,可能有任何內容 |
映射配置
上面我們配置了映射模板,但是我們用到了映射,下麵我們說下映射
什麼是映射
在創建索引時,可以預先定義欄位的類型(映射類型)及相關屬性
資料庫建表的時候,我們DDL依據一般都會指定每個欄位的存儲類型,例如:varchar、int、datetime等,目的很明確,就是更精確的存儲數據,防止數據類型格式混亂,在Elasticsearch中也是這樣,創建索引的時候一般也需要指定索引的欄位類型,這種方式稱為映射(Mapping)
被動創建(動態映射)
此時欄位和映射類型不需要事先定義,只需要存在文檔的索引,當向此索引添加數據的時候當遇到不存在的映射欄位,ES會根據數據內容自動添加映射欄位定義。
動態映射規則
使用動態映射的時候,根據傳遞請求數據的不同會創建對應的數據類型
數據類型 | Elasticsearch 數據類型 |
---|---|
null | 不添加任何欄位 |
true或者false | boolean類型 |
浮點數據 | float類型 |
integer數據 | long類型 |
object | object類型 |
array | 取決於數組中的第一個非空值的類型。 |
string | 如果此內容通過了日期格式檢測,則會被認為是date數據類型 如果此值通過了數值類型檢測則被認為是double或者long數據類型 帶有關鍵字子欄位會被認為一個text欄位 |
禁止動態映射
一般生產環境下需要禁用動態映射,使用動態映射可能出現以下問題
- 造成集群元數據一直變更,導致不穩定;
- 可能造成數據類型與實際類型不一致;
如何禁用動態映射,動態
mapping
的dynamic
欄位進行配置,可選值及含義如下
- true:支持動態擴展,新增數據有新的欄位屬性時,自動添加對於的mapping,數據寫入成功
- false:不支持動態擴展,新增數據有新的欄位屬性時,直接忽略,數據寫入成功
- strict:不支持動態擴展,新增數據有新的欄位時,報錯,數據寫入失敗
主動創建(顯示映射)
動態映射只能保證最基礎的數據結構的映射
所以很多時候我們需要對欄位除了數據結構定義更多的限制的時候,動態映射創建的內容很可能不符合我們的需求,所以可以使用PUT {index}/mapping
來更新指定索引的映射內容。
映射類型
我們要創建映射必須還要知道映射類型,否則就會走預設的映射類型,下麵我們看看常用的映射類型
準備工作
我們先創建一個用於測試映射類型的索引
COPYPUT mapping_demo
字元串類型
字元串類型是我們最常用的類型之一,我們操作的時候字元串類型可以被設置為以下幾種類型
text
當一個欄位是要被全文搜索的,比如Email內容、產品描述,應該使用text類型,text類型會被分詞
設置text類型以後,欄位內容會被分詞,在生成倒排索引以前,字元串會被分析器分成一個一個詞項,text類型的欄位不用於排序,很少用於聚合
keyword
keyword類型不會被分詞,常用於關鍵字搜索,比如姓名、email地址、主機名、狀態碼和標簽等
如果欄位需要進行過濾(比如查姓名是張三發佈的博客)、排序、聚合,keyword類型的欄位只能通過精確值搜索到,常常被用來過濾、排序和聚合
兩者區別
它們的區別在於
text
會對欄位進行分詞處理而keyword
則不會進行分詞
也就是說如果欄位是text
類型,存入的數據會先進行分詞,然後將分完詞的片語存入索引,而keyword
則不會進行分詞,直接存儲,這樣劃分數據更加節省記憶體。
使用案例
我們先創建一個映射,name是keyword類型,描述是text類型的
COPYPUT mapping_demo/_mapping
{
"properties": {
"name": {
"type": "keyword"
},
"city": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
插入數據
COPYPUT mapping_demo/_doc/1
{
"name":"北京小區",
"city":"北京市昌平區回龍觀街道"
}
對於keyword的name欄位進行精確查詢
COPYGET mapping_demo/_search
{
"query": {
"term": {
"name": "北京小區"
}
}
}
對於text的city進行模糊查詢
COPYGET mapping_demo/_search
{
"query": {
"term": {
"city": "北京市"
}
}
}
數字類型
數字類型也是我們最常用的類型之一,下麵我們看下數字類型的使用
類型 | 取值範圍 |
---|---|
long | -263 ~ 263 |
integer | -231 ~ 231 |
short | -215 ~ 215 |
byte | -27 ~ 27 |
double | 64位的雙精度 IEEE754 浮點類型 |
float | 32位的雙精度 IEEE754 浮點類型 |
half_float | 16位的雙精度 IEEE754 浮點類型 |
scaled_float | 縮放類型的浮點類型 |
註意事項
- 在滿足需求的情況下,優先使用範圍小的欄位,欄位長度越小,索引和搜索的效率越高。
日期類型
JSON表示日期
JSON沒有表達日期的數據類型,所以在ES裡面日期只能是下麵其中之一
- 格式化的日期字元串,比如:
"2015-01-01"
or"2015/01/01 12:10:30"
- 用數字表示的從新紀元開始的毫秒數
- 用數字表示的從新紀元開始的秒數(epoch_second)
註意點:毫秒數的值是不能為負數的,如果時間在1970年以前,需要使用格式化的日期表達
ES如何處理日期
在ES的內部,時間會被轉換為UTC時間(如果聲明瞭時區)並使用從新紀元開始的毫秒數的
長整形數字類型的進行存儲,在日期欄位上的查詢,內部將會轉換為使用長整形的毫秒進行範圍查詢,根據與欄位關聯的日期格式,聚合和存儲欄位的結果將轉換回字元串
註意點:日期最終都會作為字元串呈現,即使最開始初始化的時候是利用JSON文檔的long聲明的
預設日期格式
日期的格式可以被定製化的,如果沒有聲明日期的格式,它將會使用預設的格式:
COPY"strict_date_optional_time||epoch_millis"
這意味著它將會接收帶時間戳的日期,它將遵守strict_date_optional_time限定的格式(yyyy-MM-dd'T'HH:mm:ss.SSSZ
或者 yyyy-MM-dd
)或者毫秒數
日期格式示例
COPYPUT mapping_demo/_mapping
{
"properties": {
"datetime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
# 添加數據
PUT mapping_demo/_doc/2
{
"name":"河北區",
"city":"河北省小區",
"datetime":"2022-02-21 11:35:42"
}
日期類型參數
下麵表格裡的參數可以用在date欄位上面
參數 | 說明 |
---|---|
doc_values | 該欄位是否按照列式存儲在磁碟上以便於後續進行排序、聚合和腳本操作,可配置 true(預設)或 false |
format | 日期的格式 |
locale | 解析日期中時使用了本地語言表示月份時的名稱和/或縮寫,預設是 ROOT locale |
ignore_malformed | 如果設置為true,則奇怪的數字就會被忽略,如果是false(預設)奇怪的數字就會導致異常並且該文檔將會被拒絕寫入。需要註意的是,如果在腳本參數中使用則該屬性不能被設置 |
index | 該欄位是否能快速的被查詢,預設是true。date類型的欄位只有在doc_values設置為true時才能被查詢,儘管很慢。 |
null_value | 替代null的值,預設是null |
on_script_error | 定義在腳本中如何處理拋出的異常,fail(預設)則整個文檔會被拒絕索引,continue:繼續索引 |
script | 如果該欄位被設置,則欄位的值將會使用該腳本產生,而不是直接從source裡面讀取。 |
store | true or false(預設)是否在 _source 之外在獨立存儲一份 |
布爾類型
boolean類型用於存儲文檔中的true/false
範圍類型
顧名思義,範圍類型欄位中存儲的內容就是一段範圍,例如年齡30-55歲,日期在2020-12-28到2021-01-01之間等
類型範圍
es中有六種範圍類型:
- integer_range
- float_range
- long_range
- double_range
- date_range
- ip_range
使用實例
COPYPUT mapping_demo/_mapping
{
"properties": {
"age_range": {
"type": "integer_range"
}
}
}
# 指定年齡範圍,可以使用 gt、gte、lt、lte。
PUT mapping_demo/_doc/3
{
"name":"張三",
"age_range":{
"gt":20,
"lt":30
}
}
分詞器
什麼是分詞器
分詞器的主要作用將用戶輸入的一段文本,按照一定邏輯,分析成多個詞語的一種工具
顧名思義,文本分析就是把全文本轉換成一系列單詞(term/token)的過程,也叫分詞,在 ES 中,Analysis 是通過分詞器(Analyzer) 來實現的,可使用 ES 內置的分析器或者按需定製化分析器。
舉一個分詞簡單的例子:比如你輸入 Mastering Elasticsearch
,會自動幫你分成兩個單詞,一個是 mastering
,另一個是 elasticsearch
,可以看出單詞也被轉化成了小寫的。
分詞器構成
分詞器是專門處理分詞的組件,分詞器由以下三部分組成:
character filter
接收原字元流,通過添加、刪除或者替換操作改變原字元流
例如:去除文本中的html標簽,或者將羅馬數字轉換成阿拉伯數字等,一個字元過濾器可以有零個或者多個
tokenizer
簡單的說就是將一整段文本拆分成一個個的詞
例如拆分英文,通過空格能將句子拆分成一個個的詞,但是對於中文來說,無法使用這種方式來實現,在一個分詞器中,有且只有一個
tokenizeer
token filters
將切分的單詞添加、刪除或者改變
例如將所有英文單詞小寫,或者將英文中的停詞a
刪除等,在token filters
中,不允許將token(分出的詞)
的position
或者offset
改變,同時,在一個分詞器中,可以有零個或者多個token filters
。
分詞順序
同時 Analyzer 三個部分也是有順序的,從圖中可以看出,從上到下依次經過 Character Filters
,Tokenizer
以及 Token Filters
,這個順序比較好理解,一個文本進來肯定要先對文本數據進行處理,再去分詞,最後對分詞的結果進行過濾。
測試分詞
可以通過
_analyzer
API來測試分詞的效果,我們使用下麵的html過濾分詞
COPYPOST _analyze
{
"text":"<b>hello world<b>" # 輸入的文本
"char_filter":["html_strip"], # 過濾html標簽
"tokenizer":"keyword", #原樣輸出
}
什麼時候分詞
文本分詞會發生在兩個地方:
創建索引
:當索引文檔字元類型為text
時,在建立索引時將會對該欄位進行分詞。搜索
:當對一個text
類型的欄位進行全文檢索時,會對用戶輸入的文本進行分詞。
創建索引時指定分詞器
如果設置手動設置了分詞器,ES將按照下麵順序來確定使用哪個分詞器
- 先判斷欄位是否有設置分詞器,如果有,則使用欄位屬性上的分詞器設置
- 如果設置了
analysis.analyzer.default
,則使用該設置的分詞器 - 如果上面兩個都未設置,則使用預設的
standard
分詞器
欄位指定分詞器
為addr屬性指定分詞器,這裡我們使用的是中文分詞器
COPYPUT my_index
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
設置預設分詞器
COPYPUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"default":{
"type":"simple"
}
}
}
}
}
搜索時指定分詞器
在搜索時,通過下麵參數依次檢查搜索時使用的分詞器,這樣我們的搜索語句就會先分詞,然後再來進行搜索
- 搜索時指定
analyzer
參數 - 創建mapping時指定欄位的
search_analyzer
屬性 - 創建索引時指定
setting
的analysis.analyzer.default_search
- 查看創建索引時欄位指定的
analyzer
屬性 - 如果上面幾種都未設置,則使用預設的
standard
分詞器。
指定analyzer
搜索時指定analyzer查詢參數
COPYGET my_index/_search
{
"query": {
"match": {
"message": {
"query": "Quick foxes",
"analyzer": "stop"
}
}
}
}
指定欄位analyzer
COPYPUT my_index
{
"mappings": {
"properties": {
"title":{
"type":"text",
"analyzer": "whitespace",
"search_analyzer": "simple"
}
}
}
}
指定預設default_seach
COPYPUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"default":{
"type":"simple"
},
"default_seach":{
"type":"whitespace"
}
}
}
}
}
內置分詞器
es在索引文檔時,會通過各種類型
Analyzer
對text類型欄位做分析
不同的 Analyzer
會有不同的分詞結果,內置的分詞器有以下幾種,基本上內置的 Analyzer
包括 Language Analyzers
在內,對中文的分詞都不夠友好,中文分詞需要安裝其它 Analyzer
分析器 | 描述 | 分詞對象 | 結果 |
---|---|---|---|
standard | 標準分析器是預設的分析器,如果沒有指定,則使用該分析器。它提供了基於文法的標記化(基於 Unicode 文本分割演算法,如 Unicode 標準附件 # 29所規定) ,並且對大多數語言都有效。 | The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog’s, bone ] |
simple | 簡單分析器將文本分解為任何非字母字元的標記,如數字、空格、連字元和撇號、放棄非字母字元,並將大寫字母更改為小寫字母。 | The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ] |
whitespace | 空格分析器在遇到空白字元時將文本分解為術語 | The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog’s, bone. ] |
stop | 停止分析器與簡單分析器相同,但增加了刪除停止字的支持。預設使用的是 _english_ 停止詞。 |
The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [ quick, brown, foxes, jumped, over, lazy, dog, s, bone ] |
keyword | 不分詞,把整個欄位當做一個整體返回 | The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.] |
pattern | 模式分析器使用正則表達式將文本拆分為術語。正則表達式應該匹配令牌分隔符,而不是令牌本身。正則表達式預設為 w+ (或所有非單詞字元)。 |
The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. | [ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ] |
多種西語系 arabic, armenian, basque, bengali, brazilian, bulgarian, catalan, cjk, czech, danish, dutch, english等等 | 一組旨在分析特定語言文本的分析程式。 |
IK中文分詞器
IKAnalyzer
IKAnalyzer是一個開源的,基於java的語言開發的輕量級的中文分詞工具包
從2006年12月推出1.0版開始,IKAnalyzer已經推出了3個大版本,在 2012 版本中,IK 實現了簡單的分詞歧義排除演算法,標志著 IK 分詞器從單純的詞典分詞向模擬語義分詞衍化
中文分詞器演算法
中文分詞器最簡單的是ik分詞器,還有jieba分詞,哈工大分詞器等
分詞器 | 描述 | 分詞對象 | 結果 |
---|---|---|---|
ik_smart | ik分詞器中的簡單分詞器,支持自定義字典,遠程字典 | 學如逆水行舟,不進則退 | [學如逆水行舟,不進則退] |
ik_max_word | ik_分詞器的全量分詞器,支持自定義字典,遠程字典 | 學如逆水行舟,不進則退 | [學如逆水行舟,學如逆水,逆水行舟,逆水,行舟,不進則退,不進,則,退] |
ik_smart
原始內容
COPY傳智教育的教學質量是杠杠的
測試分詞
COPYGET _analyze
{
"analyzer": "ik_smart",
"text": "傳智教育的教學質量是杠杠的"
}
ik_max_word
原始內容
COPY傳智教育的教學質量是杠杠的
測試分詞
COPYGET _analyze
{
"analyzer": "ik_max_word",
"text": "傳智教育的教學質量是杠杠的"
}
本文由
傳智教育博學谷狂野架構師
教研團隊發佈。如果本文對您有幫助,歡迎
關註
和點贊
;如果您有任何建議也可留言評論
或私信
,您的支持是我堅持創作的動力。轉載請註明出處!