分佈式框架-日誌系統思路及實現

轉自:https://www.jianshu.com/p/ce30c31111calinux

背景

隨着互聯網時代數據規模的爆發式增加,傳統的單機系統在性能和可用性上已經沒法勝任,分佈式應用和服務化應用開始走進你們的視野,可是分佈式的部署也會帶來另外的問題,日誌分散在各個應用服務節點中,出現問題不方便及時排查,尤爲是服務化的應用中,分析問題時可能須要查看多個日誌文件才能定位問題,若是相關項目不是一個團隊維護時溝通成本更是直線上升,怎麼將日誌文件歸集,怎麼將日誌文件呈現成了不少公司須要面對的問題,所以日誌系統應運而生。git

dapeng日誌系統的選型

日誌系統一般有三部分組成,採集器、解析器、存儲器github

採集器一般部署在各個應用結點中,它監控本地文件的變化,對於新產生的日誌變化,它實時收集併發送給對應的解析器,常見的採集器有flume、logstash、fluentd以及更輕量級的fluent-bitsql

解析器一般和採集器結合在一塊兒,也有一部分解析器是經過接收緩衝隊列,將日誌解析成json格式數據後,把數據發往存儲器進行存儲docker

存儲器用於存儲對應的數據,提供相關的查詢,常見的存儲有hdfs、elasticsearchjson

咱們dapeng選取的是fluent-bit+fluentd+kafka+elasticsearch做爲日誌系統的方案,zookeeper、elasticsearch、kafka都採用集羣模式,示例圖中採用單結點fluent-bit收集各個docker容器中的日誌文件發往fluentd,fluentd作爲中轉收集全部的日誌發往kafak用於削峯填谷,削峯後的數據再經由fluentd發送給elasticsearch進行存儲後端

 
image

爲了支持fluent-bit<=>fluentd的高可用, 咱們改動了fluent-bit的源碼. 另外, 生產環境上, 上述結構圖中的每個環節都不能省, 以避免數據量太大發生不可預料的錯誤.
目前咱們生產環境, 小規模應用的狀況下, 天天大概產生5千萬條日誌記錄.api

關於MDC的小插曲

Logback中有一項功能很好使-MDC,映射診斷環境(Mapped Diagnostic Context)MDC本質上是使用的ThreadLocal。系統調用鏈可能很長,爲了方便日誌跟蹤,統一打印標識。咱們dapeng使用MDC來保存sessionTid,在一個完整的調用鏈中使sessionTid在各個服務中進行傳遞,將服務進行串聯,方便問題定位,具體的logback以下tomcat

<appender name="SIMPLEFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <prudent>false</prudent> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${soa.base}/logs/simple-dapeng-container.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{MM-dd HH:mm:ss SSS} %t %p [%X{sessionTid}] - %m%n</pattern> </encoder> </appender> 

配置採集器

[SERVICE]
    Flush        5
    Daemon       On
    Log_Level    error
    Log_File     /fluent-bit/log/fluent-bit.log Parsers_File parse_dapeng.conf [INPUT] Name tail Path /dapeng-container/logs/*.log Exclude_Path /dapeng-container/logs/fluent*.log,/dapeng-container/logs/gc*.log Tag dapeng Multiline on Buffer_Chunk_Size 2m buffer_max_size 30m Mem_Buf_Limit 32m DB.Sync Normal db_count 400 Parser_Firstline dapeng_multiline db /fluent-bit/db/logs.db [FILTER] Name record_modifier Match * Record hostname ${HOSTNAME} Record tag ${serviceName} [OUTPUT] Name Forward Match * Host fluentd Port 24224 HostStandby fluentdStandby PortStandby 24224 

record_modifer用於在解析出的json中增長hostname標識和tag標識方便日誌檢索
chunk及buffer塊的設置根據各系統日誌的大小來進行設置
HostStandby和PortStandby是咱們dapeng基於原生fluent-bit進行改造添,當主fluentd掛掉後,日誌事件會相應的發送給fluentdstandBy進行處理ruby

解析器的配置

[PARSER]
    Name        dapeng_multiline
    Format      regex
    Regex       (?<logtime>\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2} \d{1,3}) (?<threadPool>.*) (?<level>.*) \[(?<sessionTid>.*)\] - (?<message>.*) 

解析器這塊對應上面的logback配置,將日誌消息處理成比較直觀的JSON數據進行存儲

轉發器fluentd的配置(用於接收消息發送kafka)

<system>
        log_level error
        flush_thread_count 8
        workers 8
</system>
<source> @type forward port 24224 </source> <source> @type monitor_agent port 24225 </source> <match dapeng tomcat> @type kafka_buffered brokers ${kafkabrokers} topic_key messages #zookeeper 192.168.20.200:2181 buffer_type file buffer_path /tmp/buffer flush_interval 60s default_topic messages output_data_type json compression_codec gzip max_send_retries 3 required_acks -1 discard_kafka_delivery_failed true </match> 

monitor_agent是fluentd的一個插件,能夠及時獲取fluentd響應用於fluentd的健康度檢查

[root@monitor-elk etc]# curl 192.168.20.200:24225/api/plugins.json {"plugins":[{"plugin_id":"object:3ff681f97a88","plugin_category":"input","type":"forward","config":{"@type":"forward","port":"24224"},"output_plugin":false,"retry_count":null},{"plugin_id":"object:3ff681c37078","plugin_category":"input","type":"monitor_agent","config":{"@type":"monitor_agent","port":"24225"},"output_plugin":false,"retry_count":null},{"plugin_id":"object:3ff681c19ca8","plugin_category":"output","type":"kafka_buffered","config":{"@type":"kafka_buffered","brokers":"192.168.20.200:9092","topic_key":"messages","buffer_type":"file","buffer_path":"/tmp/buffer","flush_interval":"60s","default_topic":"messages","output_data_type":"json","compression_codec":"gzip","max_send_retries":"3","required_acks":"-1","discard_kafka_delivery_failed":"true"},"output_plugin":true,"buffer_queue_length":0,"buffer_total_queued_size":1174144,"retry_count":6,"retry":{}}]} 

轉發器fluentd的配置(用於接收kafka中的消息發送elasticsearch)

<system>
        log_level info
        flush_thread_count 8
        workers 8
</system>
<source> @type kafka_group brokers 192.168.20.200:9092 consumer_group dapeng_consume2 topics messages format json start_from_beginning false </source> <source> @type monitor_agent port 24225 </source> <match> @type elasticsearch host 192.168.20.200 port 9200 index_name dapeng_log_index type_name dapeng_log content_type application/x-ndjson buffer_type file buffer_path /tmp/buffer_file buffer_chunk_limit 30m buffer_queue_limit 512 flush_mode interval flush_interval 60s request_timeout 15s flush_thread_count 8 reload_on_failure true resurrect_after 30s reconnect_on_error true with_transporter_log true logstash_format true logstash_prefix dapeng_log_index template_name dapeng_log_index template_file /fluentd/etc/template.json num_threads 8 utc_index false </match> 

start_from_beginning默認爲true,表明從消息隊列中起始讀取數據,當fluentd重啓會形成日誌消息冗餘,所以這裏配置false,若是須要恢復日誌索引,能夠配置成true讓日誌消息再消息一次(咱們日誌kafka消息保留的策略是保留1天,所以當出現故障時咱們能夠快速恢復1天內的日誌)
logstash_format 用於配置將日誌索引按天數來存放

template.json模板配置

{
 "mappings": { "dapeng_log": { "properties": { "logtime": { "type": "date", "format": "MM-dd HH:mm:ss SSS" }, "threadPool": { "type": "string", "index": "not_analyzed" }, "level": { "type": "string", "index": "not_analyzed" }, "tag": { "type": "string", "index": "not_analyzed" }, "message": { "type": "string", "index": "not_analyzed", "ignore_above":256 }, "hostname":{ "type": "string", "index": "not_analyzed" }, "sessionTid":{ "type": "string", "index": "not_analyzed" }, "log":{ "type": "string", "index": "not_analyzed" } } } }, "settings": { "index": { "max_result_window": "100000000", "number_of_shards": "1", "number_of_replicas": "1", "refresh_interval": "60s" } }, "warmers": {}, "template": "dapeng_log_index-*" } 

配置使用的是es2的配置,線上咱們使用的是5.6.9的版本,es這塊能夠向下兼容將string類型的轉換爲keyword

日誌查詢

查詢服務調用關係

經過sessionTid來查詢服務間的調用關係,這裏sessionTid正是上面MDC中設置的,在服務的調用中經過InvocationContext(dapeng上下文)進行傳遞

 
服務調用關係.png

查詢堆棧異常

 
堆棧異常.png

 

按天進行錯誤分組

GET dapeng_log_index-2018.07.25/_search
{
  "size": 0, "query": { "bool": { "must": [ { "term": { "level": "ERROR" } } ], "filter": { "script": { "script": { "source": "doc['message'].values.length==0" } } } } }, "aggs": { "group_by_tag": { "terms": { "field": "tag", "size": 100 } } } } 

坑及優化

fluent-bit報Invalid indentation level
fluent-bit對配置文件的要求比較高,請保持配置用空格對齊,不要使用tab鍵

fluent-bit高內存佔用
根據官方文檔描述,在某些環境中,一般會發現被攝取的日誌或數據比將其刷新到某些目的地的速度要快。 常見的狀況是從大日誌文件讀取並經過網絡將日誌分派到後端,這須要一些時間來響應,這樣會產生背壓,致使服務中的高內存消耗。爲了不背壓,Fluent Bit在引擎中實現了一種限制數據量的機制,經過配置參數Mem_Buf_Limit完成的。

咱們這裏經過配置Mem_Buf_Limit來優化,另外fluent-bit默認使用Glibc來管理分配內存,這裏咱們使用jmalloc,這是一種替代內存分配器,它具備更好的策略來減小其餘碎片以得到更好的性能

 
image

fluentd隔天寫入索引
寫入es中的日誌會比當前時間提早8個小時,例如0-8點的日誌會寫入到昨天的索引中,這裏咱們配置utc-index爲false便可

elasticsearch長期報GC
因爲業務高峯日誌量致使瞬時寫入較大,es會長時間報gc,影響數據的寫入,這裏咱們引入kafka做消息緩衝,另外咱們棄用elasticsearch默認的垃圾回收器,使用G1回收器

jdb2高io使用
最開始,咱們在網站上檢索關於jdb2高iowait的解決方案,給出的方案都是ext4的bug,差一點我就信了,linux的bug也能遇到,可是轉過來一想這bug也好多年了,內核早就修復了,應該不是這方面的問題,咱們使用top查看cpu的使用狀況,比較空閒,可是wait比較高

 
image

使用iotop來查看磁盤的io使用狀況,基本都是fluent-bit產生的

 
image

接下來咱們使用 blktrace來收集更進一步的詳細信息

 
image

最後咱們使用wc來統計43這一秒內fluent-bit產生的IO請求數(Q表示即將生成IO請求)

 
image

問題元兇找到了,fluent-bit讀取的日誌文件後會在寫出的時候更新文件位置索引,將索引保存在sqllite中,根據上面的統計,每秒鐘產生的IO操做在101次(因爲有4個fluent-bit)正是因爲fluent-bit頻繁的更新sqlite中的文件索引,形成文件合併引發的高iowait,所以須要對sqlite的寫入次數加限制,這裏咱們基於fluent-bit改造了兩種 方案,第一種,每次都只從尾部讀取文件,這樣就省掉了文件索引的保存達到減小磁盤IO,第二種,增長db_count參數用於對chunk塊計數,當發送chunk塊計數達到配置的參數時保存文件的位置索引,咱們dapeng對這兩塊都進行了個性化改造實現,改造後的效果對比圖以下

 
image

elasticsearch內存使用優化

es這塊按天來存放對應的日誌索引,長期不用的索引會佔用大量內存。通常日誌索引只須要開放近三天的索引便可,日誌索引保留近一月便可

#!/bin/bash date=`date -d "3 days ago" +%Y.%m.%d` date1=`date -d "30 days ago" +%Y.%m.%d` echo $date echo $date1 curl -XPOST http://192.168.20.200:9200/dapeng_log_index-$date/_close curl -XDELETE "http://192.168.20.200:9200/dapeng_log_index-${date1}" 

基於日誌系統的衍生擴展

目前咱們基於現有的日誌系統,作了生產故障實時告警系統,直接釘釘推送給相關的業務系統負責人,具體方案有兩種,一種是根據索引去過濾近30分鐘的日誌異常推送,另一種是從kafak中提取消息後過濾推送,第一種是假實時,錯誤有所延遲,第二種是徹底實時,咱們如今採起的是第一種方案,第二種方案有待實現

 
image

總結

到這一步,咱們的日誌系統已經搭建成功了,當服務器擴容時,因爲fluent-bit是集成在dapeng容器中,只須要在環境變量中簡單配置serviceName和hostname以及fluentdhost便可,日誌消息就會寫入到es存儲中。

日誌系統是一個很是重要的功能組成部分,咱們可使用日誌系統來進行錯誤編排,系統優化,根據這些信息調整系統的行爲,提升系統的可用性。(想了解更多?請關注dapeng開源)

相關文章
相關標籤/搜索