Elasticsearch 問題收集
Mac端安裝
安裝elasticsearch
安裝elasticsearch
❯ brew install elasticsearch
Running `brew update --auto-update`...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
Error: elasticsearch has been disabled because it is switching to an incompatible license. Check out `opensearch` instead!
異常
安裝elasticsearch-full
❯ brew install elastic/tap/elasticsearch-full
==> Tapping elastic/tap
Cloning into '/usr/local/Homebrew/Library/Taps/elastic/homebrew-tap'...
remote: Enumerating objects: 1395, done.
remote: Counting objects: 100% (574/574), done.
remote: Compressing objects: 100% (53/53), done.
remote: Total 1395 (delta 547), reused 531 (delta 521), pack-reused 821
Receiving objects: 100% (1395/1395), 331.44 KiB | 2.42 MiB/s, done.
Resolving deltas: 100% (1087/1087), done.
Tapped 17 formulae (37 files, 445.5KB).
==> Fetching elastic/tap/elasticsearch-full
==> Downloading https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.4-darwin-x86_64.tar.gz?tap=elastic/homeb
######################################################################## 100.0%
==> Installing elasticsearch-full from elastic/tap
Warning: Tried to install empty array to /usr/local/etc/elasticsearch/jvm.options.d
==> codesign -f -s - /usr/local/Cellar/elasticsearch-full/7.17.4/libexec/modules/x-pack-ml/platform/darwin-x86_64/controller.app
==> find /usr/local/Cellar/elasticsearch-full/7.17.4/libexec/jdk.app/Contents/Home/bin -type f -exec codesign -f -s - {} ;
==> Caveats
Data: /usr/local/var/lib/elasticsearch/elasticsearch_hongda/
Logs: /usr/local/var/log/elasticsearch/elasticsearch_hongda.log
Plugins: /usr/local/var/elasticsearch/plugins/
Config: /usr/local/etc/elasticsearch/
To start elastic/tap/elasticsearch-full now and restart at login:
brew services start elastic/tap/elasticsearch-full
Or, if you don't want/need a background service you can just run:
elasticsearch
==> Summary
🍺 /usr/local/Cellar/elasticsearch-full/7.17.4: 946 files, 476.2MB, built in 8 seconds
==> Running `brew cleanup elasticsearch-full`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
查看
❯ brew info elasticsearch-full
==> elastic/tap/elasticsearch-full: stable 7.17.4
Distributed search & analytics engine
https://www.elastic.co/products/elasticsearch
Conflicts with:
elasticsearch
/usr/local/Cellar/elasticsearch-full/7.17.4 (946 files, 476.2MB) *
Built from source on 2022-12-21 at 11:37:26
From: https://github.com/elastic/homebrew-tap/blob/HEAD/Formula/elasticsearch-full.rb
==> Caveats
Data: /usr/local/var/lib/elasticsearch/elasticsearch_hongda/
Logs: /usr/local/var/log/elasticsearch/elasticsearch_hongda.log
Plugins: /usr/local/var/elasticsearch/plugins/
Config: /usr/local/etc/elasticsearch/
To restart elastic/tap/elasticsearch-full after an upgrade:
brew services restart elastic/tap/elasticsearch-full
Or, if you don't want/need a background service you can just run:
elasticsearch
啓動
brew services start elastic/tap/elasticsearch-full
❯ elasticsearch -version
warning: usage of JAVA_HOME is deprecated, use ES_JAVA_HOME
Future versions of Elasticsearch will require Java 11; your Java version from [/Library/Java/JavaVirtualMachines/graalvm-ee-java8/Contents/Home/jre] does not meet this requirement. Consider switching to a distribution of Elasticsearch with a bundled JDK. If you are already using a distribution with a bundled JDK, ensure the JAVA_HOME environment variable is not set.
warning: usage of JAVA_HOME is deprecated, use ES_JAVA_HOME
Future versions of Elasticsearch will require Java 11; your Java version from [/Library/Java/JavaVirtualMachines/graalvm-ee-java8/Contents/Home/jre] does not meet this requirement. Consider switching to a distribution of Elasticsearch with a bundled JDK. If you are already using a distribution with a bundled JDK, ensure the JAVA_HOME environment variable is not set.
Version: 7.17.4, Build: default/tar/79878662c54c886ae89206c685d9f1051a9d6411/2022-05-18T18:04:20.964345128Z, JVM: 1.8.0_321
Curl查看
❯ curl http://127.0.0.1:9200/
{
"name" : "hongda-iMac.local",
"cluster_name" : "elasticsearch_hongda",
"cluster_uuid" : "xs9f7EnQRw28UJLcZUbJvg",
"version" : {
"number" : "7.17.4",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "79878662c54c886ae89206c685d9f1051a9d6411",
"build_date" : "2022-05-18T18:04:20.964345128Z",
"build_snapshot" : false,
"lucene_version" : "8.11.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
安裝kibana
❯ brew install elastic/tap/kibana-full
==> Fetching elastic/tap/kibana-full
==> Downloading https://artifacts.elastic.co/downloads/kibana/kibana-7.17.4-darwin-x86_64.tar.gz?tap=elastic/homebrew-tap
######################################################################## 100.0%
==> Installing kibana-full from elastic/tap
==> Caveats
Config: /usr/local/etc/kibana/
If you wish to preserve your plugins upon upgrade, make a copy of
/usr/local/opt/kibana-full/plugins before upgrading, and copy it into the
new keg location after upgrading.
To start elastic/tap/kibana-full now and restart at login:
brew services start elastic/tap/kibana-full
Or, if you don't want/need a background service you can just run:
kibana
==> Summary
🍺 /usr/local/Cellar/kibana-full/7.17.4: 37,690 files, 630.8MB, built in 49 seconds
==> Running `brew cleanup kibana-full`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
查看版本
❯ kibana --version
7.17.4
啓動
❯ brew services start elastic/tap/kibana-full
==> Successfully started `kibana-full` (label: homebrew.mxcl.kibana-full)
查看
❯ curl --location --request GET 'http://127.0.0.1:9200/_cat/indices?v'
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open .geoip_databases uZ17Xau6RgGNet5_dZZqGw 1 0 40 37 38.1mb 38.1mb
green open .apm-custom-link mE3YFQn0RCuhbfNd7t2JPQ 1 0 0 0 226b 226b
yellow open user_admin g3-wI7CaSKGjTu7r0d4FSg 1 1 3 0 10.1kb 10.1kb
green open .apm-agent-configuration 5P4eIDpcTn2UuDuLouOuPg 1 0 0 0 226b 226b
green open .kibana_task_manager_7.17.4_001 Ni7AVwi9QLCFpRmY-mWCMQ 1 0 17 466 95.6kb 95.6kb
yellow open user eKuLL-pvT4irwxEsXra15Q 1 1 2 0 9.5kb 9.5kb
green open .kibana_7.17.4_001 RNF7VkEfSIOWjdelat-2aQ 1 0 317 1920 2.5mb 2.5mb
安裝smartcn分詞器
❯ elasticsearch-plugin install analysis-smartcn
warning: usage of JAVA_HOME is deprecated, use ES_JAVA_HOME
Future versions of Elasticsearch will require Java 11; your Java version from [/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre] does not meet this requirement. Consider switching to a distribution of Elasticsearch with a bundled JDK. If you are already using a distribution with a bundled JDK, ensure the JAVA_HOME environment variable is not set.
-> Installing analysis-smartcn
-> Downloading analysis-smartcn from elastic
[=================================================] 100%
-> Installed analysis-smartcn
-> Please restart Elasticsearch to activate any plugins installed
smartcn是目前ES官方推薦的中文分詞插件,不過目前不支持自定義詞庫。
重啓
❯ brew services restart elasticsearch-full
Stopping `elasticsearch-full`... (might take a while)
==> Successfully stopped `elasticsearch-full` (label: homebrew.mxcl.elasticsearch-full)
==> Successfully started `elasticsearch-full` (label: homebrew.mxcl.elasticsearch-full)
安裝ik中文分詞
安裝ik插件:
// 到這裏找跟自己ES版本一致的插件地址
https://github.com/medcl/elasticsearch-analysis-ik/releases
我本地使用的ES版本是7.17.4,所以選擇的Ik插件版本地址是:
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.4/elasticsearch-analysis-ik-7.17.4.zip
當然你也可以嘗試根據我給出這個地址,直接修改版本號,試試看行不行。
安裝命令
{ES安裝目錄}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.4/elasticsearch-analysis-ik-7.17.4.zip
ik分詞插件支持 ik_smart 和 ik_max_word 兩種分詞器
- ik_smart - 粗粒度的分詞
- ik_max_word - 會盡可能的枚舉可能的關鍵詞,就是分詞比較細緻一些,會分解出更多的關鍵詞
❯ elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.4/elasticsearch-analysis-ik-7.17.4.zip
warning: usage of JAVA_HOME is deprecated, use ES_JAVA_HOME
Future versions of Elasticsearch will require Java 11; your Java version from [/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre] does not meet this requirement. Consider switching to a distribution of Elasticsearch with a bundled JDK. If you are already using a distribution with a bundled JDK, ensure the JAVA_HOME environment variable is not set.
-> Installing https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.4/elasticsearch-analysis-ik-7.17.4.zip
-> Downloading https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.4/elasticsearch-analysis-ik-7.17.4.zip
[=================================================] 100%
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: plugin requires additional permissions @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.net.SocketPermission * connect,resolve
See https://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.
Continue with installation? [y/N]y
-> Installed analysis-ik
-> Please restart Elasticsearch to activate any plugins installed
ik自定義詞庫
/usr/local/etc/elasticsearch/analysis-ik at 08:59:01
❯ touch demo.dic
demo.dict內容
上海大學
復旦大學
人民廣場
一行一個詞條即可
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!--用戶可以在這裏配置自己的擴展字典 -->
<entry key="ext_dict">demo.dic</entry>
<!--用戶可以在這裏配置自己的擴展停止詞字典-->
<entry key="ext_stopwords"></entry>
<!--用戶可以在這裏配置遠程擴展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用戶可以在這裏配置遠程擴展停止詞字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
重啓es
Ik新增擴展詞庫,支持熱更新,不用重啓ES,使用remote_ext_dict和remote_ext_stopwords配置遠程詞庫地址即可,詞庫地址需要返回兩個頭部(header),一個是
Last-Modified
,一個是ETag
,ES靠這兩個頭識別是否需要更新詞庫,不瞭解這兩個HTTP頭,可以搜一下。
Elasticsearch vs RDBMS
Elasticsearch 中,索引是一個集合,相當於數據庫 ( RDBMS , 關係數據庫管理系統 ) 中的數據庫
集合中的每個映射,相當於 RDBMS 中的表
而索引中的每個 JSON 對象,又相當於 RDBMS 中的數據行
所以,Elasticsearch,集合是一些包含了 JSON 對象的映射的集合
詳細的對比如下
Elasticsearch | RDBMS |
---|---|
Index 索引 | Database |
Shard | Shard |
Mapping | Table |
Field | Field |
JSON Object | Tuple |
Elasticsearch | 索引 | 類型 | 文檔 |
---|---|---|---|
DB | 庫 | 表 | 行 |
集羣內部通過 Elasticsearch 的選主算法選出主節點,而集羣外部則是可以通過任何節點進行操作,無主從節點之分 ( 對外表現對等/去中心化,有利於客戶端編程,例如故障重連 )
type(文檔類型)
在新版的Elasticsearch中,已經不使用文檔類型了,在Elasticsearch老的版本中文檔類型,代表一類文檔的集合,index(索引)類似mysql的數據庫、文檔類型類似Mysql的表。
既然新的版本文檔類型沒什麼作用了,那麼index(索引)就類似mysql的表的概念,ES沒有數據庫的概念了。
提示:在Elasticsearch7.0以後的版本,已經廢棄文檔類型了,如果大家不是接手老的項目,可以不必理會文檔類型,可以直接將index(索引)類比Mysql的表。
分片
Elasticsearch 在保存索引時會選擇適合的 「 主分片 」 ( Primary Shard ),把索引保存到其中
我們可以把分片理解爲一塊物理存儲區域
分片的分法是固定的,而且是安裝時候就必須要決定好的 ( 默認是 5 ),後面就不能改變了
既然有主分片,那肯定是有 「 從 」 分片的,但 Elasticsearch 裏稱之爲 「 副本分片 」 ( Replica Shard )
副本分片主要有兩個作用:
-
高可用
某分片節點掛了的話可走其他副本分片節點,節點恢復後上面的分片數據可通過其他節點恢復
-
負載均衡
Elasticsearch 會自動根據負載情況控制搜索路由,副本分片可以將負載均攤
上圖中:
- 3 個 Elasticsearch 節點 ( es-58/59/60 ) 組成一個集羣
- 搭建集羣時使用默認的主分片數 5,shard0 ~ shard4
- 該集羣內有加入兩個索引 index1、index2
- 這兩個索引中分別 「 索引 」 ( 保存 ) 了兩個文檔
- index1 索引中這個文檔被 Elasticsearch 自動保存到了分片 2 中,主分片在 es-58 節點,副本分片在 es-59 節點
- index2 索引中這個文檔被 Elasticsearch 自動保存到了分片 2 中,主分片在 es-59 節點,副本分片在 es-58 節點
primary shard ( 主分片 )
每個文檔都會被保存在一個主分片上
當我們索引一個文檔時,它將在一個主分片上進行索引,然後才放到該主分片的各副本分片上
默認情況下,一個索引有 5 個主分片
我們可以指定更少或更多的主分片來伸縮索引可處理的文檔數
需要注意的是,一旦索引創建,就不能修改主分片個數
replica shard ( 副本分片 )
每個主分片可以擁有 0 個或多個副本分片,一個副本分片是主分片的一份拷貝
這樣做有兩個主要原因:
-
故障轉移
當主分片失效時,一個副本分片會被提升爲主分片
-
提高性能
獲取與搜索請求可以被主分片或副本分片處理
默認情況下,每個主分片都有一個副本分片,副本分片的數量可以動態調整
在同一個節點上,副本分片和其主分片不會同時運行
篩選
boolean
- must:必須匹配,會影響算分結果
- should:選擇性匹配,也會影響算分結果
- must_not:必須不能匹配,不會影響算分
- filter:必須匹配,不會影響算分
那麼如果不指定minimum_should_match
,可能這四個條件中有一個滿足就能查到,但是如果指定了minimum_should_match=3
,那麼這四個條件中必須滿足三個纔會返回。
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "user.id" : "kimchy" }
},
"filter": {
"term" : { "tags" : "production" }
},
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [ # 一個數組,包括了兩個term查詢,如果沒有指定must條件,那麼should查詢中的term必須至少滿足一條查詢
{ "term" : { "tags" : "env1" } },
{ "term" : { "tags" : "deployed" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}
更新 put是替換了整個文檔,而不是更新name這一個字段
post更新的是文檔的局部字段,原來有的字段更新,沒有的字段則新增這個字段
1)數據先寫入內存buffer,在寫入buffer的同時將數據寫入translog日誌文件,注意:此時數據還沒有被成功es索引記錄,因此無法搜索到對應數據;
2)如果buffer快滿了或者到一定時間,es就會將buffer數據refresh到一個新的segment file中,但是此時數據不是直接進入segment file的磁盤文件,而是先進入os cache的。這個過程就是refresh。
每隔1秒鐘,es將buffer中的數據寫入一個新的segment file,因此每秒鐘會產生一個新的磁盤文件segment file,這個segment file中就存儲最近1秒內buffer中寫入的數據。
操作系統中,磁盤文件其實都有一個操作系統緩存os cache,因此數據寫入磁盤文件之前,會先進入操作系統級別的內存緩存os cache中。
一旦buffer中的數據被refresh操作,刷入os cache中,就代表這個數據就可以被搜索到了。
這就是爲什麼es被稱爲準實時(NRT,near real-time):因爲寫入的數據默認每隔1秒refresh一次,也就是數據每隔一秒才能被 es 搜索到,之後才能被看到,所以稱爲準實時。
只要數據被輸入os cache中,buffer就會被清空,並且數據在translog日誌文件裏面持久化到磁盤了一份,此時就可以讓這個segment file的數據對外提供搜索了。
3)重複1~2步驟,新的數據不斷進入buffer和translog,不斷將buffer數據寫入一個又一個新的segment file中去,每次refresh完,buffer就會被清空,同時translog保留一份日誌數據。隨着這個過程推進,translog文件會不斷變大。當translog文件達到一定程度時,就會執行commit操作。
4)commit操作發生第一步,就是將buffer中現有數據refresh到os cache中去,清空buffer。
5)將一個 commit point 寫入磁盤文件,裏面標識着這個 commit point 對應的所有 segment file,同時強行將 os cache 中目前所有的數據都 fsync 到磁盤文件中去。
6)將現有的translog清空,然後再次重啓啓用一個translog,此時commit操作完成。
translog日誌文件的作用是什麼?
在你執行commit操作之前,數據要麼是停留在buffer中,要麼是停留在os cache中,無論是buffer還是os cache都是內存,一旦這臺機器死了,內存中的數據就全丟了。
因此需要將數據對應的操作寫入一個專門的日誌文件,也就是translog日誌文件,一旦此時機器宕機,再次重啓的時候,es會自動讀取translog日誌文件中的數據,恢復到內存buffer和os cache中去。
綜上可以看出:
es是準實時的,因此數據寫入1秒後纔可以搜索到。
如果translog是異步寫入的話,es可能會丟失數據:有n秒的數據停留在buffer、translog的os cache、segment file的os cache中,也就是這n秒的數據不在磁盤上,此時如果宕機,會導致n秒的數據丟失。
translog
寫入ES的數據首先會被寫入translog文件,該文件持久化到磁盤,保證服務器宕機的時候數據不會丟失,由於順序寫磁盤,速度也會很快。
同步寫入:每次寫入請求執行的時候,translog在fsync到磁盤之後,纔會給客戶端返回成功
異步寫入:寫入請求緩存在內存中,每經過固定時間之後纔會fsync到磁盤,寫入量很大,對於數據的完整性要求又不是非常嚴格的情況下,可以開啓異步寫入
ES整體結構
- 一個 ES Index 在集羣模式下,有多個 Node (節點)組成。每個節點就是 ES 的Instance (實例)。
- 每個節點上會有多個 shard (分片), P1 P2 是主分片, R1 R2 是副本分片
- 每個分片上對應着就是一個 Lucene Index(底層索引文件)
- Lucene Index 是一個統稱由多個 Segment (段文件,就是倒排索引)組成。每個段文件存儲着就是 Doc 文檔。commit point記錄了所有 segments 的信息
Master選舉
master選舉
選舉策略
如果集羣中存在master,認可該master,加入集羣
如果集羣中不存在master,從具有master資格的節點中選id最小的節點作爲master
選舉時機
集羣啓動:後臺啓動線程去ping集羣中的節點,按照上述策略從具有master資格的節點中選舉出master
現有的master離開集羣:後臺一直有一個線程定時ping master節點,超過一定次數沒有ping成功之後,重新進行master的選舉
避免腦裂
腦裂問題是採用master-slave模式的分佈式集羣普遍需要關注的問題,腦裂一旦出現,會導致集羣的狀態出現不一致,導致數據錯誤甚至丟失。
ES避免腦裂的策略:過半原則,可以在ES的集羣配置中添加一下配置,避免腦裂的發生
文檔的唯一性由 _index
, _type
, 和 routing
values的組合來確定。
搜索發生時
搜索時,Lucene會搜索所有的segment然後將每個segment的搜索結果返回,最後合併呈現給客戶。
Lucene的一些特性使得這個過程非常重要:
-
Segments是不可變的(immutable)
- Delete? 當刪除發生時,Lucene做的只是將其標誌位置爲刪除,但是文件還是會在它原來的地方,不會發生改變
- Update? 所以對於更新來說,本質上它做的工作是:先刪除,然後重新索引(Re-index)
-
隨處可見的壓縮
Lucene非常擅長壓縮數據,基本上所有教科書上的壓縮方式,都能在Lucene中找到。
-
緩存所有的所有
Lucene也會將所有的信息做緩存,這大大提高了它的查詢效率。
分步驟看數據持久化過程
通過分步驟看數據持久化過程:write -> refresh -> flush -> merge
- write 過程
一個新文檔過來,會存儲在 in-memory buffer 內存緩存區中,順便會記錄 Translog(Elasticsearch 增加了一個 translog ,或者叫事務日誌,在每一次對 Elasticsearch 進行操作時均進行了日誌記錄)。
這時候數據還沒到 segment ,是搜不到這個新文檔的。數據只有被 refresh 後,纔可以被搜索到。
- refresh 過程
refresh 默認 1 秒鐘,執行一次上圖流程。ES 是支持修改這個值的,通過 index.refresh_interval 設置 refresh (沖刷)間隔時間。refresh 流程大致如下:
- in-memory buffer 中的文檔寫入到新的 segment 中,但 segment 是存儲在文件系統的緩存中。此時文檔可以被搜索到
- 最後清空 in-memory buffer。注意: Translog 沒有被清空,爲了將 segment 數據寫到磁盤
- 文檔經過 refresh 後, segment 暫時寫到文件系統緩存,這樣避免了性能 IO 操作,又可以使文檔搜索到。refresh 默認 1 秒執行一次,性能損耗太大。一般建議稍微延長這個 refresh 時間間隔,比如 5 s。因此,ES 其實就是準實時,達不到真正的實時。
- flush 過程
每隔一段時間—例如 translog 變得越來越大—索引被刷新(flush);一個新的 translog 被創建,並且一個全量提交被執行
上個過程中 segment 在文件系統緩存中,會有意外故障文檔丟失。那麼,爲了保證文檔不會丟失,需要將文檔寫入磁盤。那麼文檔從文件緩存寫入磁盤的過程就是 flush。寫入次怕後,清空 translog。具體過程如下:
- 所有在內存緩衝區的文檔都被寫入一個新的段。
- 緩衝區被清空。
- 一個Commit Point被寫入硬盤。
- 文件系統緩存通過 fsync 被刷新(flush)。
- 老的 translog 被刪除。
- merge 過程
由於自動刷新流程每秒會創建一個新的段 ,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。 每一個段都會消耗文件句柄、內存和cpu運行週期。更重要的是,每個搜索請求都必須輪流檢查每個段;所以段越多,搜索也就越慢。
Elasticsearch通過在後臺進行Merge Segment來解決這個問題。小的段被合併到大的段,然後這些大的段再被合併到更大的段。
當索引的時候,刷新(refresh)操作會創建新的段並將段打開以供搜索使用。合併進程選擇一小部分大小相似的段,並且在後臺將它們合併到更大的段中。這並不會中斷索引和搜索。
一旦合併結束,老的段被刪除:
- 新的段被刷新(flush)到了磁盤。 ** 寫入一個包含新段且排除舊的和較小的段的新提交點。
- 新的段被打開用來搜索。
- 老的段被刪除。
合併大的段需要消耗大量的I/O和CPU資源,如果任其發展會影響搜索性能。Elasticsearch在默認情況下會對合並流程進行資源限制,所以搜索仍然 有足夠的資源很好地執行。
採用多個副本後,避免了單機或磁盤故障發生時,對已經持久化後的數據造成損害,但是Elasticsearch裏爲了減少磁盤IO保證讀寫性能,一般是每隔一段時間(比如5分鐘)纔會把Lucene的Segment寫入磁盤持久化,對於寫入內存,但還未Flush到磁盤的Lucene數據,如果發生機器宕機或者掉電,那麼內存中的數據也會丟失,這時候如何保證?
對於這種問題,Elasticsearch學習了數據庫中的處理方式:增加CommitLog模塊,Elasticsearch中叫TransLog。
索引優化設置
索引優化主要是在 Elasticsearch 的插入層面優化,Elasticsearch 本身索引速度其實還是蠻快的,具體數據,我們可以參考官方的 benchmark 數據。我們可以根據不同的需求,針對索引優化。
批量提交
當有大量數據提交的時候,建議採用批量提交(Bulk 操作);此外使用 bulk 請求時,每個請求不超過幾十M,因爲太大會導致內存使用過大。
比如在做 ELK 過程中,Logstash indexer 提交數據到 Elasticsearch 中,batch size 就可以作爲一個優化功能點。但是優化 size 大小需要根據文檔大小和服務器性能而定。
像 Logstash 中提交文檔大小超過 20MB,Logstash 會將一個批量請求切分爲多個批量請求。
如果在提交過程中,遇到 EsRejectedExecutionException 異常的話,則說明集羣的索引性能已經達到極限了。這種情況,要麼提高服務器集羣的資源,要麼根據業務規則,減少數據收集速度,比如只收集 Warn、Error 級別以上的日誌。
增加 Refresh 時間間隔
爲了提高索引性能,Elasticsearch 在寫入數據的時候,採用延遲寫入的策略,即數據先寫到內存中,當超過默認1秒(index.refresh_interval)會進行一次寫入操作,就是將內存中 segment 數據刷新到磁盤中,此時我們才能將數據搜索出來,所以這就是爲什麼 Elasticsearch 提供的是近實時搜索功能,而不是實時搜索功能。
如果我們的系統對數據延遲要求不高的話,我們可以通過延長 refresh 時間間隔,可以有效地減少 segment 合併壓力,提高索引速度。比如在做全鏈路跟蹤的過程中,我們就將 index.refresh_interval 設置爲30s,減少 refresh 次數。再如,在進行全量索引時,可以將 refresh 次數臨時關閉,即 index.refresh_interval 設置爲-1,數據導入成功後再打開到正常模式,比如30s。
在加載大量數據時候可以暫時不用 refresh 和 repliccas,index.refresh_interval 設置爲-1,index.number_of_replicas 設置爲0。
相關原理,請參考[原理:ES原理之索引文檔流程詳解]
修改 index_buffer_size 的設置
索引緩衝的設置可以控制多少內存分配給索引進程。這是一個全局配置,會應用於一個節點上所有不同的分片上。
indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb
indices.memory.index_buffer_size 接受一個百分比或者一個表示字節大小的值。默認是10%,意味着分配給節點的總內存的10%用來做索引緩衝的大小。這個數值被分到不同的分片(shards)上。如果設置的是百分比,還可以設置 min_index_buffer_size (默認 48mb)和 max_index_buffer_size(默認沒有上限)。
修改 translog 相關的設置
一是控制數據從內存到硬盤的操作頻率,以減少硬盤 IO。可將 sync_interval 的時間設置大一些。默認爲5s。
index.translog.sync_interval: 5s
也可以控制 tranlog 數據塊的大小,達到 threshold 大小時,纔會 flush 到 lucene 索引文件。默認爲512m。
index.translog.flush_threshold_size: 512mb
translog我們在[原理:ES原理之索引文檔流程詳解]也有介紹。
注意 _id 字段的使用
_id 字段的使用,應儘可能避免自定義 _id,以避免針對 ID 的版本管理;建議使用 ES 的默認 ID 生成策略或使用數字類型 ID 做爲主鍵。
注意 _all 字段及 _source 字段的使用
_all 字段及 _source 字段的使用,應該注意場景和需要,_all 字段包含了所有的索引字段,方便做全文檢索,如果無此需求,可以禁用;_source 存儲了原始的 document 內容,如果沒有獲取原始文檔數據的需求,可通過設置 includes、excludes 屬性來定義放入 _source 的字段。
合理的配置使用 index 屬性
合理的配置使用 index 屬性,analyzed 和 not_analyzed,根據業務需求來控制字段是否分詞或不分詞。只有 groupby 需求的字段,配置時就設置成 not_analyzed,以提高查詢或聚類的效率。
減少副本數量
Elasticsearch 默認副本數量爲3個,雖然這樣會提高集羣的可用性,增加搜索的併發數,但是同時也會影響寫入索引的效率。
在索引過程中,需要把更新的文檔發到副本節點上,等副本節點生效後在進行返回結束。使用 Elasticsearch 做業務搜索的時候,建議副本數目還是設置爲3個,但是像內部 ELK 日誌系統、分佈式跟蹤系統中,完全可以將副本數目設置爲1個。
查詢方面優化
Elasticsearch 作爲業務搜索的近實時查詢時,查詢效率的優化顯得尤爲重要。
路由優化
當我們查詢文檔的時候,Elasticsearch 如何知道一個文檔應該存放到哪個分片中呢?它其實是通過下面這個公式來計算出來的。
shard = hash(routing) % number_of_primary_shards
routing 默認值是文檔的 id,也可以採用自定義值,比如用戶 ID。
不帶 routing 查詢
在查詢的時候因爲不知道要查詢的數據具體在哪個分片上,所以整個過程分爲2個步驟:
- 分發:請求到達協調節點後,協調節點將查詢請求分發到每個分片上。
- 聚合:協調節點蒐集到每個分片上查詢結果,再將查詢的結果進行排序,之後給用戶返回結果。
帶 routing 查詢
查詢的時候,可以直接根據 routing 信息定位到某個分配查詢,不需要查詢所有的分配,經過協調節點排序。
向上面自定義的用戶查詢,如果 routing 設置爲 userid 的話,就可以直接查詢出數據來,效率提升很多。
Filter VS Query
儘可能使用過濾器上下文(Filter)替代查詢上下文(Query)
- Query:此文檔與此查詢子句的匹配程度如何?
- Filter:此文檔和查詢子句匹配嗎?
Elasticsearch 針對 Filter 查詢只需要回答「是」或者「否」,不需要像 Query 查詢一樣計算相關性分數,同時Filter結果可以緩存。
深度翻頁
在使用 Elasticsearch 過程中,應儘量避免大翻頁的出現。
正常翻頁查詢都是從 from 開始 size 條數據,這樣就需要在每個分片中查詢打分排名在前面的 from+size 條數據。協同節點收集每個分配的前 from+size 條數據。協同節點一共會受到 N*(from+size) 條數據,然後進行排序,再將其中 from 到 from+size 條數據返回出去。如果 from 或者 size 很大的話,導致參加排序的數量會同步擴大很多,最終會導致 CPU 資源消耗增大。
可以通過使用 Elasticsearch scroll 和 scroll-scan 高效滾動的方式來解決這樣的問題。
也可以結合實際業務特點,文檔 id 大小如果和文檔創建時間是一致有序的,可以以文檔 id 作爲分頁的偏移量,並將其作爲分頁查詢的一個條件。
協調節點(Coordinating Node)
協調節點用於做分佈式裏的協調,將各分片或節點返回的數據整合後返回。該節點不會被選作主節點,也不會存儲任何索引數據。該服務器主要用於查詢負載均衡。在查詢的時候,通常會涉及到從多個 node 服務器上查詢數據,並將請求分發到多個指定的 node 服務器,並對各個 node 服務器返回的結果進行一個彙總處理,最終返回給客戶端。在 ES 集羣中,所有的節點都有可能是協調節點,但是,可以通過設置 node.master、node.data、node.ingest 都爲 false 來設置專門的協調節點。需要較好的 CPU 和較高的內存。
- node.master:false和node.data:true,該node服務器只作爲一個數據節點,只用於存儲索引數據,使該node服務器功能單一,只用於數據存儲和數據查詢,降低其資源消耗率。
- node.master:true和node.data:false,該node服務器只作爲一個主節點,但不存儲任何索引數據,該node服務器將使用自身空閒的資源,來協調各種創建索引請求或者查詢請求,並將這些請求合理分發到相關的node服務器上。
- node.master:false和node.data:false,該node服務器即不會被選作主節點,也不會存儲任何索引數據。該服務器主要用於查詢負載均衡。在查詢的時候,通常會涉及到從多個node服務器上查詢數據,並將請求分發到多個指定的node服務器,並對各個node服務器返回的結果進行一個彙總處理,最終返回給客戶端。
收集
- now的值不受time-zone影響
參考:
ElasticSearch查詢DSL之組合查詢(bool、boosting、constant_score、dis_max)介紹