使用ElasticSearch踩過的坑

版權申明:

此文章首發於公眾號程序員在深圳,搜索 studycode 即可關注

本文無需授權即可轉載,轉載時請務必註明作者

使用ElasticSearch將近3個月了,在使用過程中,陸陸續續踩了不少坑,每次覺得無法逾越時,心裡都想放棄,一是因為這東西要完全掌握不是那麼容易,需要花很多時間;二是如果繼續使用曾經用過的zabbix,說不定可以很快滿足眼前的需求,從而可以抽身做其他事情。但堅持下來,就一定能從坑裡爬起來,從而對這個系統更加了解,並利用這頭」猛獸」幫助我做更多事情。原因很簡單,ElasticSearch除了是一個分散式資料庫,還是一個擴展性和可用性都很強的近實時搜索引擎。

目前為止,踩過以下幾個坑:

  1. 集群搭建不成功
  2. 未使用內網IP,導致恢復緩慢
  3. 未使用隊列及logstash,導致數據丟失
  4. Master和DataNode未分離,導致集群不穩定
  5. Logstash吞吐量問題
  6. Logstash如何創建Mapping
  7. head插件安裝錯誤

犯了這麼多錯誤基本上都是使用不當、以偏概全的原因,以為看了一點文檔,就憑直覺可以猜測到系統的所有內容,造成了後續問題的不斷湧現,下面就逐一說一下淌坑過程

集群搭建不成功

一開始是在單機上玩ElasticSearch的,上生產環境肯定要使用它的集群功能,但文檔說只需要在elasticsearch.yml中設置cluster.name和node.name即可,ElasticSearch節點啟動時會自動發現集群並加入到集群,但全部設置完畢後,竟無法使各個節點組成集群,最後發現這種方法只在一台機器上有效,而要組成集群,需要在每台節點做以下配置:

discovery.zen.ping.unicast.hosts: ["Node1_IP:9300", "Node2_IP:9300", "Node3_IP:9300"]n

ElasticSearch一般會開兩個埠,一個是集群維護埠,如上面的9300; 一個是對外提供的埠,默認是9200。

未使用內網IP,導致恢復緩慢

在部署集群時,我挑選了幾台配置相近的同網段機器,但當時其中一台機器操作系統沒有載入內網網卡,為了偷一下懶,便直接使用了外網IP,集群是跑起來了,運行了一段時間也沒有什麼問題,但隨著流量越來也大,終於有一天Master突然down掉了,我當時心想,ElasticSearch集群本身具有故障轉移功能,馬上會分配一個Master節點,然後我只需要把原先的Master節點重啟即可,然而重啟了之後,通過head頁面查看恢復情況,發現集群長時間處於黃色狀態(有replica shard丟失),而丟失的shard一直處於未分配狀態,並沒有如我預期的:啟動後,該節點可以重用在磁碟上的shard數據,上報給Master,不需要數據拷貝,立馬恢復為綠色狀態。過了幾分鐘後,我發現恢復的shard數正以緩慢的速度增加,便推導出這樣的錯誤結論:ElasticSearch在某一台機器掛掉後,只會從primary shard複製數據,即便節點迅速恢復,也不會復用該節點上的數據,如果是這種實現方式,我認為這種恢復速度是無法接受的,頓時產生了無法繼續使用下去的念頭,當時的心情是無比失落的。

而ElasticSearch的使用是相當廣泛的,我們熟知的世界最大同性交友網站也是使用它來實現搜索功能的,所以可以斷定是我的使用方法有問題,要麼是我選的版本不穩定,要麼是其他的原因,而穩定版一般出問題的幾率不大,因此在換版本之前,需要找其他方面的原因,很久之前看過一篇google的slide,上面有一頁介紹了不同介質數據的傳輸速度:外網的傳輸速度在10ms級別,而內網卻在20微秒級,這種速度的差異便會造成以下幾個方面的影響

  • 集群無法提供正常服務:因為每個請求,ElasticSearch節點都會經過轉發和收集兩個過程,如果使用外網網卡,便會造成延遲大,訪問量上不去,而流量到達一定程度後,集群很快便無法提供正常服務
  • 由於ES集群已經無法正常服務,所以down機、恢復困難一系列綜合症的情況便會陸續發生

後續我將集群切到了內網中,再測試重啟某一個節點,便不會再出現恢復一個節點需要半天的情況了。

未使用隊列或logstash,導致數據丟失

最初用到的架構非常簡單: 使用ES(ElasticSearch縮寫)集群作為存儲,beats和rsyslog作為shipper向ES集群發送數據,使用這種架構的主要原因是配置簡單,ES本身是一個高可用集群,直接把數據發過去就好。而自己心裡還產生了為什麼會有ELK架構,感覺Logstash是多餘的想法,在發生了幾次down機之後,才發現之前的想法很傻很天真,之前的架構也有明顯的問題

  • 在某一個節點down掉後,如果不馬上恢復,在不了解beats負載均衡機制的前提之下,很難判斷數據還會不會發送給down掉的節點,而新增一個節點,需要修改所有beat的配置,即這裡至少要使用一個負載均衡器給所有ES節點做負載均衡
  • ES是一個高可用集群,但目前還沒有足夠的使用經驗,所以可能今後還會出現集群故障的問題,而出故障,很可能造成數據的丟失,為了避免這種情況發生,需要在beats和ES集群之間構建一套可持久化的隊列,最簡單的隊列是redis,而logstash放在redis兩邊分別作為生產者和消費者。想到的方案便是beats->logstash->redis->logstash->ES,這樣便解決了丟數據的問題,當然最新版的logstash可以將數據持久化到磁碟上,也許可以對此模型進行簡化

Logstash和Redis的使用都非常簡單,這裡就不一一介紹,值得注意的是,如果要使用redis做持久化,需要使用Redis的List的方式,而不是Sub-Pub的方式,以下是具體的架構圖,箭頭的方向是數據流動的方向。

Master和DataNode未分離,導致集群不穩定

在ES集群中,節點分為Master、DataNode、Client等幾種角色,任何一個節點都可以同時具備以上所有角色,其中比較重要的角色為Master和DataNode:

  • Master主要管理集群信息、primary分片和replica分片信息、維護index信息。
  • DataNode用來存儲數據,維護倒排索引,提供數據檢索等。

可以看到元信息都在Master上面,如果Master掛掉了,該Master含有的所有Index都無法訪問,文檔中說,為了保證Master穩定,需要將Master和Node分離。而構建master集群可能會產生一種叫做腦裂的問題,為了防止腦裂,需要設置最小master的節點數為eligible_master_number/2 + 1

腦裂的概念:

如果你有2個Master候選節點,並設置最小Master節點數為1,則當網路抖動或偶然斷開時,2個Master都會認為另一個Master掛掉了,他們都被選舉為主Master,則此時集群中存在兩個主Master,即物理上1個集群變成了邏輯上的2個集群,而當其中一個Master再次掛掉時,即便它恢復後回到了原有的集群,在它作為主Master期間寫入的數據都會丟失,因為它上面維護了Index信息。

根據以上理論,我對集群做了如下更改,額外選取3個獨立的機器作為Master節點,修改elasticsearch.yml配置

node.master = truennode.data = falsendiscovery.zen.minimum_master_nodes = 2n

修改其他節點配置,將其設置為DataNode,最後挨個重啟

node.master = falsennode.data = truen

Logstash吞吐量問題

在使用了新的架構後,我發現了當流量上來後,Redis的隊列會持續增長,消費速度跟不上生產速度,造成的問題是數據在Redis中堆積,圖表展示有大量的延遲。解決這個問題有以下幾個思路

  1. 可能是ES插入速度太慢,需要調整參數提升插入性能
  2. 可能是Logstash吞吐量低,需要增加每次向Redis拿數據的緩存、增加向ES輸出的緩存、增加線程數、增加每次批量操作的content length等

對於ES調優中,我調整了線程數,增加線程隊列,增大shard數,但都沒有解決問題。

而Logstash調優,我首先調整了LS_HEAP_SIZE參數,讓Logstash可以同時處理大量的數據,然後主要專註在調整Logstash的Input和Output插件參數上,插件中可以設置線程數、batch_count數值等,而當我將Redis插件參數改為batch_count=>10000後,發現隊列不再一直增長了,它會漲到一定程度後,瞬間減少到2-3位數,即隊列的長度在一定範圍內浮動,當時欣喜若狂,以為自己解決了,但跑了大概5個小時候,發現隊列又開始不斷增長了,問題並沒有得到解決。而產生解決了的假象應該是我增加了Logstash內存的原因,數據只是先把Logstash內存填滿,再開始填隊列,而填滿Logstash內存花了幾個小時,關鍵的Logstash到ES的吞吐量還是沒有上去,在access日誌中,無論如何也無法讓bulk API的content length增加,如下圖中的長度一直維持在2K左右。

最後,我採用了替換Logstash版本的策略,更新了時下最新的5.1.1版本,由於新版的配置和舊版配置不一樣,所以認真研究了一下配置,在這個過程中,我發現了一個-b參數可以修改批量插入的大小,也許就是我需要的。果然,將這個參數由默認的125改為了1000,順利的解決了這個難題,同時也證明了並不是版本問題,還是使用問題,而這個參數也正是修改content length的方法,順便說一下,如果你使用nginx作為負載均衡器,你需要同時增加client_max_body_size參數,避免產生content length過大而返回413錯誤碼。

Logstash如何創建Mapping

當使用Logstash進行轉發時,有可能你的數據都在一個Index中,當然你也可以設置不同的Index,這篇文章中就有根據type來劃分Index的方法,不管劃不劃分Index,都會默認生成一個或多個mapping結構,mapping結和不同的type即對應MySQL中的資料庫和表結構信息,當然我這裡不是為了說明它們的區別,而是我們無法自定義欄位的類型。

這會產生各種各樣的問題,比如它會默認產生analyzed類型的string欄位,會自動將帶有連接符的字元串分為兩個字元串輸出,即"idc-1"這樣的字元串會輸出為"idc"和"1",這並不是我想要的,讓我相當困擾,而Mapping在生成後是無法修改欄位的,除非你換一個新的欄位。

解決這個問題的方法並不在mapping上,而我卻花了很多時間在這個上面,最終答案卻是使用template,在template中可以定義你需要的mapping,這樣便解決以上問題。到此,我還是不能完全理解裡面的機制,以後抽空了解後再補上。

head插件安裝失敗

上文有介紹head插件,它是一個可以顯示集群狀態及操作ES集群的UI,可以取代官方的X-Pack,後者只有30天的試用期,因為創業公司,能用免費的盡量採用免費的。在集群中,有幾個節點安裝該插件會失敗,提示:Unable to veryfy checksum for download plugin ...,google上查了一圈仍然沒有找到解決辦法,最後試著手動將該插件下載然後解壓到/usr/share/elasticsearch/plugins/目錄下,並將目錄改為head即可解決該問題。

以上問題是我這段時間來碰到的坑,每個都花了不少時間去解決,自己也比較幸運,花在上面的時間沒有白費。因為個人覺得這個技術棧實在是比較好,而資料主要以英文的為主,把自己的經歷寫下來,希望今後不再犯同樣的錯誤,也希望可以幫助其他使用該技術的同學。


推薦閱讀:

Elasticsearch 5:集群設置與管理(第二篇)
從Elasticsearch來看分散式系統架構設計
Elasticsearch與Solr的選擇?
日誌分析的模式發現功能實現(1)

TAG:Elasticsearch | 后端技术 | 运维 |