背景
利用服务器日志做分析是很多公司进入大数据分析的第一步,也是很关键的一步。大部分情况下,这些公司在考虑进行大数据分析的时候,都会遇到以下问题:
- 团队里面缺乏了解大数据技术栈的工程师
- 都听过Hadoop,想要学习Hadoop,但是不知道从何入手
- 从市面上寻找大数据人才效果不理想
- 不愿意一下子投入过多的资金去组建一个专门的大数据团队
虽然Hadoop没有办法一下子搭起来,但是其实在刚开始进入大数据的时候完全可以用MPP数据库来快速满足需求。但是你可能会有疑问,MPP能够代替Hadoop吗?要回答这个问题,首先要理解Hadoop的出现到底解决了什么问题:
- 传统的单节点关系型数据库,要提升性能,只能通过scale up的方式,即增加cpu/内存/硬盘。到后面提升5%的计算能力可能是前面10倍的成本投入。Hadoop利用分布式的思想,通过shared-nothing的架构,实现了scale out的能力。在这样的架构下面,加入同样性能的机器,可以达到线性提升处理性能的效果,投入产出成正比。
- 关系型数据库对于非/半结构化数据不是特别友好,主要表现在关系型数据库是以行列为结构存储数据的,无法直接把json,xml这类型的数据插入进去。Hadoop的优势是可以通过编写特定的input reader,来解释这些数据格式,从而达到处理非结构数据的能力。但是不能忘记的一点是,为了提升处理性能,即使使用Hadoop,最后也是要将数据结构化的。
通过上面的描述,其实思路就很清晰了。如果可以有一个shared-nothing架构的关系型数据库,将要导入的数据预先进行结构化处理,那么我们还是可以在关系型数据库(MPP)里面做大数据分析。显然ADB就是这么一个场景下最合适的选择。
组件介绍
这一章介绍一下本方案使用到的各种组件
名称 | 介绍 |
---|---|
Nginx | 反向代理,一般被用来做负载均衡。作为网络流量的总入口,会沉淀所有的用户行为。利用这里的日志作为分析可以得到最全面的数据视图。 |
Logstash | 日志采集工具,可以对采集到的数据进行一定的预处理,通过配置文件进行格式化改造。 |
DataHub | 数据管道,提供对数据的发布和订阅。支持直接导出到一些常用的数据存储包括MaxCompute,OSS,ADB等。 |
ADB for MySQL | 高并发低延时的PB级别数据仓库,MPP架构,完全兼容MySQL协议。 |
搭建步骤
安装Nginx
在CentOS下面,安装Nginx
yum install nginx
成功安装的话,会看到如下内容
已安装:
nginx.x86_64 1:1.12.2-3.el7
作为依赖被安装:
nginx-all-modules.noarch 1:1.12.2-3.el7 nginx-mod-http-geoip.x86_64 1:1.12.2-3.el7 nginx-mod-http-image-filter.x86_64 1:1.12.2-3.el7 nginx-mod-http-perl.x86_64 1:1.12.2-3.el7
nginx-mod-http-xslt-filter.x86_64 1:1.12.2-3.el7 nginx-mod-mail.x86_64 1:1.12.2-3.el7 nginx-mod-stream.x86_64 1:1.12.2-3.el7
完毕!
完成安装之后,需要定义Nginx打印日志的格式。在这个例子中,因为我们想要统计网站访问的UV和每个请求的响应时长,所以我们需要把 $request_time
和 $http_cookie
添加进去。另外,Nginx日期的默认打印方式是 07/Jul/2019:03:02:59
, 对于我们后面做分析时候查询不是十分友好,所以要把它们统一改成 2019-07-11T16:32:09
的格式。
找到/etc/nginx/nginx.conf
,用vi打开,将log_format
更改如下:
log_format main '$remote_addr - [$time_iso8601] "$request" '
'$status $body_bytes_sent $request_time "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$http_cookie"' ;
执行下面命令,重启Nginx使配置生效:
service nginx restart
这时去看一下日志(位于/var/log/nginx/access.log
),会发现日志打印格式如下
119.35.6.17 - [2019-07-14T16:39:17+08:00] "GET / HTTP/1.1" 304 0 0.000 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36" "-" "-"
由于默认的Nginx主页不会记录cookie,建议后面挂一个带登陆的系统来做验证
部署DataHub
DataHub的控制台地址 https://datahub.console.aliyun.com/datahub
首先要创建一个Project,取名为log_demo
:
进入到这个log_demo
项目之后,要创建一个Topic, 我们可以取名topic_nginx_log
:
Topic的类型选择Tuple,这是一种带schema的组织形式,更方便我们查看和后面的处理。Schema的定义跟我们要从日志中取得的字段是强相关的,所以建立的时候需要确保没有错误,否则可能导致后面日志写入的时候出错。
字段名称 | 意义 | 类型 | 对应Nginx日志的字段 |
---|---|---|---|
remote_ip | 请求的来源IP | STRING | $remote_addr |
date | 请求发生的日期 | BIGINT | $time_iso8601 的日期部分 |
time | 请求发生的时间,24小时制,精确到秒 | STRING | $time_iso8601 的时间部分 |
method | Http请求的方法 | STRING | $request 的请求动作部分,例如GET |
request | 请求的uri和param部分 | STRING | $request的uri和param部分 |
http_version | Http版本号 | STRING | $request的http版本号部分 |
status | 请求的返回状态码 | STRING | $status |
bytes | 请求体的大小 | BIGINT | $body_bytes_sent |
request_time | 请求的耗时 | DOUBLE | $request_time |
referer | 发出这个请求之前,用户在哪个页面 | STRING | $http_referer |
agent | 客户端使用的操作系统和浏览器 | STRING | $http_user_agent |
xforward | 请求经过的代理 | STRING | $http_x_forwarded_for |
cookie | 用户的标识 | STRING | $http_cookie |
成功创建之后,可以在这个Topic的Schema里面看到如下图展示
安装Logstash
官方的Logstash没有提供DataHub的兼容工具,所以建议到DataHub的官网上面去下载最新的兼容版本,减少中间融合操作成本。具体的介绍页面在这里。
回到控制台,输入下面命令,下载和解压Logstash到你想要的目录
##注意,这里的版本号和下载链接可能因为更新缘故有区别,建议到介绍页面去获取最新链接
wget http://aliyun-datahub.oss-cn-hangzhou.aliyuncs.com/tools/logstash-with-datahub-6.4.0.tar.gz?spm=a2c4g.11186623.2.17.60f73452tHbnZQ&file=logstash-with-datahub-6.4.0.tar.gz
tar -zxvf logstash-with-datahub-6.4.0.tar.gz
安装完成之后需要配置Logstash,这里有两个工作需要完成。第一,由于我们需要将日志处理成结构化的数据,所以在抓取的过程中,还需要做一点加工。Logstash的grok可以帮我们轻松地完成这个任务。第二,我们需要告诉Logstash从哪里(Nginx的日志存放位置)抓起日志文件,最后要同步到哪里(DataHub)去。
配置grok
我们需要让grok能够理解日志文件并进行转换,需要往grok-pattern文件里面添加一个新格式。这个文件存放在刚刚下载的Logstash目录里面,具体路径如下 :
logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-xxx/patterns/grok-patterns
用vi打开这个文件,把下面的新pattern填到文件最末端即可
DATE_CHS %{YEAR}\-%{MONTHNUM}\-%{MONTHDAY}
NGINXACCESS %{IP:remote_ip} \- \[%{DATE_CHS:date}T%{TIME:time}\+08:00\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:bytes} %{NUMBER:request_time} %{QS:referer} %{QS:agent} %{QS:xforward} %{QS:cookie}
配置Logstash
在Logstash的目录里面,有一个config
文件夹,里面有一个logstash-sample.conf
文件。打开它,把它改成
input {
file {
path => "/var/log/nginx/access.log" #nginx的日志位置,默认在这里。
start_position => "beginning" #从什么位置开始读起日志文件,beginning表示从最开始。
}
}
filter {
grok {
match => {"message" => "%{NGINXACCESS}"} #当grok获取到一个消息时,怎么去转换格式
}
mutate {
gsub => [ "date", "-", ""] #因为日期在后面要用作ADB表的二级分区,所以需要把非数字字符去掉
}
}
output {
datahub {
access_id => "<access-key>" #从RAM用户获取access key
access_key => "<access-secrect>" #从RAM用户获取access secret
endpoint => "http://dh-cn-shenzhen-int-vpc.aliyuncs.com" #DataHub的endpoint,取决于把project建立在哪个区域
project_name => "log_demo" #刚刚在DataHub上面创建的项目名称
topic_name => "topic_nginx_log" #刚刚在DataHub上面创建的Topic名称
dirty_data_continue => true #脏数据是否继续运行
dirty_data_file => "/root/dirty_file" #脏数据文件名称,脏数据会被写入到这里
dirty_data_file_max_size => 1000 #脏数据的文件大小
}
}
完成这个配置之后,我们可以启动Logstash来验证是否有日志被写入到DataHub里面了。在Logstash的根目录里面,执行如下命令
bin/logstash -f config/logstash-sample.conf
看到这个如下日志代表启动成功
ending Logstash logs to /root/logstash-with-datahub-6.4.0/logs which is now configured via log4j2.properties
[2019-07-12T13:36:49,946][WARN ][logstash.config.source.multilocal] Ignoring the 'pipelines.yml' file because modules or command line options are specified
[2019-07-12T13:36:51,000][INFO ][logstash.runner ] Starting Logstash {"logstash.version"=>"6.4.0"}
[2019-07-12T13:36:54,756][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>2, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50}
[2019-07-12T13:36:55,556][INFO ][logstash.outputs.datahub ] Init datahub success!
[2019-07-12T13:36:56,663][INFO ][logstash.inputs.file ] No sincedb_path set, generating one based on the "path" setting {:sincedb_path=>"/root/logstash-with-datahub-6.4.0/data/plugins/inputs/file/.sincedb_d883144359d3b4f516b37dba51fab2a2", :path=>["/var/log/nginx/access.log"]}
[2019-07-12T13:36:56,753][INFO ][logstash.pipeline ] Pipeline started successfully {:pipeline_id=>"main", :thread=>"#<Thread:0x445e23fb run>"}
[2019-07-12T13:36:56,909][INFO ][logstash.agent ] Pipelines running {:count=>1, :running_pipelines=>[:main], :non_running_pipelines=>[]}
[2019-07-12T13:36:56,969][INFO ][filewatch.observingtail ] START, creating Discoverer, Watch with file and sincedb collections
[2019-07-12T13:36:57,650][INFO ][logstash.agent ] Successfully started Logstash API endpoint {:port=>9600}
这时候可以开始去刷一下Nginx代理的页面了。默认Nginx自带一个静态的网页,可以通过访问80端口打开。产生日志的话,Logstash的日志会马上打印如下信息
[2019-07-12T13:36:58,740][INFO ][logstash.outputs.datahub ] [2010]Put data to datahub success, total 10
然后在DataHub上,可以看到抽样数据如下
配置ADB
先到ADB的控制台去创建一个新的集群,数据库的名字取nginx_logging
。需要注意的是,当ADB创建好之后,在控制台那边看不到数据库的名字,转而变成了集群名称。目前看来称谓不一样,但是其实是一回事。
新的集群初始化会需要一点时间。待初始化完成之后,可以通过控制台的登陆数据库
按钮跳转到DMS(数据管理)去创建用于存放日志的数据库。在创建数据库之前,需要先理解ADB的几个核心概念,这样设计出来的表性能才会更佳。
一级分区
要理解一级分区,需要先理解MPP数据库是怎么提升性能的。本文的背景中说到,MPP是可以线性扩展的,但是有一个前提,数据要均匀分布。这样在一个Query
过来的时候,才能让尽量多的磁盘被利用起来。在ADB中,数据是否均匀分布,主要取决选择哪个列作为一级分区。千万不要选择数据倾斜的列作为分区列,例如日期,性别,或者存在大量空值的列。在本例中,比较适合的应该是remote_ip
这一列。
二级分区
二级分区是在一级分区的基础上做二次切分,可以理解为隐性地帮用户把一个表切成了多个表,从而提升全表查询的性能。此外,二级分区还有一个优点,就是可以设置分区个数。在超过分区个数的时候,ADB会自动将历史最久的一个二级分区删除,实现历史数据自动清楚的效果。在本例中,date
这个列特别时候做二级分区。假如我们想保留30天的日志数据,我们只需要将二级分区设置成30个即可。注意,二级分区的列一定要是整数类型,例如bigint
或者long
。
下面是创建表nginx
的SQL
CREATE TABLE nginx_logging.nginx(
remote_ip varchar NOT NULL COMMENT '',
date bigint NOT NULL COMMENT '',
time varchar NOT NULL COMMENT '',
method varchar NOT NULL COMMENT '',
request varchar COMMENT '',
http_version varchar COMMENT '',
status varchar COMMENT '',
bytes bigint COMMENT '',
request_time double COMMENT '',
referer varchar COMMENT '',
agent varchar COMMENT '',
xforward varchar COMMENT '',
cookie varchar COMMENT '',
PRIMARY KEY (remote_ip,date,time)
)
PARTITION BY HASH KEY (remote_ip) PARTITION NUM 128 -- remote_ip作为一级分区
SUBPARTITION BY LIST KEY (date) -- date作为二级分区
SUBPARTITION OPTIONS (available_partition_num = 30) -- 二级分区30个,代表保留30天的数据
TABLEGROUP logs
OPTIONS (UPDATETYPE='realtime') -- 由于要被DataHub更新,所以一定要选择是realtime的表
COMMENT ''''
成功配置创建表之后,ADB这边的准备任务就算完成了。需要提示一下,默认ADB的网络连接信息只有公有网络
和经典网络
的连接。专有网络
的连接地址需要通过配置打开。因为ADB完全兼容MySQL
协议,所以我们要从本地登陆的话,可以利用公有网络
的地址。而DataHub访问ADB,目前使用的是经典网络
的连接。
配置DataConnector
回到刚刚创建的Topic, topic_nginx_log
, 下面。右上角有一个按钮叫+DataConnector
。这里需要输入几个关于ADB的信息:
Host : #ADB集群经典网络的连接地址
Port : #ADB集群经典网络的端口
Database : nginx_logging #之前建立ADB集群时候输入的名字,同时也是这个ADB集群的名字
Table : nginx #上面建立的表名
Username : <access-key> #RAM用户的access key
Password : <access-secret> #RAM用户的access secret
模式 : replace into #如果主键冲突,会覆盖掉记录
成功配置之后,在DataConnectors
下面就可以看到相关的信息了。如果之前DataHub上面已经有数据,现在查询ADB里面的nginx表就可以将这些记录查出来。
至此,整个数据链路就算搭建完成了。