Logstash/Filebeat->Logstash->Kafka->Spring-kafka->MongoDb->Spark日誌收集和處理

Logstash/Filebeat->Logstash->Kafka->Spring-kafka->MongoDb日誌收集和處理

1. 背景

1.1 ELK

ELK(ElasticSearch + Logstash + Kibana)是一套非常成熟廣泛應用的日誌分析系統,對於大部分系統,直接使用ELK基本就能滿足業務需求,但是針對一些特殊的需求和無法避免的限制條件,完全可以用Elastic1的各種產品結合外部輸入輸出,比如Kafka Redis File等,達到想要的效果。

1.2 爲什麼不用ELK

  • 系統設計爲輸入端收集日誌信息推送到代理端,代理同一處理日誌推送到Kafka集羣,使用Java(spring-kafka)消費Kafka中的數據,最終把數據結果存入MongoDB
  • 首先logstashjava寫的,運行logstash需要java環境,這就導致輸入端需要額外的環境的資源,因此可以使用Filebeat代替Logstash。不過部分系統不能運行Filebeat,因爲Filebeat是用go寫的,然而一些系統,比如IBM AIX,並沒有支持的go語言的編譯器,所以不得不同時採用FilebeatLogstash,根據實際情況選擇;
  • 首先輸入端(Agent)和輸出端Kafka+MongoDB網絡不通,中間有個代理(ProxyAgent),無法直接使用LogstashFilebeat輸出到Kafka,因此在ProxyAgent上安裝Logstash接受所有Agent推送的日誌,統一處理後推送到Kafka
  • 使用Kafka除了緩存和生產與消費速度不匹配外,系統還會出現多個Group消費同一Topic的情況;
  • 使用Java消費Kafka而不直接輸出到MongoDB,因爲一部分數據需要做複雜的清洗。
  • 展示的數據要根據收集的數據做計算,用spark定時計算並存儲到MongoDB做爲可視化的數據源。

這些都是ELK無法直接實現的,當然也可以說ELK更加純粹,不過Elastic系列產品有各種各樣的插件,匹配不同的輸入和輸出數據源,完全沒有問題。

2. Agent(Filebeat/Logstash)

2.1 Filebeat

2.1.1 條件

  • Filebeat2是一個輕量級的Log Shipper,但是注意需要支持go語言編譯,否則無法使用。
  • 系統環境:Linux(Red Hat)

2.1.2 安裝、配置和啓動

#下載安裝包
wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.1.2-linux-x86_64.tar.gz

#解壓
tar -zvxf filebeat-6.1.2-linux-x86_64.tar.gz

#備份配置文件模板,全新的filebeat.yml相當於是一個配置文件reference
cd filebeat-6.1.2-linux-x86_64
cp filebeat.yml filebeat.yml.back

#配置filebeat.yml
rm -rf filebeat.yml
vim filebeat.yml

##>>>>>>>>>>>>>>>>>>>>>>>>>file start>>>>>>>>>>>>>
# prospector
filebeat.prospectors:
- type: log
  enabled: true
  paths:
    - /path/to/log/file/**/*.log
  fields:
    appName: dbaas-indicator
  document_type: dbaas-indicator
  scan_frequency: 10s

output.logstash:
  hosts: ["192.168.0.150:5044"]
##>>>>>>>>>>>>>>>>>>>>>>>>>file end>>>>>>>>>>>>>
ESC
:wq

# 驗證配置文件
./filebeat test config
#>>>>>>>>>>>>output>>>>>>>>>>>>>
Config OK
#>>>>>>>>>>>>output>>>>>>>>>>>>>

#啓動filebeat
./filebeat -e -c filebeat.yml -d "publish" &

配置含義和更多配置請參考官方參考手冊。

2.2 Logstash

2.2.1 條件

  • Logstash3需要java環境,安裝前必須安裝jdk 1.8+
  • 系統環境:Linux(Red Hat)

2.2.2 特殊要求

  • 如果Agent安裝Logstash,從Logstash推送數據到另一個Logestash需要使用Logstashlogstash-in[out]put-lumberjack插件,這兩個插件在較高的Logstash版本中是不包含的,需要手動安裝。
  • Agent可能無法連接到公網,因此不能在線安裝,需要離線安裝,所以要把插件包放到原始的安裝包內並重新打包軟件包。
  • logstash-in[out]put-lumberjack基於ssl認證,所以要準備好一對ssl證書和密鑰,可以使用openssl4生成,假設已經生成好了,並且把證書和密鑰都放在ProxyAgent(/root/server.crt)、把證書放在Agent(/root/server.key)

2.2.3 將lumberjack插件打包到logstash安裝包裏

#重新打包的服務器必須能連公網
#下載安裝包
wget https://artifacts.elastic.co/downloads/logstash/logstash-6.1.2.tar.gz

#解壓
tar -zvxf logstash-6.1.2.tar.gz

#在線安裝插件
##安裝logstash-input-lumberjack
cd logstash-6.1.2
bin/logstash-plugin install logstash-input-lumberjack
#>>>>output>>>>>
Validating logstash-input-lumberjack
Installing logstash-input-lumberjack
Installation successful
#>>>>output>>>>>

##安裝logstash-output-lumberjack
bin/logstash-plugin install logstash-output-lumberjack
#>>>>output>>>>>
Validating logstash-output-lumberjack
Installing logstash-output-lumberjack
Installation successful
#>>>>output>>>>>

##驗證安裝
bin/logstash-plugin list | grep lumberjack
#>>>>output>>>>>
logstash-input-lumberjack
logstash-output-lumberjack
#>>>>output>>>>>

##構建logstash-in[out]put-lumberjack離線包
bin/logstash-plugin prepare-offline-pack logstash-input-lumberjack logstash-output-lumberjack
#文件會生成在logstash根目錄下
#>>>>output>>>>>
Offline package created at: /root/temp-for-logstash/logstash-6.1.2/logstash-offline-plugins-6.1.2.zip

You can install it with this command `bin/logstash-plugin install file:///root/temp-for-logstash/logstash-6.1.2/logstash-offline-plugins-6.1.2.zip`
#>>>>output>>>>>

#重新構建logstash-with-plugin軟件包
cd ../
mkdir logstash-6.1.2-with-plugin
tar -zvxf logstash-6.1.2.tar.gz -C logstash-6.1.2-with-plugin
cp logstash-6.1.2/logstash-offline-plugins-6.1.2.zip logstash-6.1.2-with-plugin/logstash-6.1.2/logstash-offline-plugins-6.1.2.zip   
cd logstash-6.1.2-with-plugin
tar -czvf logstash-6.1.2-with-plugin.tar.gz logstash-6.1.2

此時logstash-6.1.2-with-plugin.tar.gz就是包含插件離線安裝包的軟件包了5

2.2.5 安裝、配置和啓動

#解壓
tar -zvxf logstash-6.1.2-with-plugin.tar.gz
rm -rf logstash-6.1.2-with-plugin.tar.gz
mv logstash-6.1.2 logstash-6.1.2-for-logstash
cd logstash-6.1.2-for-logstash

#離線安裝插件
##開始安裝
bin/logstash-plugin install file:///path/to/logstash/logstash-6.1.2-for-logstash/logstash-offline-plugins-6.1.2.zip
#>>>>output>>>>>
Installing file: /path/to/logstash/logstash-6.1.2/logstash-offline-plugins-6.1.2.zip
Install successful
#>>>>output>>>>>

##驗證安裝
bin/logstash-plugin list | grep lumberjack
#>>>>output>>>>>
logstash-input-lumberjack
logstash-output-lumberjack
#>>>>output>>>>>

#新增配置文件
vi config/logstash.conf
##>>>>>>>>>>>>>>>>>>>>>>update start>>>>>>>>>>>>>>>>>>>
input {
  file {
      path => "/path/to/log/**/*.log"
      add_field => {"appName" => "dbaas-indicator"}
      type => "dbaas-indicator"
      discover_interval => 10
  }
}

filter{
  if([message] =~ "^\{\"params\".*$"){
    drop{}
  }
}

output{
  lumberjack {
    hosts => ["192.168.0.150"]
    port => 5045
    ssl_certificate => "/root/server.crt"
    codec => json
  }
}
##>>>>>>>>>>>>>>>>>>>>>>update end>>>>>>>>>>>>>>>>>>>>>
ESC
:wq

# 驗證配置文件
bin/logstash -t -f config/config.conf
#>>>>>>>>>output>>>>>>>>>>>>>
Configuration OK
#>>>>>>>>>output>>>>>>>>>>>>>

#可以啓動了
cd ../bin
#可能需要指定JAVA_HOME
#export JAVA_HOME=/path/to/java/home
bin/logstash -f config/logstash.conf &

更多配置請參考官方參考手冊。

3. ProxyAgent(Logstash)

3.1 安裝、配置和啓動

安裝過程和Agent安裝Logstash幾乎一樣,只是將Logstash作爲input而不是AgentoutputLogestash

#新增配置文件
vi config/logstash.conf
##>>>>>>>>>>>>>>>>>>>>>>update start>>>>>>>>>>>>>>>>>>>
input {
  lumberjack {
    port => 5045
    ssl_certificate => "/root/server.crt"
    ssl_key => "/root/server.key"
    codec => json
    tags => ["logstash-input"]
  }
  beats  {
    port => 5044
    tags => ["filebeat-input"]
  }
}

output{           ##按照appName推送到不同的topic
  if "logstash-input" in [tags] {
    kafka {
      codec => json
      bootstrap_servers => "192.168.0.113:9092"
      topic_id => "%{[appName]}"
      acks => "1"
      compression_type => "gzip"
    }
  } else if "filebeat-input" in [tags] {
    kafka {
      codec => json
      bootstrap_servers => "192.168.0.113:9092"
      topic_id => "%{[fields][appName]}"
      acks => "1"
      compression_type => "gzip"
    }
  } else {
    stdout {
      codec => json
    }
  }
}
##>>>>>>>>>>>>>>>>>>>>>>update end>>>>>>>>>>>>>>>>>>>>>
ESC
:wq

其中inputoutput配置中的codec => json有丶關鍵,否則一些附加的field可能解析不出來,比如Logstashinput.add_field中添加的字段、Filebeatfilebeat.prospectors.fields等,更多配置請參考官方參考手冊。

3.2 Logstash多實例與負載均衡

3.2.1 水平集羣

Logstash is horizontally scalable and can form groups of nodes running the same pipeline. Logstash’s adaptive buffering capabilities will facilitate smooth streaming even through variable throughput loads. If the Logstash layer becomes an ingestion bottleneck, simply add more nodes to scale out. Here are a few general recommendations:

  • Beats should load balance across a group of Logstash nodes.
  • A minimum of two Logstash nodes are recommended for high availability.
  • It’s common to deploy just one Beats input per Logstash node, but multiple Beats inputs can also be deployed per Logstash node to expose independent endpoints for different data sources.6


所以水平集羣搭建相當簡單,只需要在多個節點啓動Logstash實例,它們的配置應該相同,然後在Beats端做負載均衡7,但是目前好像只支持Beats->Logstash的負載均衡,沒有找到Logstash->Logstash負載均衡相關的資料。

3.2.2 垂直集羣

同樣,這裏還是針對Beats的集羣,問題的關鍵是如何在同一個機器上啓動多個實例。Logstash有兩種安裝方式,一種是解壓即用,一種是執行bin/system-install,後者可以作爲一個服務啓動。

  • 安裝方法如果是前者,可以之際拷貝兩個軟件目錄,然後分別啓動即可,並不會出現衝突,因爲logstash會自動在9600-9700之間尋找未被佔用的端口(見config/logstash.ymlhttp.port的解釋),如果指定了http.port,只要實例的http.port與其他實例不同即可,在Beats輸出到多個hosts並設置loadbalance: true即可。
  • 安裝方法如果是後者,可以參考這裏:Multiple Logstash Instances on Single Server,相對麻煩一點,目前沒有這樣的需要,暫不做深入研究。

4. Kafka

4.1 安裝、配置和啓動

#hostname:::host113
#hostip:::::192.168.0.113
#這裏是standalone
#集羣基於zookeeper集羣,先要搭建zookeeper集羣,才能搭建kafka集羣,只需簡單修改一些配置文件重啓zookeeper和kafka即可

#下載tar包
wget http://apache.claz.org/kafka/1.0.0/kafka_2.11-1.0.0.tgz

#解壓
tar -zvxf kafka_2.11-1.0.0.tgz

#啓動zookeeper
cd kafka_2.11-1.0.0/bin
./zookeeper-server-start.sh ../config/zookeeper.properties &

#配置kafka
vim ../config/server.properties
#>>>>>>>>>>>>>>>>>>>>update start>>>>>>>>>>>>>>>>>
#很重要,否則默認只能以hostname:9092訪問kafka,consumer主機必須配置hosts文件
listeners=PLAINTEXT://192.168.0.113:9092
#下面幾個也配置上,可能不同版本的客戶端需要使用這些參數
advertised.listeners=PLAINTEXT://192.168.0.113:9092
advertised.port=9092
port=9092
advertised.host.name=192.168.0.113
#同一個Group使用6個Consumer來消費最佳
num.partitions=6
#>>>>>>>>>>>>>>>>>>>>update end>>>>>>>>>>>>>>>>>>>

#啓動kafka
./kafka-server-start.sh ../config/server.properties &

#查詢PID
jps -ml
##>>>>>>>>>>>>>>>>>>>>output start>>>>>>>>>>>>>>>>>>
#25987 org.apache.zookeeper.server.quorum.QuorumPeerMain ../config/zookeeper.properties
#26298 kafka.Kafka ../config/server.properties
##>>>>>>>>>>>>>>>>>>>>output end>>>>>>>>>>>>>>>>>>

5. Spring-kafka

基於spring-boot

5.1 dependency

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <!-- 特別注意:最好不指定版本,spring-kafka裏面依賴了很多springframework的包,很容易出現版本不一致導致包衝突 -->
</dependency>

5.2 Java Consumer

5.2.1 kafka配置和consumer創建

  • 首先配置kafka相關信息,直接在application.properties添加配置;
spring.kafka.bootstrap-servers=192.168.0.113:9092
spring.kafka.listener.concurrency=3
spring.kafka.listener.poll-timeout=3000
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.auto-commit-interval=100
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.group-id="dbaas-logger"
spring.kafka.consumer.auto-offset-reset=latest
  • 監聽直接使用@KafkaListener註解就好好了;
@Component
public class KafkaListenerConfig {
    @KafkaListener(topics = {"dbaas-indicator"})
    public void listen1(ConsumerRecord<?, ?> record) {
        Optional<?> kafkaMessage = Optional.ofNullable(record.value());
        if (kafkaMessage.isPresent()) {
            Object objectMessage = kafkaMessage.get();
            LOGGER.info("dbaas-indicator:\n\t{}", String.valueOf(objectMessage));
        }
    }
}

5.2.2 kafka預防重複消費的配置

  • 爲什麼會重複消費:正常情況下,同一個group下的consumer不會重複消費數據,但是某一個consumer在消費數據時遇到了問題導致kafka session(session.timeout.ms)超時,此時kafka認爲消費失敗,將重新分配給consumer,如果一直失敗就會導致死循環一直消費重複的數據。
  • 解決方法:
    1. spring-kafka可以禁用kafka client默認的offset提交方式,spring-kafka會在消費完成後手動提交offset,從kafka取回的數據先緩存到阻塞隊列,也不存在數據丟失的問題,是比較完美且省事的解決方法:kafka重複消費問題
    2. 配置kafka consumer,加長超時時間、修改poll策略等,但不是最好的方法:kafka9重複消費問題解決
    3. 手動緩存partition_group=>offsetmapping(比如分佈式的系統緩存到redis),每次消費前讀取緩存並手動設置consumeroffset;消費後手動去更新緩存中的offset,這種方法的可控性非常強,但是consumer要自己用Java API寫,並且各種成本都要高一些,稍微麻煩一點:Kafka重複消費和丟失數據研究|關於怎麼獲取kafka指定位置offset消息

6. Spring-mongo

6.1 dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
</dependency>

6.2 mongo自增ID的實現方法

  • 思路很簡單,首先MongoTemplate.findAndModify()是一個原子操作,和MySqlselect for update有丶類似;然後根據這個特性,只要把ID存在一個colection裏面,每次用這個方法取出來再+1更新進去就可以了;
  • 不必在每個插入方法前主動查詢ID並更新,只需寫一個全局MongoEventListener即可,繼承自AbstractMongoEventListener,重寫onBeforeConvert()方法,在裏面設置並更新ID:
@Configuration
public class MongoEventListener extends AbstractMongoEventListener {

    private final static Logger LOGGER = LoggerFactory.getLogger(MongoEventListener.class);

    @Autowired
    MongoService mongoService;

    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        if(source != null){
            JSONObject jsonSource = (JSONObject) source;
            Long dbaasId = mongoService.getNextId(COLLECTION_INDICATORS);
            LOGGER.info("設置ID:{}",dbaasId);
            jsonSource.put(KEY_DBAAS_ID,dbaasId);
        }else {
            LOGGER.error("待插入mongo的數據爲空,不寫入ID");
        }
    }
}
  • 試過重寫onBeforeSave()方法,發現在方法是執行了,但是裏面添加的ID字段並沒有存到Mongo,因爲onBeforeSave()是持久化完成之後保存之前調用,而onBeforeConvert()是序列化之前調用,因此明顯序列化之前是最好的選擇。

6.3 刪除重複數據

因爲mongo刪除某一個範圍內數據有點麻煩,如果出現了5.2.2中重複消費的問題,那麼可能需要刪除重複的數據。思路是:查出重複的範圍,然後根據唯一鍵group,此時可以用$addToSet方法,把所有的文檔_id放到集合(有點類似mysql group_concat()),然後刪除把集合中_id對應的文檔保留一個,其他的全部刪除。

db.dbIndicators.aggregate([
    {$match:{CREATE_TIME:{$gte:1519228800000,$lt:1519315200000},hostId:{$exists:true}}},
    {$group:{_id:{CREATE_TIME:'$CREATE_TIME',definitionId:'$definitionId',hostId:'$hostId'},count:{$sum:1},dups:{$addToSet:'$_id'}}},
    {$match:{count:{$gt:1}}}],{allowDiskUse:true}).forEach(function(doc){
        doc.dups.shift();//保留一個
        db.dbIndicators.remove({_id:{$in:doc.dups}})}
    );

7. Spark

待補充。


  1. ELKBeats 等都是elastic的產品,更多產品移步:elastic
  2. 更多Filebeat信息請參考:Filebeat | Filebeat Reference
  3. 更多Logstash信息請參考:Logstash | Logstash Reference
  4. openssl使用方法:How-to-configure-SSL-for-FileBeat-and-Logstash-step-by-step
  5. 更多Logstash plugin離線安裝信息:Offline Plugin Management
  6. 更多Logstash scaling信息:Deploying and Scaling Logstash
  7. 更多Filebeat負載均衡信息:Configure the Logstash output
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章