《百姓網(wǎng) Elasticsearch 2.x 升級之路》要點:
本文介紹了百姓網(wǎng) Elasticsearch 2.x 升級之路,希望對您有用。如果有疑問,可以聯(lián)系我們。
導(dǎo)讀:Elasticsearch 是廣泛使用的一個軟件,我們邀請了曾經(jīng)在高可用架構(gòu)分享過 ES 的王衛(wèi)華繼續(xù)分享在升級 Elasticsearch 過程中的經(jīng)驗.
王衛(wèi)華,資深開發(fā)工程師、架構(gòu)師,具有 10+ 年互聯(lián)網(wǎng)從業(yè)經(jīng)驗,曾獲得微軟 2002 – 2009 MVP 榮譽稱號.在百姓網(wǎng)近 9 年,負(fù)責(zé)后端代碼開發(fā)和 Elasticsearch & Solr 維護(hù)工作.現(xiàn)就職于途虎養(yǎng)車.
百姓網(wǎng)使用 Elasticsearch 雖然有用于日志(ELK),但本次分享所涉及 Elasticsearch 升級,是指用于業(yè)務(wù)系統(tǒng)數(shù)據(jù)服務(wù)的 Elasticsearch 集群.
百姓網(wǎng)是一個分類網(wǎng)站,要提供快速數(shù)據(jù)查詢,我們使用了 Lucene 作為基層的搜索系統(tǒng),從幾年前的 Solr 到 現(xiàn)在使用的 Elasticsearch.為了提供快速的查詢響應(yīng),我們使用了一個 golang 寫的代理系統(tǒng),代理后面是幾個 Elasticsearch 集群,以應(yīng)對不同查詢.
因為集群眾多,一次性全部系統(tǒng)升級需要占用一倍的機器,這比較浪費,所以我們采用一個集群一個集群升級,這就需要不同版本的集群同時存在,從 1.0 升級到 1.6/1.7,他們基本查詢都相差不大,然而,從 1.x 到 2.x,需要做的事情就很多了.而且很不幸,還有坑.
下面談?wù)勎覀冊谏夁^程所遇到的一些問題和解決之路.
這無疑是 2.x 中最大變化之一,雖然之前也有 doc_values,但是這次是默認(rèn)開啟 doc_values,也說明官方是建議你使用 doc_values 的.
這個已經(jīng) deprecated,在 2.x 中你還可以用.但是建議做如下修改
{
“query”: {
“filtered”: {
“query”: {
…….
},
“filter”: {
…….
}
}
}
}
修改為
{
“query”: {
“bool”: {
“must”: {
…….
},
“filter”: {
…….
}
}
}
}
把 query 和 filter 移到 bool 查詢的 must 和 filter 參數(shù)之中.
現(xiàn)在作為一個插件了,而且使用的是 Scroll/Scan & Bulk 來進(jìn)行安全刪除,當(dāng)然,速度可能慢一些.(./bin/plugin install delete-by-query 安裝插件)
Aggerations histogram min_doc_count 默認(rèn)值現(xiàn)在是 0.
一般設(shè)置為網(wǎng)絡(luò)設(shè)備名稱相關(guān),如 eth0,則設(shè)置為 _eth0:ipv4,若是 em1,這設(shè)置為 _em1:ipv4.
不過它也可以作為一個插件加入.1.x multicast 默認(rèn)是啟用的,2.x 使用 unicast (單播),需要設(shè)置 discovery.zen.ping.unicast.hosts: [“host1:port”, “host2”],以使得集群可以加入相關(guān)機器.
默認(rèn)使用 default_fs,是一種 Lucene MMapDirectory 和 NIOFSDirectory 混合的模式,詞典文件和 doc values 文件使用 mmap 映射到系統(tǒng)虛擬內(nèi)存(需要設(shè)置 vm.max_map_count=262144),其他的文件(如頻率、位置等)使用 nio 文件系統(tǒng).
1)同名字段:如果同一個索引中有不同類型的同名字段,那么這兩個類型的 mapping 必須一致.并且不能刪除 mapping (刪了mapping,另一個類型同名字段就沒有 mapping 了?).
2)各種 _ 前綴的名稱移除了.
3)dot (點)的各種坑,字段名不要包含 dot.
4)字段名最長 255
5)_routing 只能設(shè)置為required : true,沒有 path 參數(shù).
6)analyzer 現(xiàn)在可以分開設(shè)置 index_analyzer 和 search_analyzer.默認(rèn)設(shè)置 analyzer 即為兩者(index、search)同一配置.
1)search_type=scan deprecated,你可以在 scroll 查詢時使用 sort:”_doc” 來代替,_doc 排序已經(jīng)進(jìn)行了優(yōu)化,因此它的性能和 scan 相同.
2)search_type=count deprecated, 可以設(shè)置 size:0 .
Percolator docs 是在內(nèi)存中,不支持 doc_values,而 geo_point(ES 2.2)的一些查詢功能需要啟用 doc_values.
不過,geo_point 禁用了doc_values,有些一般查詢?nèi)匀挥行?
2.0 剛出的時候,我們進(jìn)行了測試,發(fā)現(xiàn) IO 壓力有點大.啟用比不啟用 doc_values,IO 壓力要增加一倍以上(測試磁盤非 SSD).
2.0 初始版本,Delete 會導(dǎo)致 IO 壓力更大,刪除操作會有 translog 等詭異問題.
解決辦法:建議升級到較高版本,如 2.2 及以上.
在 1.x 時候,我們沒有啟用 Bulk 接口,而是使用 Index 接口,升級后發(fā)現(xiàn)更新速度比較慢.我們改用 Bulk 接口以解決這個問題.
如果使用 Bulk 接口來進(jìn)行刪除,建議升級到較高版本,因為 2.0 初始版本 Bulk delete 可以不需要提供 routing,但是這樣性能也很差.較高版本修復(fù)了這個問題,刪除一個 DOC,需要提供 routing.
其實要獲得 routing 并不困難,2.x 在你查詢的時候,提供的結(jié)果中,就有 routing 這個數(shù)據(jù),這個對于做刪除操作還是比較方便的,不需要進(jìn)行計算,還能保證在 routing 頻繁變化后刪除干凈.
Lucene 索引是一種倒排索引,當(dāng)需要進(jìn)行排序或者計算時,需要在內(nèi)存中使用 fielddata cache 進(jìn)行計算,極端情況下,可能導(dǎo)致 OOM 或者內(nèi)存泄露.這時候可以考慮啟用 doc_values,這個是索引時候已經(jīng)進(jìn)行處理的一種非倒排索引.啟用 doc_values,性能有一點損失,但是可以設(shè)置較小的 heap size,而留下內(nèi)存給系統(tǒng)緩存 doc_values 索引,性能幾乎相當(dāng).
1)啟用 doc_values 后,Index size 增加近一倍.
2)啟用 doc_values ,當(dāng)進(jìn)行 aggs,sort 時,減少內(nèi)存需求,減低 GC 壓力.可以設(shè)置較小的 heap size.
3)啟用 doc_values 后,當(dāng) Lucene 索引有效使用系統(tǒng)緩存時,性能幾乎相當(dāng).
4)2.x 你仍然可以 Disable doc_values,設(shè)置一個較大的 heap.只要沒有較大的 GC 問題,選擇 disable doc_values 是可以的,而且?guī)淼暮锰幨撬饕^小.這是一個平衡選擇,大家可以根據(jù)平時使用情況進(jìn)行調(diào)整.我們選擇了 disable doc_values 以減少索引大小.
在 1.x 升級到 2.x 的過程,基于集群只能滾動式升級,這決定 1.x 和 2.x 集群是同時共存.而在升級過程中,不幸躺著中槍,頻繁遇到 GC 問題,幾乎導(dǎo)致升級失敗.
首先我們嘗試了進(jìn)行 GC 調(diào)優(yōu),CMS,G1,調(diào)整 heap size,heap NEW size ….,各種策略均告失敗.調(diào)整 thread pool 各項參數(shù),對 query:size 過大數(shù)字也進(jìn)行調(diào)整,以減少 GC 壓力,這些調(diào)整也均失效.
具體表現(xiàn)為,運行一段時間后,集群中某些 Node 的 CPU Usage 會突然上升,最后 JVM 保持在 100% CPU Usage,集群 Node 因為長期下線,被集群踢出,如果運氣好,Node 還會回來,大部分情況下它就保持在 100% CPU Usage 不死不活.
檢查日志,并無 OOM,而顯示 GC 問題很大,在幾次 CMS GC (new heap) 后,發(fā)生 Full GC,并且 Heap 使用率一直保持 90% 左右,GC 進(jìn)入死循環(huán).
一開始,判斷是 GC 問題,故而一直進(jìn)行 GC 調(diào)優(yōu),未果.
當(dāng)我們遇到 JVM GC 時,很可能并非 GC 策略本身問題,而可能是應(yīng)用的 BUG.最后,我們不得不另尋出路.
Static 配置:
indices.queries.cache.size
indices.cache.filter.size
indices.queries.cache.size
indices.memory.index_buffer_size
Circuit breaker 配置:
indices.breaker.request.limit
indices.breaker.fielddata.limit
indices.breaker.total.limit
前者和后者中相關(guān)的配置需要保持前者小于后者.
調(diào)整這些數(shù)據(jù),未果.
我們的 Mapping 確實比較大,因為業(yè)務(wù)處理邏輯復(fù)雜,各種名字的字段沒有明確的限制,所以 Mapping 是比較大的.在 Mapping 很大的時候,當(dāng)一個新的字段進(jìn)行索引,每個索引都要進(jìn)行 mapping 更新,可能會導(dǎo)致 OOM.不過我們觀察到我們的 GC 問題和索引更新并沒有很明顯的聯(lián)系,因為我們在進(jìn)行索引初始化時,快速 Bulk 索引也只是 LA 比較大,并無 GC 問題,再一個在 1.x mapping 也沒有什么問題.
shards 過多,也是可能導(dǎo)致 GC 問題的.因為每個 shard 的內(nèi)存使用控制變得復(fù)雜.盡管我們某些集群的 shards 數(shù)量較多( shards 90 * 2 = 180 個 shard),但嘗試調(diào)整或合并 Shards,均告無果.
因為 GC 這種問題,所以我們嘗試減少 JVM 的內(nèi)存使用,降低 GC 壓力.啟用 doc_values后,Heap 內(nèi)存占用變小,但不能解決這個問題.減小 Heap 大小,以減輕 GC 壓力,也無法解決這個問題.
我們對 1.x 和 2.x 集群加上了版本區(qū)分.在 2.x 的情況下,我們對查詢進(jìn)行了強制修改.修改辦法就是上面提到的 Filtered Query 變更.即取消 filtered 而使用 bool 來進(jìn)行代替.GC 問題得到緩解.
我們經(jīng)過仔細(xì)對比 1.x 和 2.x,對于 aggs histogram 的默認(rèn)值變化(doc_min_count從1到0),一開始并沒有重視,后來顯式的設(shè)置這個參數(shù)為 1.GC問題得到解決.
上面的 5)6) 就是 GC 問題兩個很深的坑.
雖然他們算不上是 BUG,然而在 filtered query 只是 deprecated,而不是不能使用的情況下,這也太坑人了,遇到需要多集群滾動式升級的(比如我們),可能就會沿用 filtered query,以便能平滑升級,然后就會掉進(jìn)深坑而不能自拔.
而 6)也算不上是 BUG,不過對于 doc_min_count = 0,大概率會觸發(fā) GC,使用任何 GC 策略都不能正常使用.
1、Lucene version 在初期版本要顯式的在 mapping::settings 中配置.后來的版本沒有問題了.建議升級到較高版本以避免這種問題.
2、 aggerations 盡可能不要用在 analyzed fields,原因是 analyzed fields 是沒有 doc_values的,另外 analyzed fields 分詞之后,你進(jìn)行 aggerations 也只能得到 term 的統(tǒng)計結(jié)果.
3、如果修改文檔是增量的,并且不會帶來數(shù)據(jù)覆蓋問題,建議使用 update API(或 bulk update API),可接受部分?jǐn)?shù)據(jù)更新,而不需要一個完整文檔.
4、thread pool 調(diào)整.
如果一臺服務(wù)器內(nèi)存較大或者因為多集群原因需要配置多個 Elasticsearch JVM node,建議調(diào)整默認(rèn)的 threadpool.search.size (默認(rèn)值:int((available_processors * 3) / 2) + 1),比如默認(rèn)值為 24,此時這臺機器有 2 JVM node,可以根據(jù)各 node 大致的訪問量、訪問壓力在 24 / 2 = 12 上下調(diào)整.如果更配置更多的 JVM 以有效利用 CPU 和內(nèi)存,需要進(jìn)行這個調(diào)整.否則 JVM 可能奔潰而無法啟動.
5、count api (search api with size 0)
Count API 在某種情況下是很有效,比如當(dāng)你只想獲得 Total Count 的時候,可以使用這個 API.
不過,2.1 以后已經(jīng)使用 search API 并設(shè)置 size = 0 來代替了.新版本中 Elasticsearch Java 代碼中 Count API 已經(jīng)去除,但是應(yīng)用層面 _count 還是保留的.
6、timeout 參數(shù) 2.x 必須加上 s ,如 :? timeout = 3s
0、基本優(yōu)化: 包括 硬件(CPU、Memory、SSD)、JVM 及其版本選擇(Heap size,GC,JDK8)、系統(tǒng)配置(File Descriptors、VM/Virtual memory、Swap、Swappiness、mlockall).
我們使用多核服務(wù)器和大內(nèi)存,一定程度上可以彌補非 SSD 磁盤.
一臺服務(wù)器多個 JVM,版本為 JDK8;Heapsize 一般為 30G 以內(nèi),根據(jù)不同用途、索引大小和訪問壓力 ,Heapsize 有 5、10、20、30G 的不同配置,Heap NewSize 配置比較激進(jìn),通常大于 Heapsize 的一半;GC 選擇 CMS GC.
為了提供快速查詢,根據(jù)業(yè)務(wù)特點對集群進(jìn)行不同搭配,如用戶訪問(帶有 Uid)將指向到 UID 集群;查詢一個城市的二手手機將會指向到 city + second_category 集群;指定了類目的查詢將指向 city + second_category 集群的 first_category 索引(我們的特點是一級類目基本固定).
我們的信息特點是,信息描述內(nèi)容比較多,并且需要對描述內(nèi)容做全文索引.這樣會導(dǎo)致集群的索引大小非常大,需要占用的磁盤和內(nèi)存也就很多.從上文可知,我們根據(jù)業(yè)務(wù)特點劃分了不同的集群,如果每個集群都包含了信息描述內(nèi)容,索引都會很大,帶來成本的提高,也增加了維護(hù)難度.
我們業(yè)務(wù)另外一個特點是,全文索引查詢占所有類型查詢比例較低,所以一個大的集群可以提供全部全文索引查詢,那么另外的集群就可以不需要索引“信息描述內(nèi)容”,索引就大大的減小了.
我們還有采用時間來進(jìn)行區(qū)分的集群.基于某些業(yè)務(wù)對信息新鮮度敏感,所以可以獲取一周或二月的信息即可滿足需求.大大減少對 Full 類型集群的訪問壓力,也能提供快速訪問.
使用時間劃分集群后,還有一個好處,我們可以用二月的信息的集群來作為較小集群,讓查詢優(yōu)先訪問這個集群,當(dāng)數(shù)據(jù)滿足條件后,就不需要查詢 Full 集群;數(shù)據(jù)不足繼續(xù)查詢 Full 集群.大集群的訪問壓力進(jìn)一步降低.
這時,若查詢了較小集群,并且需要準(zhǔn)確的 Total Count (默認(rèn)提供一個 Mini 集群10倍的數(shù)字),可以進(jìn)一步使用 Count API (設(shè)置 size:0)去訪問 Full 集群.
這里和 4)不同的地方在于,上面使用的是時間劃分,而這里是業(yè)務(wù)劃分.這個集群只包含了特定數(shù)據(jù)的集群(比如二手大類目的二級類目手機),主要看相關(guān)查詢量是否很大,若是這類查詢帶來壓力較大,就有必要分出去.
我們的一個特點,第一至三頁幾乎是所有訪問的 80% 以上,所以這部分查詢我們構(gòu)造了一個 Cloud Query 池,用于提供快速訪問.這個池:
1)使用 DSL 查詢,查詢方法同 Elasticsearch.
2)初始化數(shù)據(jù)從 Elasticsearch 獲取.
3)保留了幾百個左右新鮮數(shù)據(jù).
4)不斷更新.
5)數(shù)據(jù)不足,查詢指向 Elasticsearh.
6)使用 Redis zset 存儲新鮮數(shù)據(jù) (Redis Cluster).
為實現(xiàn)上面的功能,我們使用 golang 語言開發(fā)一個 Proxy 類型的服務(wù)(代號 4Sea).
0、Lucene 6
“磁盤空間少一半;索引時間少一半;” ,Merge 時間和 JVM Heap 占用都會減少,索引本身的性能也提升.
“查詢性能提升25%;IPV6也支持了”.
1、Profile API,可以用來進(jìn)行查詢性能監(jiān)控和查詢優(yōu)化.不用再對耗時查詢兩眼一抹黑.
2、翻頁利器: Search After.search 接口的一個新實現(xiàn),使得你可以深度翻頁.這個彌補了 scroll 和 search 的不足.
3、Shrink API: 合并 Shards 數(shù),現(xiàn)在不用擔(dān)心 shard 數(shù)字設(shè)置不合理,你可以使用這個 API 去合并以減少索引 shards 數(shù)量.
4、Reindex,應(yīng)該是比較令人心動的 API,可惜需要啟用 _source.
5、更新數(shù)據(jù)的 wait_for refresh 特性,可能在某種用戶非異步更新時會有好處,讓用戶(更新接口)等待到更新完成,避免用戶得不到數(shù)據(jù)或者得到老數(shù)據(jù).
6、delete_by_query 重回 core !!!但是實現(xiàn)方式優(yōu)化了.
7、2.x 中 Deprecated 的功能在這個版本大多移除.
還有更多….
升級 2.x 成功,5.x 還會遠(yuǎn)嗎?看到上面的好處,我想大家都有強烈的升級沖動.
升級工具:
0.90.x /1.x => 2.x
https://github.com/elastic/elasticsearch-migration/tree/1.x
2.x => 5.0
https://github.com/elastic/elasticsearch-migration/tree/2.x
提問:對比下 Elasticsearch 和 Solr?為何貴司選了 ES?
王衛(wèi)華:當(dāng)初使用 Solr 的時候,Elasticsearch 還沒出現(xiàn).Elasticsearch 作為一個新出現(xiàn)的開源搜索引擎,有許多新特性,我們從 0.x 就開始使用,當(dāng)初最看好的是它的管理方便,插件多,接口設(shè)計好等比較人性化的特性.
提問:線上集群如何進(jìn)行不停機 reindex 的,這個過程在有數(shù)據(jù)不斷索引的情況下如何保證原有集群數(shù)據(jù)同新集群數(shù)據(jù)一致性?
王衛(wèi)華:Reindex 是一個高耗操作,所以一般情況下,最好不要提供服務(wù),但是如果索引比較小,這個操作帶來的壓力一般.索引大,大量的碎片會帶來很大的性能問題.所以我們一般對集群每天進(jìn)行 optimize(force merge).這樣在高峰期可以提供較好的性能.
我們現(xiàn)在因為通過 4Sea 的配置,可以讓任何比較清閑的集群承擔(dān)當(dāng)前 reindex 索引的查詢.
提問:GC 選擇 CMS,為何不選擇 G1 呢?
王衛(wèi)華:G1 的性能也很不錯.官方目前支持 CMS,認(rèn)為 G1 在 JDK8 還不算成熟.我們在試驗中得出的結(jié)論 G1 對比稍差一點,并不落后多少.
不過,如果你設(shè)置 heap size 大于 30G,我建議你使用 G1.小于 30G,CMS 比較好.
提問:百姓網(wǎng)的 es 集群是從一開始就切成多個了嗎?大體分為幾個,為什么如此切分?代理服務(wù)器上路由實現(xiàn)是如何進(jìn)行的?
王衛(wèi)華:我們一開始也只有一個集群,但是我們對性能有追求,幾百毫秒一個查詢是不可接受的.隨著數(shù)據(jù)越來越多,有些查詢頂不住,需要分而治之.才能提供快速訪問(毫秒級別).
切分的原則,我們上面講了,大致是:routing,時間,業(yè)務(wù),是否提供全文索引.
代理服務(wù)器對查詢進(jìn)行分析,然后導(dǎo)引到合適的集群,比如 week,month,業(yè)務(wù),并提供不同的routing.
提問:請問,32G 的物理內(nèi)存,慢慢越來越少,是否正常?怎樣做這方面優(yōu)化?
王衛(wèi)華:32G 的內(nèi)存,JVM 會使用一部分內(nèi)存.Lucene(系統(tǒng)緩存)會使用一部分.
越來越少是因為 Lucene 索引使用了內(nèi)存,還有一些可能是其他文件緩存.
一般處理原則是 JVM + 索引大小 < 物理內(nèi)存 即可.
提問:為何選 Golang 做 Proxy,不用 Java?
王衛(wèi)華:主要看中 Golang 的 goroutine 和編碼的簡單舒適感,第三方工具包也足夠多,使用過程中也沒有 GC 性能問題(至少我們使用中沒有這個問題).
題外:我們從 Go 1.4 直接跳到 Go 1.6,解決一些坑(比如升級后鎖變化問題),性能有很大的提升.
提問:怎么應(yīng)對網(wǎng)絡(luò)不穩(wěn)定對集群的影響,特別是集群意外斷電,導(dǎo)致 shard 的自動遷移,恢復(fù)時間長,從而導(dǎo)致集群不穩(wěn)定,在 2.x 版本對 shard 的均衡分布和自動遷移有沒有相關(guān)的更新?
王衛(wèi)華:網(wǎng)絡(luò)不穩(wěn)定的情況下,解決辦法就是提高 discovery.zen.ping.timeout 的時間,然而這樣提供快速查詢就比較傷.所以一個集群中保持一個穩(wěn)定的網(wǎng)絡(luò)環(huán)境還是很重要的.
要加快恢復(fù)時間而網(wǎng)絡(luò)帶寬允許的情況下,可以調(diào)整 cluster.routing.allocation 和 recovery 各項參數(shù),增加并發(fā),提高同時恢復(fù)的 node 數(shù),提高傳輸速率.
2.x 對 allocation,recovery 進(jìn)行了不少優(yōu)化.
文章出處:高可用架構(gòu)
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/4453.html