FPs

ElasticSearch 用於日誌記錄

EdgeofSanity

  • 原文地址
    • http://edgeofsanity.net/article/2012/12/26/elasticsearch-for-logging.html
  • 已獲得原文作者翻譯許可,本文遵循原文許可協議。
  • 原文發表時間:2012-12-26

在我工作中,我們在Web 前端搜索上使用ElasticSearch。性能至關重要,對我們而言,大多數的數據是靜態的。
我們每天更新搜索索引,不過用舊的索引跑幾個星期也沒問題。這個集羣大部分的流量是搜索;這是一個“重讀”的集羣。一開始的時候我們有一些性能上的小問題,
不過我們通過和ElasticSearch 的Shay Bannon 密切合作解決了這些問題。現在我們的前端集羣非常可靠,靈活,和快速。

我目前的工作是部署一個符合規範,同時也非常有用的中心化的日誌基礎服務。這個日誌基礎服務的目標是儘可能的實現Splunk 的功能。我之前寫的一篇關於日誌記錄的文章解釋了我們爲什麼決定不用Splunk

評估了一些選擇之後,我決定使用ElasticSearch 作爲這個系統的後端存儲。這個集羣的類型和我們之前部署的用於大量搜索的集羣非常不同

譯文
EveryCloud 貢獻的一篇俄語譯文

索引設計

兩個流行的開源日誌分發系統是Graylog2LogStash。在寫這篇文章的時候,穩定版本的Graylog2 只支持從單一的索引讀/寫。正如送之前文章中指出的,Graylog2 存在很多嚴重的問題。0.10.0 版的Graylog2 將包含聯合多個索引的能力。然而,我已經在LogStash 的索引上有了一些經驗,因爲之前它是唯一的選擇。

爲了儘可能得充分利用ElasticSearch 用來做日誌記錄,你需要使用多個索引。當你滾動更新索引的時候有非常多種方式,不過LogStash 默認的每天滾動更新的方式是最合理的。這樣,你會看到下列內容:

  • logstash-2012.12.19
  • logstash-2012.12.20
  • logstash-2012.12.21
  • logstash-THE_WORLD_HAS_ENDED

你可以跟蹤每個索引中有多少個文本。在百萬或者千萬個文本之後滾動更新,或者其他你隨意設定的數值之後,但是這樣你會給你自己以後帶來更多的工作內容。在某些少見的情況下其他的索引方式可能會更高效,但是對於大多數用戶來說,一天一個索引是最簡單的,也能最有效的利用你的資源。

認真點,不然就回家待着

LogStash 和Graylog2 都附帶內置的ElasticSearch。這對示範演示和開發來說非常方便。用於真正用途的時候不要使用內置的服務器!我非常驚奇有大量的LogStash 和Graylog2 用戶使用了內置的ElasticSearch 存儲引擎,最後在irc.freenode.org 的 #elasticsearch 頻道裏對服務崩潰感到奇怪。

請運行一個獨立的ElasticSearch 集羣!

你將需要爲集羣劃劃分硬件。像LogStash和ElasticSearch 這樣的Java 應用是內存和磁盤緩存密集型的。保證一批硬件是用來作爲日誌處理的箱子,然後劃分這些箱子給ElasticSearch 集羣。Java 有一些奇怪的內存問題。我們發現你不會想把超過32GB內存給ElasticSearch,預留至少8GB 給操作系統的文件系統緩存。

在我的開發環境中,我的集羣每天處理大約60GB 的日誌資料,集羣一共有三個搜索節點,每個節點24GB 內存,毫無壓力。這引發下一個問題,我的集羣需要多少台服務器?開始先在你的ElasticSearch 集羣中部署3台服務器。這樣你可以靈活的關閉一台服務器,維持你的集羣能充分得被使用。你可以隨時添加更多的硬件!

安裝ElasticSearch

我不打算覆蓋安裝ElasticSearch 的所有內容,你可以在文檔站點上讀到更多關於這方面的內容。你可能決定通過PuppetChef 利用 .deb 包或rpm 包的方式創建一份ElasticSearch 的部署腳本。關於安裝我會說的唯一一件事是,無論有多不爽,最好還是用Sun JVM 運行ElasticSearch。這是ElasticSearch 的開發人員運行ElasticSearch 的方式,所以你也應該這樣。

ElasticSearch 配置:OS 和 Java

主機系統上有一些地方你真的需要配置一下。我假定你的主機系統上運行的是Linux 系統。你應該使用非特權用戶來運行ElasticSearch。我們的集羣是用'elasticsearch' 用戶來運行的,另外我們調整了/etc/security/limits.conf中內核對進程和內存上的限制:

# Ensure ElasticSearch can open files and lock memory!
elasticsearch   soft    nofile          65536
elasticsearch   hard    nofile          65536
elasticsearch   -       memlock         unlimited

你也應該把ElasticSearch 最小和最大的內存池設置爲一樣的值。這樣使得啓動時就能分配到總內存,這樣線程需要更多的內存的時候不需要等待內核分配。我在一個RedHat 系統上部署了ElasticSearch,/etc/sysconfig/elasticsearch中的配置會在daemon 進程啓動時設置好環境變量:

# Allocate 14 Gigs of RAM
ES_MIN_MEM=14g
ES_MAX_MEM="$ES_MIN_MEM"

這份文件由Puppet 管理,並設置內存大小等於50% 的RAM 大小,寫2遍。這沒什麼難的,在每份ElasticSearch 優化指南中都會提到。

ElasticSearch 配置:elasticsearch.yml

elasticsearch.yml 文件中有一些調優的事情我們可以做,可以極大的改進重寫的節點的性能。首先是設置bootstrap.mlockall 爲 true。這會強制JVM 立即分配所有的ES_MIN_MEM。這意味着Java 在啓動時就有了所有所需的內存!重寫的集羣的另外一個讓人擔心的點是indexing/bulk 引擎分配內存時不均衡的問題。

ElasticSearch 是假定你將大量使用搜索,所以分配了大部分的內存用於保障搜索。這不是我的集羣的使用情況,所以調整indices.memory.index_buffer_size到50%,從而恢復在我們使用方式下內存分配的平衡。在我的設置裏,我還提高了刷新間隔和日誌刷新的傳輸總數。否則,ElasticSearch 將需要接近每分鐘都刷新傳輸日誌。

另外一個需要我們調整以避免災難性的失敗的地方是線程池的設置。ElasticSearch 會做它認爲能獲得最好性能的事情。我們發現,在生產環境中,這意味着生成成千上萬的線程來處理傳入的請求。在高負載下,你的集羣很快會被壓垮。爲了避免這種情況,我們分別設置了search,index 和bulk 線程池的最大值。我們主要的操作是bulk,所以給bulk 設置了60個線程,其他的操作設置了20個線程。我們也設置了bulk 隊列能處理的最大請求數爲200,其他的各爲100。這樣一來,如果集羣過載,它會拒絕新的請求,不過它會留給你足夠的文件描述數和PID 來ssh 到主機中找出問題所在。

總結以上內容,這是我的配置文件

##################################################################
# /etc/elasticsearch/elasticsearch.yml
#
# Base configuration for a write heavy cluster
#

# Cluster / Node Basics
cluster.name: logng

# Node can have abritrary attributes we can use for routing
node.name: logsearch-01
node.datacenter: amsterdam

# Force all memory to be locked, forcing the JVM to never swap
bootstrap.mlockall: true

## Threadpool Settings ##

# Search pool
threadpool.search.type: fixed
threadpool.search.size: 20
threadpool.search.queue_size: 100

# Bulk pool
threadpool.bulk.type: fixed
threadpool.bulk.size: 60
threadpool.bulk.queue_size: 300

# Index pool
threadpool.index.type: fixed
threadpool.index.size: 20
threadpool.index.queue_size: 100

# Indices settings
indices.memory.index_buffer_size: 30%
indices.memory.min_shard_index_buffer_size: 12mb
indices.memory.min_index_buffer_size: 96mb

# Cache Sizes
indices.fielddata.cache.size: 15%
indices.fielddata.cache.expire: 6h
indices.cache.filter.size: 15%
indices.cache.filter.expire: 6h

# Indexing Settings for Writes
index.refresh_interval: 30s
index.translog.flush_threshold_ops: 50000

# Minimum nodes alive to constitute an operational cluster
discovery.zen.minimum_master_nodes: 2

# Unicast Discovery (disable multicast)
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: [ "logsearch-01", "logsearch-02", "logsearch-03" ]

ElasticSearch 配置: 索引模板

一開始的時候,我是基於LogStash 來部署集羣,由於那時Graylog2 部署上的缺點。這一節內容主要關於“logstash”,但同樣也適用於Graylog2 或其他自定義的索引映射(譯註:Mapping)。

因爲我們決定一天創建一個索引,這就有兩種方式配置每個索引的映射和特性。我們可以按照我們的需求配置明確得生成索引,也可以使用一個包含我們需要的配置和特性的模板來生成每個索引!模板在這種情況下最方便,我們可以在運行的集羣上創建它們!

我的模板配置如下:

{
    "template": "logstash-*",
    "settings" : {
        "index.number_of_shards" : 3,
        "index.number_of_replicas" : 1,
        "index.query.default_field" : "@message",
        "index.routing.allocation.total_shards_per_node" : 2,
        "index.auto_expand_replicas": false
    },
    "mappings": {
        "_default_": {
        "_all": { "enabled": false },
        "_source": { "compress": false },
        "dynamic_templates": [
            {
                "fields_template" : {
                    "mapping": { "type": "string", "index": "not_analyzed" },
                    "path_match": "@fields.*"
                }
            },
            {
                "tags_template" : {
                    "mapping": { "type": "string", "index": "not_analyzed" },
                    "path_match": "@tags.*"
                }
            }
        ],
        "properties" : {
            "@fields": { "type": "object", "dynamic": true, "path": "full" },
            "@source" : { "type" : "string", "index" : "not_analyzed" },
            "@source_host" : { "type" : "string", "index" : "not_analyzed" },
            "@source_path" : { "type" : "string", "index" : "not_analyzed" },
            "@timestamp" : { "type" : "date", "index" : "not_analyzed" },
            "@type" : { "type" : "string", "index" : "not_analyzed" },
            "@message" : { "type" : "string", "analyzer" : "whitespace" }
        }
        }
    }
}

要在集羣上應用配置,我們可以通過一個PUT 來創建和更新模板:

curl -XPUT 'http://localhost:9200/_template/template_logstash/' -d @logstash-template.json

將模板設置爲logstash-* ,意味着配置將應用到所以'logstash-' 開頭新建的索引。我通過禁用對_all 字段進行搜索並將默認的屬性修改爲@message,覆蓋了原來的默認搜索行爲(譯註:默認會對全部字段進行搜索,作者禁用了這種行爲,修改爲只對message 字段進行搜索)。這個字段(譯註:指message 字段)將包含原生的syslog 消息。這也是唯一一個禁止分析的字段。不要不安。這能節省空間和建立索引的時間。這意味着搜索文檔中其他字段時只能使用精確搜索,而不能使用模糊搜索,不過這是OK 的。我們依然可以對@message 字段進行模糊搜索!這將極大的降低存儲空間。

在之前的文章中,ElasticSearch 0.19 之前,你可能見過設置了"_source": { "compress": true } 。這對日誌類的數據是不推薦的。這個屬性決定了每份文檔(日誌消息)是否進行壓縮存儲。由於這些文件往往非常小,壓縮並不能真正的節省很多的空間。在進行索引和檢索的時,這會帶來額外的時間成本。對記錄日誌的集羣來說最好禁用壓縮。我們elasticsearch.yml中啓用的壓縮是使用塊級別的壓縮方式,更加高效。

索引配置

這份索引的配置是爲一個3個節點的集羣調優的。我們可以修改配置中的任何項,但是如果我們需要擴容或者縮容時,index.number_of_shards需要立即修改。這份配置不是絕對完美的,因爲我們有時最後會有一些孤兒(未分配)的分片。通過ElasticSearch API 來移動分片可以很方便的糾正這些錯誤。

我們隨着添加節點而提升存儲能力,而不是複製整個索引到整個集羣。這樣我們有一個類似“RAID” 的分配分片的設置。我有一個三個節點的集羣,每個索引我創建3個分片。這意味着主分片或者“寫”分片可以分攤到每一個節點。爲了冗餘,我設置副本數爲一。這意味着每個索引有6個分片。每個節點只需要有每個索引的2個分片。

你需要進行實驗來找到你需要的配置。在失去功能前,考慮一下損失多少個節點是你能夠承受的。你需要基於這個調整副本數量。我採用了一個簡單的做法,即用1個分片備份。這意味着我一次只能從集羣中下掉一個節點。到目前爲止,我發現number_of_replicas 等於 (2/3 * 節點數) -1 是一個不錯的數字,每個人的情況可能不同。

自動擴充副本

最好也禁用ElasticSearch 默認會基於集羣中有多少節點就擴充多少個副本的行爲。我們假定手工管理這個能提高性能,特別是當我們需要停止或重啓一個集羣中的節點時。自動擴充是一個很不錯的特性,對於重搜索的、有着小到中量的數據的索引來說。無需配置,添加一個幾點就能提高性能。然而,如果你的索引有着大量的數據,並啓用了這個特性,當一個節點重啓時,將會發生下列事情:

  • 一切正常。副本(replica)數 = 1
  • 節點A 關閉
  • 集羣注意到節點下線,狀態變成黃色(譯註:elasticsearch 集羣健康狀態用三種顏色表示,紅、黃、綠,具體可參考Elasticsearch "Yellow" cluster status explained
    • 副本數 = 0, 期望值爲1
    • 目前節點數爲1
    • 目前副本數期望 = 0
  • 集羣健康狀態上升爲綠色,一切完美
  • 節點A 重新上線
  • 集羣發送期望的副本數和實際的索引個數
  • 節點A 它的副本是不必要的,於是刪除了數據
  • 集羣增加了節點數,期望的副本數 = 1,實際 = 0
  • 節點A 被通知它的副本數不滿足要求
  • 節點A 通過網絡複製所有副本加到它的索引中

正如你看到的,這不太理想,特別是在一個非常繁忙的集羣裏。請小心你的生產環境中的操作,當你從集羣中添加/刪除節點時請注意你的網絡設備監控圖。如果你看到尖峯,你可能需要手動進行處理。你失去了一些魔法,不過你可能發現這些是黑魔法。禁用副本的自動擴充,會出現下列情況:

  • 一切正常
  • 節點A 關閉
  • 集羣注意到節點下線,狀態變成黃色
  • 集羣健康狀態未恢復,期望的副本數 != 實際副本數
  • 節點A 重新上線
  • 集羣發送期望的副本數和實際上所有的索引個數
  • 節點A 通知集羣它有分片的拷貝
  • 集羣期望和實際上的副本數相等,健康狀態變綠
  • 集羣校驗分片,和複製任何過期的分片給節點A

這才是大多數人民期望集羣默認該做的,但是判定集羣狀態之間的邏輯關係讓這很難實現。再說一次,auto_expand_replicas的魔法動作在大多數情況下是有用的,不過不適用與我們的情況。

維護和監控

我爲工作在生產環境中的ElasticSearch寫了一些腳本 。它包含傳送標準值給Graphite 和Cacti。這兒也有一份Nagios 的監控檢測腳本,非常容易配置。我們使用這些工具來跟蹤我們多個集羣的性能和健康狀態,包括日誌記錄集羣。過幾天,我會更新一下,將包含我們logstash 索引維護的腳本。

當你將數據寫入日誌集羣時,ElasticSearch 同時在後臺創建Lucence 索引。收到的文檔有一個緩衝區,基於你的設置,數據會被刷新到一個Lucene 索引。Lucene 索引新建/更新 的成本非常高,但是搜索非常。這意味這單個分片可能包含上百個Lucence 索引,常常被稱爲段。這些段可以被很快速的搜索,但是每個段只能被一個線程處理。這可能對性能有負面影響。我們已經觀察到20多個段的索引,有10%的性能下降。

幸運的是,ElasticSearch 提供了一個API 優化Lucene段。你不應該優化一個正在索引數據的索引。在這些分片上,新數據將會生成更多的段。那麼我們如何知道已經寫完一個索引了呢?如果你還記得,我建議按天建立索引。這意味着,你可以運行一個每天的cron 任務(或每小時)來檢查昨天或者更久之前的索引,確保他們已經被優化(或max_num_segments = 1)。如果你已經爲創建索引的名字選擇了一些其他模式,那麼你剛剛給自己增加加了很多工作。

未來的探索

這篇文章比我想象的長很多。我只是對設計和實踐用於日誌記錄的ElasticSearch 集羣做了表面的研究。我的集羣很快會從開發環境遷移到生產環境(儘管它正在提供生產環境的功能)。當我這樣做的時候,我將面對更多的挑戰,我有一個筆記本寫滿了關於如何構建索引的方法,關於集羣如何處理負載,和其他當你突然提供簡單、快速的訪問大量的數據時候出現的一些隱私相關的問題。

This article is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
If you reprint it, please indicate the source: http://fangpeishi.com/elasticsearch-for-logging_zh.html