基于ELK(7.2.0版本)框架的ETL系统架构及技术实现

ETL介绍

Extract-Transform-Load的缩写,用来描述将数据从来源端经过萃取(extract)、转置(transform)、加载(load)至目的端的过程。

通用架构

先来一张通用架构图:
在这里插入图片描述

数据源:数据源可以来自多个不同种类的源,例如数据库,日志文件,系统日志,数据库日志,业务日志等。

数据收集:采集数据,日志等数据文件。常用的采集工具有Flume,Logstash,Filebeat等。

数据缓冲:数据收集的过程是从多个源端采集过来,高并发,数据量大,如果直接处理会造成瓶颈甚至系统崩溃,所以一般需要使用消息队列将请求缓冲,根据系统处理能力按序处理。常用的消息中间件有Kafka,ActiveMQ,RabbitMQ等。

数据清洗:将送过来的数据做过滤,格式化,转换等操作,把各个形式的数据源统一转换为希望的数据格式。

离线存储:存储清洗后的数据,一般数据量都是非常巨大的,传统关系型数据库不利于存储和索引,所以离线存储使用非关系型数据库也就是常说的NOSQL,将数据创建索引后存储,用于数据分析和查询,效率很高。常用的有HBase,MongoDB,Elasticsearch等。

实时存储:业务上需要使用到的数据存入关系型数据库,用于业务上的功能,例如告警,运营统计等。常用的关系型数据库有Oracle,MySQL,SQL server等。

系统架构

在这里插入图片描述

Filebeat

数据采集使用Filebeat, Filebeat是Elastic旗下Beats

系列中负责日志文件采集的产品,它是一个轻量级的日志传输agent,安装在数据源本地,监视日志文件目录和日志文件变化,将指定日志转发给Kafka,
Logstash,Elasticsearch,Redis等 。

这里我们在每个数据源本机安装Filebeat,并转给Kafka。

Kafka

Kafka是时下比较流行的消息中间件,它的运行需要依赖Zookeeper,这里我们使用Kafka + ZK集群,接收Filebeat的数据。

关于消息中间件的功能这里就不介绍了,可以在另一篇文章《ActiveMQ环境搭建与使用》了解消息中间件的功能和原理,这里主要说一下Kafka如何区别点对点模式(p2p)和订阅模式(topic)。

在这里插入图片描述

上图中每一个broker对应Kafka集群中的一个Kafka实例,Filebeat是producer,将请求送给broker,默认情况下Kafka是订阅模式将消息广播给所有的consumer,例如我们部署了两个Logstash做高可用,即为两个consumer,其中一个broker接收到消息log1这个数据后会将log1同时发给两个Logstash处理,这样处理数据岂不是重复了?所以我们需要使用p2p的方式,即log1只有一个consumer可以接收并处理,在Kafka中需要使用consumer group的概念来实现,即给consumer分组,同一个组内的consumer会去竞争消息,所以我们把两个Logstash放入同一个consumer组内,即可实现高可用。

Logstash

同样,Logstash也是elastic旗下的产品,它是服务器端的数据处理通道,能够同时从多个来源采集数据,转换数据,然后将数据发送到存储中。Logstash具备实时解析和转换数据的功能,数据从源传输到存储的过程中Logstash
过滤器能够解析各个事件,识别已命名的字段以构建结构,并将它们转换成通用格式,以便更轻松、更快速地分析和实现商业价值,例如:

l 利用 Grok 从非结构化数据中派生出结构

l 从 IP 地址破译出地理坐标

l 将 PII 数据匿名化,完全排除敏感字段

l 简化整体处理,不受数据源、格式或架构的影响

这里我们主要使用Logstash的数据处理功能,将采集到的数据过滤,转换为我们需要的格式和内容,再存储到下游的数据库中。

常用插件

grok

将非结构化事件数据分析到字段中。 这个工具非常适用于系统日志,Apache和其他网络服务器日志,MySQL日志,以及通常为人类而不是计算机消耗的任何日志格式。

举例:

文件中的一行日志为:

2019-03-09 11:50:04.009 INFO
[MockBPServer-PacketRepository-9] [QdbLogger.java:23]
[Lid:127.0.0.1-92-bp1] sessionId: [329]
sequenceNo: [6] request: [ExecutionDynamicParamRequest] byteLength: [99]

我们利用grok将这行日志的时间和日志级别取出来

代码为:

filter {

grok {

match => {

                  "message"
=>
"(?<date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s+(?<level>[A-Z]+)(?<text>.*)"

   
       }

}

}

代码的含义是用正则表达式匹
配上游发送的数据,如果匹配成功则记录到message这个字段中,并且匹配到< date >和< level >的数据也记录到date和level的字段中。

其中的正则表达式\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3}表示为日期格式xxxx-xx-xx xx:xx:xx.xxx

[A-Z]+表示多个大写英文字母

.*表示剩下的所有字符

所以最后的匹配结果为:

{

 
"date": "2019-03-09 11:50:04.009",

 
"level": "INFO",

 
"text": " [MockBPServer-PacketRepository-9]
[QdbLogger.java:23] [Lid:127.0.0.1-92-bp1] 
sessionId: [329] sequenceNo: [6] request: [ExecutionDynamicParamRequest]
byteLength: [99]"

}

date

从字段解析日期以用作事件的Logstash时间戳,以下配置解析名为logdate的字段以设置Logstash时间戳:

代码:

filter
{

  date {

    match => [ "logdate", "yyyy-MM-DD
HH:mm:ss" ]

  }

}

返回结果:

{“logdate”:“2018-01-01
12:02:03”}

dissect

基于分隔符原理解析数据,解决grok解析时消耗过多cpu资源的问题

使用分隔符将非结构化事件数据提取到字段中。 解剖过滤器不使用正则表达式,速度非常快。
但是,如果数据的结构因行而异,grok过滤器更合适。

dissect的应用有一定的局限性:主要适用于每行格式相似且分隔符明确简单的场景

dissect语法比较简单,有一系列字段(field)和分隔符(delimiter)组成

%{}字段

%{}之间是分隔符12

例如,假设日志中包含以下消息:

Apr 26
12:20:02 localhost systemd[1]: Starting system activity accounting tool…1

代码:

filter
{

	dissect {

           	mapping => { "message" =>
"%{ts} %{+ts} %{+ts} %{src} %{prog}[%{pid}]: %{msg}" 

		}

      }

}

结果:

{

  "msg"        => "Starting system activity
accounting tool...",

  "src"        => "localhost",

  "pid"        => "1",

  "message"    => "Apr 26 12:20:02 localhost systemd[1]:
Starting system activity accounting tool...",

  "prog"       => "systemd",

  "ts"         => "Apr 26 12:20:02"

}

说明: Apr 26 12 :20:02

%{ts}
%{+ts} %{+ts} #+代表该匹配值追加到ts字段下

{

“ts”:“Apr 26 12:20:02”

}

two
three one go

%{+order/2}
%{+order/3} %{+order/1} %{+order/4}
#/后面的数字代表拼接的次序

{

“order”: “one two three go”

}

a=1&b=2

%{?key1}=%{&key1}&%{?key2}=%{&key2} #%{?}代表忽略匹配值,但是富裕字段名,用于后续匹配用;%{&}代表将匹配值赋予key1的匹配值

{
“a”:“1”,
“b”:“2” }

dissect可以自动处理空的匹配值

John
Smith,Big Oaks,Wood Lane,Hambledown,Canterbury,CB34RY

%{name},%{addr1},%{addr2},%{addr3},%{city},%{zip}

Jane
Doe,4321 Fifth Avenue,New York,87432

{

“name”:“Jane Doe”,

“addr1”:"4321 Fifth

Avenue",

“addr2”:"",

“addr3”:"",

“city”:“New York”,

“zip”:“87432”

}

#dissect分割后的字段值都是字符串,可以使用convert_datatype属性进行类型转换

filter{

	dissect{

		convert_datatype => {age =>"int"
		}
   	}

}

mutate

可以对字段进行各种操作,比如重命名、删除、替换、更新等,主要操作如下:

convert
#类型转换

gsub
#字符串替换

split/join/merge
#字符串切割、数组合并为字符串、数组合并为数组

rename
#字段重命名

update/replace
#字段内容更新或替换

remove_field
#删除字段

drop

过滤掉不需要的数据

代码:

filter {

 	if ([level] !~ "(ERROR|INFO)"){

		drop {}
	}

}

说明:

如果level字段不是ERROR或者INFO 则丢弃

Elasticsearch

Elastic旗下最核心产品,Elasticsearch是一个分布式、RESTful 风格的搜索和数据分析引擎,能够执行及合并多种类型的搜索(结构化数据、非结构化数据、地理位置、指标),搜索方式随心而变,具体功能和部署使用方法请参考另一篇文章《Elasticsearch环境搭建与使用》

这里我们搭建es集群用来存储清洗后的日志文件。

Kibana

Elastic旗下产品,一般搭配Logstash和Elasticsearch使用,被称为ELK组合。通过Kibana,你可以对自己的Elasticsearch中的数据进行可视化,并可以自定义展示报表,方便查询和管理es中的数据。

环境搭建

基于docker的搭建(首先请安装docker和docker-compose)

镜像下载

docker pull docker.elastic.co/elasticsearch/elasticsearch:7.2.0

docker pull
docker.elastic.co/kibana/kibana:7.2.0

docker pull docker.elastic.co/logstash/logstash:7.2.0

docker pull
docker.elastic.co/beats/filebeat:7.2.0

docker pull wurstmeister/kafka

配置

宿主机映射存储目录

[root@hbase es]# pwd

/root/Log-mgt/es

[root@hbase es]# mkdir data

[root@hbase es]# chmod 777 data

filebeat.yml

每一台数据源的主机上都需要配置filebeat agent的配置文件

filebeat.inputs:

-
type: log

  enabled: true

  paths:

    - /var/log/*.log

  multiline: 

     pattern:
"^(([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))"

     negate: true

     match: after

首先input部分配置fileb

eat需要采集的信息

type:采集的类型,有如下:

l Log: 读一般日志文件的每一行

l Sdin:读事件

l Container:读docker容器日志,指向日志路径

l Redis:读redis日志

l UDP:读UDP上的事件

l Docker:读docker容器日志,指向容器id

l TCP:读TCP上的事件

l Syslog:读TCP或UDP上的事件

l NetFlow:读NetFlow和IPFIX

Path为日志路径

Multiline指匹配多行,如果不用,放入下游的数据是按每一行为一条,但是很多时候我们更希望一条数据是一个完整的信息,需要包含多行,所以这里需要匹配正则表达式,^(([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])) 表示以日期格式xxxx-xx-xx为开头的数据为切分。

output.kafka:

  hosts: ["172.16.23.126:9092"]

  topic: "leontest"

  ##partition策略必须为random、round_robin或者hash的其中一个

  #开启kafka的partition分区

  partition.round_robin:

    reachable_only: true

  compression: gzip

out部分的配置是将采集到的数据发送到下游,这里配置的是Kafka

topic是放入Kafka队列里的消息名,不同的数据源可以配置为不同topic

logstash.conf

input{

	kafka {

       
		client_id => "beats" 

       
		bootstrap_servers => "172.16.23.126:9092"

       
		consumer_threads => 3

       
		group_id => "groupA"

       
		topics => ["leontest"]

       
		##数据json化才能读取到message数据

       
		codec => "json"

   
	}

}

Input部分配置Logstash从哪个上游获取数据,这里配置的Kafka

这里的group_id用于实现Logstash高可用,相同groupid的Logstash竞争同一个topic,及实现p2p队列模式,不同组则为订阅模式。

filter{

   
	grok {

   
        	match => {

   
                "message" => "(?<date>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3})\s+(?<level>[A-Z]+)(?<text>.*)"

   
        	}

   
 }


#如果不是error或info删除日志


if ([level] !~ "(ERROR|INFO)") {

   
        drop {}

   
}

 

mutate {

##不在output中输出以下字段,节省空间

       
remove_field => [

            "beat",

          
        "@version",

          
        "message",

          
        "host",

          
        "meta",

          
        "input",

          
        "offset",

          
        "source",

    
              "fileset",

          
        "event",

          
        "prospector",

          
        "log",

          
        "ecs",

          
        "agent",

          
        "fields",

          
        "sort",

          
        "_version",

          
        "_score"

      
        ]

   
}

Filter部分配置Logstash的数据清洗规则

这里我们将不是INFO和ERROR的日志记录丢弃,并且输出日志级

别和日志时间戳,将多余字段删除

output{

	elasticsearch {

		codec => "json"

		hosts => ["172.16.23.126:9200"] 

	index => "leontest"

 }

}

输出部分的配置我们配置输出到Elasticsearch,输出格式为json

docker-compose编排

docker-compose.yml

version:
'2.2'

services:

#Elasticsearch容器配置

  es:

    image:
docker.elastic.co/elasticsearch/elasticsearch:7.2.0

    container_name: es

    environment:

      - node.name=es

      - cluster.initial_master_nodes=es

      - cluster.name=es-cluster

      - "ES_JAVA_OPTS=-Xms512m
-Xmx512m"

      #跨域访问

      - http.cors.enabled=true

      - http.cors.allow-origin="*"

volumes:

        #数据目录映射

      -
/root/Log-mgt/es/data:/usr/share/elasticsearch/data

    ports:

      - 9200:9200

      - 9300:9300

       #重启策略,能够使服务保持始终运行,生产环境推荐使用

    restart: "always"

 #kibana容器配置

  kibana:

    image:
docker.elastic.co/kibana/kibana:7.2.0

    container_name: kibana

    environment:

      XPACK_MONITORING_ENABLED:
"true"

     
XPACK_MONITORING_UI_CONTAINER_ELASTICSEARCH_ENABLED: "true"

      ELASTICSEARCH_HOSTS: http://172.16.23.126:9200

      CSP_STRICT: "true"

    ports:

      - 5601:5601

    restart: "always"

 #logstash容器配置

  logstash:

    image:
docker.elastic.co/logstash/logstash:7.2.0 

    restart: "always"

    container_name: logstash

    environment:

      - log.level=error

      - xpack.monitoring.enabled=true

      -
xpack.management.elasticsearch.hosts=http://172.16.23.126:9200

      -
xpack.monitoring.elasticsearch.hosts=http://172.16.23.126:9200

    ports:

      - 5044:5044 

volumes:  

#配置文件映射

      -
/root/Log-mgt/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf

     

 #filebeat容器配置

  beat:

    image:
docker.elastic.co/beats/filebeat:7.2.0

    container_name: filebeat

    user: root

    environment:

      - setup.kibana.host=172.16.23.126:5601

      - strict.perms=false

    restart: "always"

volumes:

  #配置文件映射

      -
/root/Log-mgt/filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml

      #需要采集的文件目录映射

      - /root/Log-mgt/filebeat/input:/var/log/

 #zookeeper容器配置

  zookeeper:

    image: zookeeper:latest

    container_name: zookeeper2183

    ports:

      - 2183:2181

    restart: always

 

  #kafka容器配置

  kafka:

    image: wurstmeister/kafka

    container_name: kafka

    ports:

      - 9092:9092

       #依赖zookeeper启动

    depends_on:

      - zookeeper

    restart: always

    environment:

          KAFKA_BROKER_ID: 1
    
          KAFKA_ADVERTISED_HOST_NAME: 172.16.23.126
    
          KAFKA_ZOOKEEPER_CONNECT:
    172.16.23.126:2183

启动

docker-compose up -d

效果

容器启动成功

在这里插入图片描述

Es集群运行正常

在这里插入图片描述

在filebeat采集目录下放入日志,样例为:

在这里插入图片描述

访问kibana,查询leontest的index,只找到INFO和ERROR的日志,并且ERROR日志为多行

在这里插入图片描述

在这里插入图片描述

后续

Logstash输出了日志数据到es中,同时我们也可以将一些日志的关键信息输出到mysql中,用于业务操作,例如发送短信告警。

例如如下配置:

output {

       jdbc
{

      driver_jar_path => "/etc/logstash/jdbc/mysql-connector-java-5.1.47/mysql-connector-java-5.1.47-bin.jar"

      driver_class =>
"com.mysql.jdbc.Driver"

      connection_string =>
"jdbc:mysql://mysql服务器ip:端口/数据库?user=数据库用户名&password=数据库密码"

      statement => [ "insert into 数据表 (TIME ,IP,LEVEL) values (?,?,?)","%{@timestamp}"
,"%{host}","%{level}" ]

      }

}

好了,到此为止我们什么数据都有了,接下来可以开发业务代码去使用这些数据做业务功能啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值