背景
在公司采用ELK架构之后,度过了开始——不稳定——稳定期之后,来总结一下期间出现过的问题以及解决方案
问题
logstash能否try catch
在logstash中,是不能够使用类似Java的try catch语句的,那么问题来了,如果我们从filebeta传入数据,通过logstash json插件解析,logstash 解析失败了,那这些数据是不是就丢了?但是我想保留这部分数据,该怎么做?
解决方案:
通过json-parse-failed,具体用法如下:
input {
beats{
port => "5057"
}
}
filter {
json {
source => "message"
tag_on_failure => ["json-parse-failed"]
}
date {
match => ["#datetime","yyyy-MM-dd HH:mm:ss.SSS"]
target => "@timestamp"
}
}
output {
if "json-parse-failed" in [tags] {
elasticsearch {
hosts => ["xxx:9200"]
index => "biz-error-logs-test-%{+YYYY.MM.dd}"
user => xxx
password => "xxxx"
}
}else{
elasticsearch {
hosts => ["xxx:9200"]
index => "%{#fb_collect_app}-%{+YYYY.MM.dd}"
user => xxx
password => "xxxx"
}
}
}
相当于是将json解析失败的数据保留到biz-error-logs-test-%{+YYYY.MM.dd}这个索引里面了,如果我们没找到相关数据,就去这个索引里面找
logstash 数据备份,最长保留期限
其实对于这个问题来说,其实是个伪命题,虽然logstash可以做持久化,但是它不是专门做的,它内部队列的大小是一定的(磁盘空间),并不适合高并发量的应用,也不适合做,它并不能保证数据不丢失,事实上,对于高并发的应用,架构应该是filebeat ——> kafka——>logstash——> ES,这种在很大程度上保证了数据不丢失
不过,logstash也可以做,在logstash.yml添加以下内容:
queue.type: persisted
path.queue: /usr/share/logstash/data #队列存储路径;如果队列类型为persisted,则生效
queue.page_capacity: 250mb #队列为持久化,单个队列大小
queue.max_events: 0 #当启用持久化队列时,队列中未读事件的最大数量,0为不限制
queue.max_bytes: 1024mb #队列最大容量
queue.checkpoint.acks: 1024 #在启用持久队列时强制执行检查点的最大数量,0为不限制
queue.checkpoint.writes: 1024 #在启用持久队列时强制执行检查点之前的最大数量的写入事件,0为不限制
queue.checkpoint.interval: 1000 #当启用持久队列时,在头页面上强制一个检查点的时间间隔
持久化队列将事件存储在硬盘上,但由于硬盘空间也不是无限的,所以需要根据应用实际需求配置持久化队列的容量大小。Logstash持久化队列容量 可通过事件数量和存储空间大小两种方式来控制,在默认情况下 Logstash 持久化队列容量为 1024MB,而事件数量则没有设置上限。当持久化队列达到了容量上限,Logstash 会通过控制输入插件产生事件的频率来防止队列溢出,或者拒绝再接收输入事件直到队列有空闲空间。持久化队列事件数量容量可通过 queue. max events 修改,而存储空间容量则可通过 queue. max bytes 来修改。
当 Logstash 开启了持久化队列时,即使 Logstash 发生了宕机,其实也不会造成数据的丢失。对于 ES 侧而言,Logstash 是调用 Bulk API 向 ES 批量输出事件的,只有 ES 输出插件确认已经处理了事件,持久化队列才会将事件从持久化队列即磁盘中删除。因此,当 Logstash 重启时,仍然能从磁盘中恢复之前未曾处理的数据。
死信队列介绍可参考:logstash高可用之队列、死信队列
ES数据重复采集问题
在项目运行的过程中,忽然发现某一天的数据采集每条日志都采入了两次,这是什么原因?
主要有两方面原因:
- 多个filebeat采集同一条日志发送给logstash;
- Filebeat 在处理过程中关闭,或者在确认事件之前断开了连接(filebeat重试功能)
我们是第一个原因,因为我们项目中,一个pod有两个容器,一个是filebeat容器,一个是app容器,然后我们起了两个pod,并且我们将日志挂到了共享磁盘上,就导致两个filebeat读了同一个文件两次
解决方案
要么改filebeat扫描位置,要么在logstash统一处理,但是filebeat那块处理起来有点麻烦,需要制定规则,所以我们统一在logstash这里做了处理
需要处理两部分
-
logstash插件fingerprint
这个东西就类似一个指纹,给你唯一标识原始事件。可以将原始事件中的一个或多个字段(默认为消息字段)作为源来创建一致的哈希值 (hash)。一旦创建了这些指纹,你就可以将其用作下游Elasticsearch输出中的文档 ID
-
es添加document_id
Elasticsearch 的索引编制过程。 Elasticsearch 提供了一个 REST API 来为你的文档建立索引。你可以选择提供唯一代表你的文档的 ID,也可以让 Elasticsearch 为你生成ID。如果你将 HTTP PUT 与索引 API 一起使用,Elasticsearch 希望你提供一个 ID。如果已经存在具有相同 ID 的文档,Elasticsearch 将用你刚才提供的文档替换现有内容-最后索引的文档将获胜。如果使用 POST 动词,则即使语料库中已经存在内容,Elasticsearch 也会生成具有新ID的新文档。例如,假设你刚在一秒钟之前为博客文章建立了索引,并使用 POST 动词重新发送了同一篇博客文章,Elasticsearch 创建了另一个具有相同内容但新具有 ID 的文档
具体用法
input {
http {
id => "data_http_input"
}
}
filter {
fingerprint {
# 在这里可以定义任何你想定义的字段,用部分字段进行加密
source => [ "sensor_id", "date"]
# 输出到指定字段
target => "[@metadata][fingerprint]"
# 加密算法
method => "SHA1"
# 密钥
key => "liuxg"
concatenate_sources => true
base64encode => true
}
}
output {
stdout {
codec => rubydebug
}
elasticsearch {
manage_template => "false"
index => "fingerprint"
hosts => "localhost:9200"
# es文档id
document_id => "%{[@metadata][fingerprint]}"
}
}
参考文献: Beats:如何避免重复的导入数据
logstash采集json之后,有好多/u0000问题
这个问题比较恶心,总体来说是编码问题,网上很多帖子都采用下列方案来解决:
filter {
mutate{
gsub => [ "message", "\u0000", " " ]
}
}
但其实是不对的,本质问题还是编码问题,你如果按照上面配置后,就会将一个原本是json的内容,变成不是json了,也会丢失很多数据,这种解决方案是下下策
解决方案
本质上还是应该在编码问题上找,要么在logstash上处理编码,要么在filebeat处理编码,还有一种是在日志输出上处理编码(比如logback)
我这里在filebeat上处理的编码,因为不是所有项目都用到了logback,还有,在logstash上处理也行(我在logstash上没找到encoding类似的参数=.=//)
filebeat配置如下:
filebeat.inputs:
- type: log
paths:
- /home/logs/biz/*.log
encoding: utf-8
fields:
fb_collect_type: bizlog
send_kafka: "false"
fields_under_root: true
- type: log
paths:
- /home/logs/sys/*.log
encoding: utf-8
fields:
fb_collect_type: syslog
fields_under_root: true
output.logstash:
hosts:
- "xxxxxx"
processors:
- drop_fields:
fields: ["ecs", "agent", "log"]
或许这个方案还有问题,我现在还是或多或少的露一点日志,现在这里记一下,后续解决了,再来更新解决方案