Logstash讀取Kafka資料寫入HDFS詳解

運維咖啡吧發表於2019-03-20

強大的功能,豐富的外掛,讓logstash在資料處理的行列中出類拔萃

通常日誌資料除了要入ES提供實時展示和簡單統計外,還需要寫入大資料叢集來提供更為深入的邏輯處理,前邊幾篇ELK的文章介紹過利用logstash將kafka的資料寫入到elasticsearch叢集,這篇文章將會介紹如何通過logstash將資料寫入HDFS

本文所有演示均基於logstash 6.6.2版本

資料收集

logstash預設不支援資料直接寫入HDFS,官方推薦的output外掛是webhdfs,webhdfs使用HDFS提供的API將資料寫入HDFS叢集

外掛安裝

外掛安裝比較簡單,直接使用內建命令即可

# cd /home/opt/tools/logstash-6.6.2
# ./bin/logstash-plugin install logstash-output-webhdfs
複製程式碼

配置hosts

HDFS叢集內通過主機名進行通訊所以logstash所在的主機需要配置hadoop叢集的hosts資訊

# cat /etc/hosts
192.168.107.154 master01
192.168.107.155 slave01
192.168.107.156 slave02
192.168.107.157 slave03
複製程式碼

如果不配置host資訊,可能會報下邊的錯

[WARN ][logstash.outputs.webhdfs ] Failed to flush outgoing items
複製程式碼

logstash配置

kafka裡邊的源日誌格式可以參考這片文章:ELK日誌系統之使用Rsyslog快速方便的收集Nginx日誌

logstash的配置如下:

# cat config/indexer_rsyslog_nginx.conf
input {
    kafka {
        bootstrap_servers => "10.82.9.202:9092,10.82.9.203:9092,10.82.9.204:9092"
        topics => ["rsyslog_nginx"]
        codec => "json"
    }
}

filter {
    date {
        match => ["time_local","dd/MMM/yyyy:HH:mm:ss Z"]
        target => "time_local"
    }

    ruby {
        code => "event.set('index.date', event.get('time_local').time.localtime.strftime('%Y%m%d'))"
    }

    ruby {
        code => "event.set('index.hour', event.get('time_local').time.localtime.strftime('%H'))"
    }
}

output {
    webhdfs {
        host => "master01"
        port => 50070
        user => "hadmin"
        path => "/logs/nginx/%{index.date}/%{index.hour}.log"
        codec => "json"
    }
    stdout { codec => rubydebug }
}
複製程式碼

logstash配置檔案分為三部分:input、filter、output

input指定源在哪裡,我們是從kafka取資料,這裡就寫kafka叢集的配置資訊,配置解釋:

  • bootstrap_servers:指定kafka叢集的地址
  • topics:需要讀取的topic名字
  • codec:指定下資料的格式,我們寫入的時候直接是json格式的,這裡也配置json方便後續處理

filter可以對input輸入的內容進行過濾或處理,例如格式化,新增欄位,刪除欄位等等

  • 這裡我們主要是為了解決生成HDFS檔案時因時區不對差8小時導致的檔名不對的問題,後邊有詳細解釋

output指定處理過的日誌輸出到哪裡,可以是ES或者是HDFS等等,可以同時配置多個,webhdfs主要配置解釋:

  • host:為hadoop叢集namenode節點名稱
  • user:為啟動hdfs的使用者名稱,不然沒有許可權寫入資料
  • path:指定儲存到HDFS上的檔案路徑,這裡我們每日建立目錄,並按小時存放檔案
  • stdout:開啟主要是方便除錯,啟動logstash時會在控制檯列印詳細的日誌資訊並格式化方便查詢問題,正式環境建議關閉

webhdfs還有一些其他的引數例如compression,flush_size,standby_host,standby_port等可檢視官方文件瞭解詳細用法

啟動logstash

# bin/logstash -f config/indexer_rsyslog_nginx.conf
複製程式碼

因為logstash配置中開了stdout輸出,所以能在控制檯看到格式化的資料,如下:

{
               "server_addr" => "172.18.90.17",
           "http_user_agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C92 Safari/601.1 wechatdevtools/1.02.1902010 MicroMessenger/6.7.3 Language/zh_CN webview/ token/e7b92168159736c30401a55589317d8c",
               "remote_addr" => "172.18.101.0",
                    "status" => 200,
              "http_referer" => "https://ops-coffee.cn/wx02935bb29080a7b4/devtools/page-frame.html",
    "upstream_response_time" => "0.056",
                      "host" => "ops-coffee.cn",
               "request_uri" => "/api/community/v2/news/list",
              "request_time" => 0.059,
           "upstream_status" => "200",
                  "@version" => "1",
      "http_x_forwarded_for" => "192.168.106.100",
                "time_local" => 2019-03-18T11:03:45.000Z,
           "body_bytes_sent" => 12431,
                "@timestamp" => 2019-03-18T11:03:45.984Z,
                "index.date" => "20190318",
                "index.hour" => "19",
            "request_method" => "POST",
             "upstream_addr" => "127.0.0.1:8181"
}
複製程式碼

檢視hdfs發現資料已經按照定義好的路徑正常寫入

$ hadoop fs -ls /logs/nginx/20190318/19.log
-rw-r--r--   3 hadmin supergroup       7776 2019-03-18 19:07 /logs/nginx/20190318/19.log
複製程式碼

至此kafka到hdfs資料轉儲完成

遇到的坑

HDFS按小時生成檔名不對

logstash在處理資料時會自動生成一個欄位@timestamp,預設情況下這個欄位儲存的是logstash收到訊息的時間,使用的是UTC時區,會跟國內的時間差8小時

我們output到ES或者HDFS時通常會使用類似於rsyslog-nginx-%{+YYYY.MM.dd}這樣的變數來動態的設定index或者檔名,方便後續的檢索,這裡的變數YYYY使用的就是@timestamp中的時間,因為時區的問題生成的index或者檔名就差8小時不是很準確,這個問題在ELK架構中因為全部都是用的UTC時間且最終kibana展示時會自動轉換我們無需關心,但這裡要生成檔案就需要認真對待下了

這裡採用的方案是解析日誌中的時間欄位time_local,然後根據日誌中的時間欄位新增兩個新欄位index.dateindex.hour來分別標識日期和小時,在output的時候使用這兩個新加的欄位做變數來生成檔案

logstash filter配置如下:

filter {
    # 匹配原始日誌中的time_local欄位並設定為時間欄位
    # time_local欄位為本地時間欄位,沒有8小時的時間差
    date {
        match => ["time_local","dd/MMM/yyyy:HH:mm:ss Z"]
        target => "time_local"
    }

    # 新增一個index.date欄位,值設定為time_local的日期
    ruby {
        code => "event.set('index.date', event.get('time_local').time.localtime.strftime('%Y%m%d'))"
    }

    # 新增一個index.hour欄位,值設定為time_local的小時
    ruby {
        code => "event.set('index.hour', event.get('time_local').time.localtime.strftime('%H'))"
    }
}
複製程式碼

output的path中配置如下

path => "/logs/nginx/%{index.date}/%{index.hour}.log"
複製程式碼

HDFS記錄多了時間和host欄位

在沒有指定codec的情況下,logstash會給每一條日誌新增時間和host欄位,例如:

源日誌格式為

ops-coffee.cn | 192.168.105.91 | 19/Mar/2019:14:28:07 +0800 | GET / HTTP/1.1 | 304 | 0 | - | 0.000
複製程式碼

經過logstash處理後多了時間和host欄位

2019-03-19T06:28:07.510Z %{host}  ops-coffee.cn | 192.168.105.91 | 19/Mar/2019:14:28:07 +0800 | GET / HTTP/1.1 | 304 | 0 | - | 0.000
複製程式碼

如果不需要我們可以指定最終的format只取message,解決方法為在output中新增如下配置:

codec => line {
    format => "%{message}"
}
複製程式碼

同時output到ES和HDFS

在實際應用中我們需要同時將日誌資料寫入ES和HDFS,那麼可以直接用下邊的配置來處理

# cat config/indexer_rsyslog_nginx.conf
input {
    kafka {
        bootstrap_servers => "localhost:9092"
        topics => ["rsyslog_nginx"]
        codec => "json"
    }
}

filter {
    date {  
        match => ["time_local","dd/MMM/yyyy:HH:mm:ss Z"]
        target => "@timestamp"
    }

    ruby {
        code => "event.set('index.date', event.get('@timestamp').time.localtime.strftime('%Y%m%d'))"
    }

    ruby {
        code => "event.set('index.hour', event.get('@timestamp').time.localtime.strftime('%H'))"
    }


}

output {
    elasticsearch {
        hosts => ["192.168.106.203:9200"]
        index => "rsyslog-nginx-%{+YYYY.MM.dd}"
    }

    webhdfs {
        host => "master01"
        port => 50070
        user => "hadmin"
        path => "/logs/nginx/%{index.date}/%{index.hour}.log"
        codec => "json"
    }
}
複製程式碼

這裡我使用logstash的date外掛將日誌中的"time_local"欄位直接替換為了@timestamp,這樣做有什麼好處呢?

logstash預設生成的@timestamp欄位記錄的時間是logstash接收到訊息的時間,這個時間可能與日誌產生的時間不同,而我們往往需要關注的時間是日誌產生的時間,且在ELK架構中Kibana日誌輸出的預設順序就是按照@timestamp來排序的,所以往往我們需要將預設的@timestamp替換成日誌產生的時間,替換方法就用到了date外掛,date外掛的用法如下

date {  
    match => ["time_local","dd/MMM/yyyy:HH:mm:ss Z"]
    target => "@timestamp"
}
複製程式碼

match:匹配日誌中的時間欄位,這裡為time_local

target:將match匹配到的時間戳儲存到給定的欄位中,預設不指定的話就存到@timestamp欄位

另外還有引數可以配置:timezone,locale,tag_on_failure等,具體可檢視官方文件


Logstash讀取Kafka資料寫入HDFS詳解

如果你覺得文章不錯,請點右下角【在看】。如果你覺得讀的不盡興,推薦閱讀以下文章:

相關文章