Logstash/Filebeat->Logstash->Kafka->Spring-kafka->MongoDb日誌收集和處理
1. 背景
1.1 ELK
ELK(ElasticSearch + Logstash + Kibana)
是一套非常成熟廣泛應用的日誌分析系統,對於大部分系統,直接使用ELK
基本就能滿足業務需求,但是針對一些特殊的需求和無法避免的限制條件,完全可以用Elastic
1的各種產品結合外部輸入輸出,比如Kafka
Redis
File
等,達到想要的效果。
1.2 爲什麼不用ELK
- 系統設計爲輸入端收集日誌信息推送到代理端,代理同一處理日誌推送到
Kafka
集羣,使用Java(spring-kafka)
消費Kafka
中的數據,最終把數據結果存入MongoDB
; - 首先
logstash
是java
寫的,運行logstash
需要java
環境,這就導致輸入端需要額外的環境的資源,因此可以使用Filebeat
代替Logstash
。不過部分系統不能運行Filebeat
,因爲Filebeat
是用go
寫的,然而一些系統,比如IBM AIX
,並沒有支持的go
語言的編譯器,所以不得不同時採用Filebeat
和Logstash
,根據實際情況選擇; - 首先輸入端(
Agent
)和輸出端Kafka+MongoDB
網絡不通,中間有個代理(ProxyAgent
),無法直接使用Logstash
或Filebeat
輸出到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 條件
Filebeat
2是一個輕量級的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 條件
Logstash
3需要java
環境,安裝前必須安裝jdk 1.8+
。- 系統環境:
Linux(Red Hat)
。
2.2.2 特殊要求
- 如果
Agent
安裝Logstash
,從Logstash
推送數據到另一個Logestash
需要使用Logstash
的logstash-in[out]put-lumberjack
插件,這兩個插件在較高的Logstash
版本中是不包含的,需要手動安裝。 Agent
可能無法連接到公網,因此不能在線安裝,需要離線安裝,所以要把插件包放到原始的安裝包內並重新打包軟件包。logstash-in[out]put-lumberjack
基於ssl
認證,所以要準備好一對ssl
證書和密鑰,可以使用openssl
4生成,假設已經生成好了,並且把證書和密鑰都放在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
而不是Agent
的output
到Logestash
。
#新增配置文件
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
其中input
和output
配置中的codec => json
有丶關鍵,否則一些附加的field
可能解析不出來,比如Logstash
的input.add_field
中添加的字段、Filebeat
的filebeat.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.yml
中http.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
,如果一直失敗就會導致死循環一直消費重複的數據。 - 解決方法:
spring-kafka
可以禁用kafka client
默認的offset
提交方式,spring-kafka
會在消費完成後手動提交offset
,從kafka
取回的數據先緩存到阻塞隊列,也不存在數據丟失的問題,是比較完美且省事的解決方法:kafka重複消費問題- 配置
kafka consumer
,加長超時時間、修改poll
策略等,但不是最好的方法:kafka9重複消費問題解決 - 手動緩存
partition_group=>offset
的mapping
(比如分佈式的系統緩存到redis
),每次消費前讀取緩存並手動設置consumer
的offset
;消費後手動去更新緩存中的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()
是一個原子操作,和MySql
的select 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
待補充。
ELK
和Beats
等都是elastic
的產品,更多產品移步:elastic ↩- 更多
Filebeat
信息請參考:Filebeat | Filebeat Reference ↩ - 更多
Logstash
信息請參考:Logstash | Logstash Reference ↩ openssl
使用方法:How-to-configure-SSL-for-FileBeat-and-Logstash-step-by-step ↩- 更多
Logstash plugin
離線安裝信息:Offline Plugin Management ↩ - 更多
Logstash scaling
信息:Deploying and Scaling Logstash ↩ - 更多
Filebeat
負載均衡信息:Configure the Logstash output ↩