1. 方案介绍
所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语。指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。 埋点的技术实质,是先监听软件应用运行过程中的事件,当需要关注的事件发生时进行判断和捕获。本文主要介绍nginx,logstash-output-datahub,odps来实现埋点。
上图的流程是这样的,应用推送规定的参数到埋点服务器,nginx接收埋点信息,将其转换成json文件保存到服务器上,然后logstash读取json文件,经过过滤将其推送到datahub,最后odps定时去datahub中拉取埋点数据。
注:这里为什么选择gif文件返回,最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节。同样的响应,GIF可以比BMP节约41%的流量,比PNG节约35%的流量。
2. 方案优势
传统的埋点方案:开发人员编写java埋点应用,通过post的方式获取埋点信息,再推送到datahub。
与传统的埋点方案相比,这个方案有以下几点优势
2.1. 响应速度快
2.2. 防止跨域
一般而言,埋点域名都不是当前域名,所以所有的接口请求都会构成跨域。而跨域请求很容易出现由于配置不当被浏览器拦截并报错,这是不能接受的。但图片的src属性并不会跨域,并且同样可以发起请求。(排除接口上报)
2.3. 防止阻塞页面加载,影响用户体验
通常,创建资源节点后只有将对象注入到浏览器DOM树后,浏览器才会实际发送资源请求。反复操作DOM不仅会引发性能问题,而且载入js/css资源还会阻塞页面渲染,影响用户体验。
但是图片请求例外。构造图片打点不仅不用插入DOM,只要在js中new出Image对象就能发起请求,而且还没有阻塞问题,在没有js的浏览器环境中也能通过img标签正常打点,这是其他类型的资源请求所做不到的。(排除文件方式)
3. 安装教程
3.1. 第一步:配置nginx
由于公司ecs镜像都有nginx,因此安装nginx的教程省略....此处主要讲解一下nginx配置文件的设置。
#auto_report日志输出格式
log_format auto_report '{"trackTime": "$time_iso8601", '
'"trackid": "$arg_trackid", '
'"uuid": "$arg_uuid", '
'"accessKeyId": "$arg_accessKeyId", '
'"libVersion": "$arg_libVersion", '
' }';
#login_report日志输出格式
log_format login_report '{"trackTime": "$time_iso8601", '
'"trackid": "$arg_trackid", '
'"uuid": "$arg_uuid", '
'"accessKeyId": "$arg_accessKeyId", '
'"userType": "$arg_userType", '
'"userId": "$arg_userId", '
'"userName": "$arg_userName", '
'"idCard": "$arg_idCard", '
'"belong": "$arg_belong", '
'"UA": "$arg_UA", '
'"referer": "$arg_referer", '
'"url": "$arg_url", '
'"pageName": "$arg_pageName", '
'"pageLevel": "$arg_pageLevel", '
'"libVersion": "$arg_libVersion", '
' }';
#获取埋点时间yyyyMMdd
map $time_iso8601 $logdate {
'~^(?<ymd>\d{4}-\d{2}-\d{2})' $ymd;
default 'date-not-found';
}
server {
listen 8123;
server_name 127.0.0.1;
charset utf-8;
client_max_body_size 200m;
#charset koi8-r;
# 静态页面目录
root /home/md;
index index.html;
location /md/auto.gif {
log_subrequest on;
#json的输出路径
access_log /home/md/log/auto_report_$logdate.json auto_report;
#返回一个空的文件
add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";
add_header Pragma "no-cache";
add_header Cache-Control "no-cache, max-age=0, must-revalidate";
#返回一个1×1的空gif图片
empty_gif;
}
location /md/login.gif {
log_subrequest on;
#json的输出路径
access_log /home/md/log/login_report_$logdate.json login_report;
#返回一个空的文件
add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";
add_header Pragma "no-cache";
add_header Cache-Control "no-cache, max-age=0, must-revalidate";
#返回一个1×1的空gif图片
empty_gif;
}
}
将这个文件放到指定的路径下, 重启nginx即可。
做完这一步你可以尝试触发一下,然后再json的输出路径下看一下是否有json文件生成。
注意:配置文件37行,root /home/md; 这个路径配置错误的话,则不会生成json文件,但是./nginx -t时通过的,作者的血泪史
3.2. 第二步:安装logstash-output-datahub
3.2.1. 一键安装
阿里云提供了免安装下载:点此下载
然后解压即可使用
3.2.2. 单独安装
安装Logstash: 参看Logstash官网提供的[安装教程]进行安装工作。特别注意的是,最新的Logstash需要Java 7及以上版本。安装DataHub插件: 下载所需要的插件。
上传至DataHub请使用: DataHub Logstash Output插件
使用如下命令进行安装:
$ {LOG_STASH_HOME}/bin/logstash-plugin install --local logstash-output-datahub-1.0.10.gem
$ {LOG_STASH_HOME}/bin/logstash-plugin install --local logstash-input-datahub-1.0.10.gem
LOG_STASH_HOME:指安装logstash的路径
如果安装时遇到类似如下错误:
WARNING: can not set Session#timeout=(0) no session context
先确认机器是否能够访问外网。网络能通的情况下,可以尝试修改为国内的镜像源。 https://gems.ruby-china.com/
注意:此处强烈推荐一键安装,有些项目的服务器不允许访问外网
3.3. 第三步:配置logstash的配置文件
#读取json文件
input {
file {
path => ["/home/md/log/auto_report_*.json"]
type => "auto"
start_position => "beginning"
#点位存储位置
sincedb_path => "/home/md/sincedb/filedatahub"
codec => json {
charset => "UTF-8"
}
}
file {
path => ["/home/md/log/login_report_*.json"]
type => "login"
start_position => "beginning"
#点位存储位置
sincedb_path => "/home/md/sincedb/filedatahub"
codec => json {
charset => "UTF-8"
}
}
}
#过滤器
filter {
if [type] == "auto"{
mutate {
#json到datahub字段映射
rename => {
"trackTime" => "tracktime"
"trackid" => "trackid"
"uuid" => "uuid"
"accessKeyId" => "accesskeyid"
"libVersion" => "libversion"
}
}
}
if [type] == "login"{
mutate {
rename => {
"trackTime" => "tracktime"
"trackid" => "trackid"
"uuid" => "uuid"
"accessKeyId" => "accesskeyid"
"userType" => "usertype"
"userId" => "userid"
"userName" => "username"
"idCard" => "idcard"
"belong" => "belong"
"UA" => "ua"
"referer" => "referer"
"url" => "url"
"pageName" => "pagename"
"pageLevel" => "pagelevel"
"libVersion" => "libversion"
}
}
}
}
output {
if [type] == "auto"{
datahub {
access_id => "xxxx"
access_key => "xxxxx"
endpoint => "http://xxxxxxxx"
project_name => "xxxxx"
topic_name => "xxxxx"
dirty_data_continue => true
dirty_data_file => "/usr/local/logstash-2/data/auto_dirty.log"
dirty_data_file_max_size => 1000
}
}
if [type] == "login"{
datahub {
access_id => "xxxxx"
access_key => "xxxxx"
endpoint => "http://xxxxxxxx"
project_name => "xxxxx"
topic_name => "xxxxx"
dirty_data_continue => true
dirty_data_file => "/usr/local/logstash-2/data/login_dirty.log"
dirty_data_file_max_size => 1000
}
}
}
注意:第8,18行,sincedb_path => "/home/md/sincedb/filedatahub"必须要配,并且filedatahub是文件,不是文件夹
4. 优化点
4.1. nginx时间点优化
nginx的$time_iso8601默认时间格式是2022-07-21T12:46:11+08:00,如果你想要把它转换成yyyy-MM-dd HH:mm:ss这种格式的话,需要进行以下操作。
首先修改ngx_http_log_module.c文件(src/http/modules/ngx_http_log_module.c):b
{ ngx_string("time_iso8601"), sizeof("1970-09-28T12:00:00+06:00") - 1,
更改后
{ ngx_string("time_iso8601"), sizeof("1970-09-28 12:00:00") - 1,
然后修改ngx_times.c文件(src/core/ngx_times.c):st
[sizeof("1970-09-28T12:00:00+06:00")];
更改后
[sizeof("1970-09-28 12:00:00")];
ngx_cached_http_log_iso8601.len = sizeof("1970-09-28T12:00:00+06:00") - 1;
更改成
ngx_cached_http_log_iso8601.len = sizeof("1970-09-28 12:00:00") - 1;
(void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
tm.ngx_tm_year, tm.ngx_tm_mon,
tm.ngx_tm_mday, tm.ngx_tm_hour,
tm.ngx_tm_min, tm.ngx_tm_sec,
tp->gmtoff < 0 ? '-' : '+',
ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
更改成
(void) ngx_sprintf(p3, "%4d-%02d-%02d %02d:%02d:%02d",
tm.ngx_tm_year, tm.ngx_tm_mon,
tm.ngx_tm_mday, tm.ngx_tm_hour,
tm.ngx_tm_min, tm.ngx_tm_sec);
最后执行make && make install指令即可
注意:要先执行./nginx -s stop,然后再启动nginx