進行文章的第二次修改,包括了以前的簡單方案的升級過程。php
由於業務的不斷更新升級,爲了保證線上業務也能正常使用elk服務,而且使得elk的服務和線業務流解耦(即避免直接寫入es的方式可能會帶來的耗時影響)因此咱們採用了下面最新的方案,也是常規方案html
業務層 >> kafka隊列 >> logstash 消費 >> elasticsearch
業務層將日誌寫入到kafka隊列,同事logstash能夠開啓多個線程,啓用同一個group_id來對kafka進行消費,將讀取到的日誌進行解析後,寫入到elasticsearch中,而且按照索引模板進行解析。java
業務層能夠直接寫入到kafka隊列中,不用擔憂elasticsearch的寫入效率問題。git
比起以前的簡單版本,須要保證kafka隊列、logstash的高可用(雖然logstash掛掉後,能夠重啓後從新讀取隊列日誌)es6
整個搭建過程,寫入kafka是很是簡單的,這裏遇到的問題是logstash和elasticsearch索引模板帶來的困擾。json
如何肯定使用誰的模板呢?
網上找到的資料,建議採用將模板配置在elasticsearch側,這樣就不用每一個logstash進行一個模板配置文件的維護。bootstrap
官網的文檔kafka的input插件
https://www.elastic.co/guide/...安全
input { kafka { // 須要讀取的kafka隊列集羣配置 bootstrap_servers => "xxx.xxx.xxx.xxx:9092" // 配置的消費者的group名稱,由於同一個組內的消費消息不會重複 group_id => "logstash-group" // 主題配置 topics => "kibana_log" // 從未消費過的偏移量開始 auto_offset_reset =>"earliest" } } // 重要,下面單獨講 filter { json { source => "message" } } output { // stdout能夠省略,這個是爲了命令行模式下方便調試 stdout{ codec => rubydebug } elasticsearch { // es 集羣 hosts=>"xxx.xxx.xxx.xxx:9200" // 重要:取消logstash自定義模板功能,進而強制使用es的內置模板 manage_template=>false // 須要匹配的模板名稱 index=>"logstash-dev-%{+YYYY.MM.dd}" } }
先解釋下filter配置,當咱們從kafka讀取消息的時候,消息體是經過message字段來進行傳遞的,因此message是一個字符串,可是咱們的es索引模板可能會很是複雜,因此咱們須要對其進行json解析後,再交給es。不然es收到的以後一個message字段。ruby
filter { json { source => "message" } }
再說下模板配置,首先經過kibana的devtool向es中寫入了一個模板,我區分了兩套環境dev、prod。服務器
這裏字段都進行了strval轉義,爲何呢?這和下面要講的動態模板
有關聯的。往下看
$position = YnUtil::getPosition(); $urlData = parse_url(\Wii::app()->request->url); $path = $urlData['path'] ?? ''; $params = [ 'category' => strval($category), 'appType' => strval(YnUtil::getAppType()), 'appVersion' => strval(YnUtil::getAppVersion()), 'host' => strval(\Wii::app()->request->hostInfo), 'uri' => strval(\Wii::app()->request->url), 'uid' => strval(\Wii::app()->user->getUid()), 'path' => strval($path), 'server' => strval(gethostname()), 'geoip' => [ 'ip' => strval(\Yii::$app->request->userIP), 'location' => [ 'lat' => floatval($position['latitude']), 'lon' => floatval($position['longitude']), ], ], 'userAgent' => strval(\Wii::app()->request->userAgent), 'message' => is_array($message) ? Json::encode($message) : strval($message), // '@timestamp' => intval(microtime(true) * 1000), ];
下面的模板是寫入到es裏面的自定義模板,爲了防止索引規則名稱衝突,這裏將order置爲1。
咱們先來看下第一個模板(這個是不推薦的,由於很繁瑣,可是類型很強制有效)
PUT _template/logstash-dev { "index_patterns": "logstash-dev*", "aliases": {}, "order":1, "mappings": { // 這裏使用logs是由於logstash默認的type類型 "logs": { // 動態模板 "dynamic_templates": [ { "string_fields": { "match": "*", "match_mapping_type": "string", "mapping": { "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } }, "norms": false, "type": "text" } } } ], // 這裏對屬性進行了類型設置 "properties": { "@timestamp": { "type": "date" }, "@version": { "type": "keyword" }, "appType": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "appVersion": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "category": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "geoip": { "dynamic": "true", "properties": { "ip": { "type": "ip" }, "latitude": { "type": "half_float" }, "location": { "type": "geo_point" }, "longitude": { "type": "half_float" } } }, "host": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "message": { "type": "text", "norms": false }, "server": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "uid": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "uri": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "path": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "userAgent": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } }, "settings": { "index": { "number_of_shards": "1" } } }
上面的模板不是最優的,可是倒是一種嘗試,模板裏面針對每一個屬性作了設置,這樣客戶端只須要寫入對應的屬性就行了。可是如何動態配置呢?若是想加入一個字段,難道還要修改模板麼? 這裏引入了動態模板的概念
Only the following datatypes can be automatically detected: boolean, date, double, long, object, string. It also accepts * to match all datatypes.
動態映射
https://www.elastic.co/guide/...
動態模板(注意看match和match_mapping_type)
https://www.elastic.co/guide/...
映射屬性
https://www.elastic.co/guide/...
而後咱們給出了一個全新的模板
這個模板裏面只針對特殊的屬性進行了設置,其餘的都是經過動態模板擴展的,下面看下效果。
PUT _template/logstash-dev { "index_patterns": "logstash-dev*", "aliases": {}, "order":1, "mappings": { "logs": { "dynamic_templates": [ { "string_fields": { "match": "*", "match_mapping_type": "string", "mapping": { "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } }, "norms": false, "type": "text" } } } ], "properties": { "@timestamp": { "type": "date" }, "@version": { "type": "keyword" }, "geoip": { "dynamic": "true", "properties": { "ip": { "type": "ip" }, "latitude": { "type": "half_float" }, "location": { "type": "geo_point" }, "longitude": { "type": "half_float" } } } } } }, "settings": { "index": { "number_of_shards": "1" } } }
咱們上面說了,全都進行了strval
轉義,爲何呢?由於動態模板裏面,匹配的是string類型,若是咱們寫入的是一個int類型,那麼就不會進行自動擴展了。試驗後代表,會生成一個int類型的message字段,這樣是不合理的。最終生成的效果是以下圖的。
分割線 =============================
新項目短期來實現日誌採集。
一臺8G 4核 500G硬盤 服務器
項目部署在4臺服務器,每臺服務器經過phpsdk直接寫入一臺es服務器中。
(在本次部署中,沒有使用logstash的功能)
沒有使用異步隊列,致使直接寫入es可能會影響業務邏輯,可是目前只會在開發和測試環境使用。
主要利用elasticsearch 和 kibana
要使用x-pack作安全校驗,包括給kibana加入登陸受權功能
Elasticsearch
https://www.elastic.co/cn/pro...
Elasticsearch-clients
這裏包含的多種語言的sdk包
https://www.elastic.co/guide/...
Kibana
https://www.elastic.co/cn/pro...
Logstash
https://www.elastic.co/cn/pro...
X-pack
安裝流程說明很詳細,祕鑰生成後記得保存下,而且加入x-pack後,kibana和elasticsearch的通信,須要修改配置文件。另外phpsdk也須要加入祕鑰,後面說明。
https://www.elastic.co/cn/pro...
es的模板超級複雜的,因此咱們要利用標準的現有的模板,須要從logstash中提取一個。
解壓下載的logstash-6.1.2.tar
,執行搜索命令
$ find ./ -name 'elasticsearch-template*' ./vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/elasticsearch-template-es2x.json ./vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/elasticsearch-template-es5x.json ./vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/elasticsearch-template-es6x.json
會發現有2x 5x 6x
三個模板,這裏咱們選擇6x
,要根據你的es版原本選擇。
而後建立索引,索引要在數據寫入以前建立,由於要給每一個字段設置類型。
https://www.elastic.co/guide/...
按照文檔的方式,將獲取到的6x
經過curl的方式寫入到es
由於默認的es配置是開啓了localhost:9200
端口,用於執行RESTFUL,可是本次咱們採用php-sdk的方式,直接寫入es,就要求每臺業務服務器,都能訪問到es。
# ---------------------------------- Network ----------------------------------- # # Set the bind address to a specific IP (IPv4 or IPv6): # 修改此處的host配置爲0.0.0.0,這樣全部的請求均可以接入進來 network.host: 0.0.0.0 # # Set a custom port for HTTP: # #http.port: 9200
下載地址
https://www.elastic.co/guide/...
try { $hosts = [ // 這個地方要填寫x-pack分配的密碼和用戶名 'http://{用戶名}:{密碼}@192.168.1.11:9200', // HTTP Basic Authentication // 'http://user2:pass2@other-host.com:9200' // Different credentials on different host ]; $client = ClientBuilder::create()->setHosts($hosts)->build(); $position = YnUtil::getPosition(); $params = [ 'index' => 'logstash-yn-' . date('Ymd'), // elastic6版本有個bug,每一個索引只能有一個type類型 'type' => 'xxxx', 'id' => md5(rand(0, 999999999)) . (microtime(true) * 1000), 'body' => [ // 索引建立好後,寫入數據必定要注意類型,否則會報錯,我這裏都會進行格式化一遍 // 類別做爲一個主要字段用於區分日誌 'category' => strval($category), 'appType' => strval(YnUtil::getAppType()), 'appVersion' => strval(YnUtil::getAppVersion()), 'host' => strval($hostInfo), 'uri' => strval($url), 'uid' => strval($user->getUid()), 'server' => strval(gethostname()), 'geoip' => [ 'ip' => strval($ip), // 這個很重要,能夠實現geo可視化圖形 'location' => [ 'lat' => floatval($position['latitude']), 'lon' => floatval($position['longitude']), ], ], 'userAgent' => strval($userAgent), 'message' => Json::encode($message), // 這裏必定要寫入毫秒時間戳 '@timestamp' => intval(microtime(true) * 1000), ], ]; $client->index($params); } catch (\Exception $e) { }
官方說明
https://www.elastic.co/guide/...
咱們這裏以6x做爲模板
{ // 將這個模板應用於全部以 logstash- 爲起始的索引。 "template": "logstash-*", "version": 60001, "settings": { "index.refresh_interval": "5s" }, "mappings": { "_default_": { // 動態模板說明,很重要,配置了動態模板後,咱們能夠添加任意字段 // https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html "dynamic_templates": [{ // 信息字段 官方說這裏能夠自定義 The template name can be any string value. "message_field": { "path_match": "message", "match_mapping_type": "string", "mapping": { "type": "text", "norms": false } } }, { // 字符串字段說明 "string_fields": { // 匹配全部 "match": "*", // 而且字段類型是string的 "match_mapping_type": "string", // "mapping": { "type": "text", // 這裏應該是和受歡迎程度評分相關 "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } }], // 定義好的屬性說明 "properties": { // 時間字段,這個很重要,否則kibana不會出現時間相關的查詢控件 "@timestamp": { "type": "date" }, "@version": { "type": "keyword" }, // 這個能夠只寫入properies裏面的任意一個字段 "geoip": { "dynamic": true, "properties": { "ip": { "type": "ip" }, // 我只是用了這個location "location": { "type": "geo_point" }, "latitude": { "type": "half_float" }, "longitude": { "type": "half_float" } } } } } } }
使用logstash內置的模板
檢查是不是毫秒時間戳
須要在連接中添加用戶名和密碼
寫入類型,必定要和索引模板中定義的一致,否則確定報錯!
elasticsearch6的 bug,官方承諾在7進行修復
安裝x-pack吧
使用supervisor
個人方案是:定時腳原本清理7天以前的索引DELETE logstash-xxxx
在kibana中有一個Dev Tools
能夠執行curl,而且看到結果
在kibana菜單的Management->Index Patterns中能夠管理
不詳細的地方,能夠留言