本帖最后由 我是楠楠 于 2018-8-29 15:03 编辑
【郑州校区】大数据离线阶段Day1之网站流量日志数据自定义采集1. 原理分析首先,用户的行为会触发浏览器对被统计页面的一个http请求,比如打开某网页。当网页被打开,页面中的埋点javascript代码会被执行。 埋点是指:在网页中预先加入小段javascript代码,这个代码片段一般会动态创建一个script标签,并将src属性指向一个单独的js文件,此时这个单独的js文件(图中绿色节点)会被浏览器请求到并执行,这个js往往就是真正的数据收集脚本。 数据收集完成后,js会请求一个后端的数据收集脚本(图中的backend),这个脚本一般是一个伪装成图片的动态脚本程序,js会将收集到的数据通过http参数的方式传递给后端脚本,后端脚本解析参数并按固定格式记录到访问日志,同时可能会在http响应中给客户端种植一些用于追踪的cookie。
2. 设计实现根据原理分析并结合Google Analytics,想搭建一个自定义日志数据采集系统,要做以下几件事: 2.1. 确定收集信息 | | | | | | | | | | | | | | | | | | | | window.screen.height & width | | | | | | | | | | | | | | | | | | | | | | | | |
2.2. 确定埋点代码埋点,是网站分析的一种常用的数据采集方法。核心就是在需要进行数据采集的关键点植入统计代码,进行数据的采集。比如以谷歌分析原型来说,需要在页面中插入一段它提供的javascript片段,这个片段往往被称为埋点代码。 [AppleScript] 纯文本查看 复制代码 <script type="text/javascript">
var _maq = _maq || [];
_maq.push(['_setAccount', 'UA-XXXXX-X']);
(function() {
var ma = document.createElement('script'); ma.type = 'text/javascript'; ma.async = true;
ma.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ma.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ma, s);
})();
</script> |
其中_maq是全局数组,用于放置各种配置,其中每一条配置的格式为: _maq.push(['Action', 'param1', 'param2', ...]); _maq的机制不是重点,重点是后面匿名函数的代码,这段代码的主要目的就是引入一个外部的js文件(ma.js),方式是通过document.createElement方法创建一个script并根据协议(http或https)将src指向对应的ma.js,最后将这个元素插入页面的dom树上。 注意ma.async = true的意思是异步调用外部js文件,即不阻塞浏览器的解析,待外部js下载完成后异步执行。这个属性是HTML5新引入的。 扩展知识:js自调用匿名函数 格式: (function(){})(); 第一对括号向脚本返回未命名的函数;后一对空括号立即执行返回的未命名函数,括号内为匿名函数的参数。 自调用匿名函数的好处是,避免重名,自调用匿名函数只会在运行时执行一次,一般用于初始化。 2.3. 前端数据收集脚本数据收集脚本(ma.js)被请求后会被执行,一般要做如下几件事: 1、通过浏览器内置javascript对象收集信息,如页面title(通过document.title)、referrer(上一跳url,通过document.referrer)、用户显示器分辨率(通过windows.screen)、cookie信息(通过document.cookie)等等一些信息。 2、解析_maq数组,收集配置信息。这里面可能会包括用户自定义的事件跟踪、业务数据(如电子商务网站的商品编号等)等。 3、将上面两步收集的数据按预定义格式解析并拼接(get请求参数)。 4、请求一个后端脚本,将信息放在http request参数中携带给后端脚本。 这里唯一的问题是步骤4,javascript请求后端脚本常用的方法是ajax,但是ajax是不能跨域请求的。一种通用的方法是js脚本创建一个Image对象,将Image对象的src属性指向后端脚本并携带参数,此时即实现了跨域请求后端。这也是后端脚本为什么通常伪装成gif文件的原因。 示例代码: [AppleScript] 纯文本查看 复制代码 (function () {
var params = {};
//Document对象数据
if(document) {
params.domain = document.domain || '';
params.url = document.URL || '';
params.title = document.title || '';
params.referrer = document.referrer || '';
}
//Window对象数据
if(window && window.screen) {
params.sh = window.screen.height || 0;
params.sw = window.screen.width || 0;
params.cd = window.screen.colorDepth || 0;
}
//navigator对象数据
if(navigator) {
params.lang = navigator.language || '';
}
//解析_maq配置
if(_maq) {
for(var i in _maq) {
switch(_maq[i][0]) {
case '_setAccount':
params.account = _maq[i][1];
break;
default:
break;
}
}
}
//拼接参数串
var args = '';
for(var i in params) {
if(args != '') {
args += '&';
}
args += i + '=' + encodeURIComponent(params[i]);
}
//通过Image对象请求后端脚本
var img = new Image(1, 1);
img.src = 'http://xxx.xxxxx.xxxxx/log.gif?' + args;
})(); |
整个脚本放在匿名函数里,确保不会污染全局环境。其中log.gif是后端脚本。 2.4. 后端脚本log.gif是后端脚本,是一个伪装成gif图片的脚本。后端脚本一般需要完成以下几件事情: 1、解析http请求参数得到信息。 2、从Web服务器中获取一些客户端无法获取的信息,如访客ip等。 3、将信息按格式写入log。 4、生成一副1×1的空gif图片作为响应内容并将响应头的Content-type设为image/gif。 5、在响应头中通过Set-cookie设置一些需要的cookie信息。 之所以要设置cookie是因为如果要跟踪唯一访客,通常做法是如果在请求时发现客户端没有指定的跟踪cookie,则根据规则生成一个全局唯一的cookie并种植给用户,否则Set-cookie中放置获取到的跟踪cookie以保持同一用户cookie不变。这种做法虽然不是完美的(例如用户清掉cookie或更换浏览器会被认为是两个用户),但是目前被广泛使用的手段。 我们使用nginx的access_log做日志收集,不过有个问题就是nginx配置本身的逻辑表达能力有限,所以选用OpenResty做这个事情。 OpenResty是一个基于Nginx扩展出的高性能应用开发平台,内部集成了诸多有用的模块,其中的核心是通过ngx_lua模块集成了Lua,从而在nginx配置文件中可以通过Lua来表述业务。 Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 首先,需要在nginx的配置文件中定义日志格式: log_format tick "$msec||$remote_addr||$status||$body_bytes_sent||$u_domain||$u_url||$u_title||$u_referrer||$u_sh||$u_sw||$u_cd||$u_lang||$http_user_agent||$u_account"; |
注意这里以u_开头的是我们待会会自己定义的变量,其它的是nginx内置变量。然后是核心的两个location: [AppleScript] 纯文本查看 复制代码 location / log.gif {
#伪装成gif文件
default_type image/gif;
#本身关闭access_log,通过subrequest记录log
access_log off;
access_by_lua "
-- 用户跟踪cookie名为__utrace
local uid = ngx.var.cookie___utrace
if not uid then
-- 如果没有则生成一个跟踪cookie,算法为md5(时间戳+IP+客户端信息)
uid = ngx.md5(ngx.now() .. ngx.var.remote_addr .. ngx.var.http_user_agent)
end
ngx.header['Set-Cookie'] = {'__utrace=' .. uid .. '; path=/'}
if ngx.var.arg_domain then
-- 通过subrequest子请求到/i-log记录日志,将参数和用户跟踪cookie带过去
ngx.location.capture('/i-log?' .. ngx.var.args .. '&utrace=' .. uid)
end
";
#此请求资源本地不缓存
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 /i-log {
#内部location,不允许外部直接访问
internal;
#设置变量,注意需要unescape,来自ngx_set_misc模块
set_unescape_uri $u_domain $arg_domain;
set_unescape_uri $u_url $arg_url;
set_unescape_uri $u_title $arg_title;
set_unescape_uri $u_referrer $arg_referrer;
set_unescape_uri $u_sh $arg_sh;
set_unescape_uri $u_sw $arg_sw;
set_unescape_uri $u_cd $arg_cd;
set_unescape_uri $u_lang $arg_lang;
set_unescape_uri $u_account $arg_account;
#打开日志
log_subrequest on;
#记录日志到ma.log 格式为tick
access_log /path/to/logs/directory/ma.log tick;
#输出空字符串
echo '';
} |
要完全掌握这段脚本的每一个细节还是比较吃力的,用到了诸多第三方ngxin模块(全都包含在OpenResty中了),重点都用注释标出来,可以不用完全理解每一行的意义,只要大约知道这个配置完成了我们提到的后端逻辑就可以了。
2.5. 日志格式日志格式主要考虑日志分隔符,一般会有以下几种选择: 固定数量的字符、制表符分隔符、空格分隔符、其他一个或多个字符、特定的开始和结束文本。 2.6. 日志切分日志收集系统访问日志时间一长文件变得很大,而且日志放在一个文件不便于管理。通常要按时间段将日志切分,例如每天或每小时切分一个日志。通过crontab定时调用一个shell脚本实现,如下: [AppleScript] 纯文本查看 复制代码 _prefix="/path/to/nginx"
time=`date +%Y%m%d%H`
mv ${_prefix}/logs/ma.log ${_prefix}/logs/ma/ma-${time}.log
kill -USR1 `cat ${_prefix}/logs/nginx.pid ` |
这个脚本将ma.log移动到指定文件夹并重命名为ma-{yyyymmddhh}.log,然后向nginx发送USR1信号令其重新打开日志文件。 USR1通常被用来告知应用程序重载配置文件, 向服务器发送一个USR1信号将导致以下步骤的发生:停止接受新的连接,等待当前连接停止,重新载入配置文件,重新打开日志文件,重启服务器,从而实现相对平滑的不关机的更改。 cat ${_prefix}/logs/nginx.pid 取 nginx 的进程号 然后再/etc/crontab里加入一行: 59 * * * * root /path/to/directory/rotatelog.sh 在每个小时的59分启动这个脚本进行日志轮转操作。
3. 系统环境部署服务器中安装依赖 yum -y install gcc perl pcre-devel openssl openssl-devel 上传LuaJIT-2.0.4.tar.gz并安装LuaJIT tar -zxvf LuaJIT-2.0.4.tar.gz -C /usr/local/src/ cd /usr/local/src/LuaJIT-2.0.4/ make && make install PREFIX=/usr/local/luajit 设置LuaJIT环境变量 vi /etc/profile export LUAJIT_LIB=/usr/local/luajit/lib export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0 source /etc/profile 创建modules文件夹,保存nginx依赖的模块 mkdir -p /usr/local/nginx/modules 上传nginx依赖的模块 set-misc-nginx-module-0.29.tar.gz lua-nginx-module-0.10.0.tar.gz ngx_devel_kit-0.2.19.tar.gz echo-nginx-module-0.58.tar.gz 将依赖的模块直接解压到modules目录 tar -zxvf lua-nginx-module-0.10.0.tar.gz -C /usr/local/nginx/modules/ tar -zxvf set-misc-nginx-module-0.29.tar.gz -C /usr/local/nginx/modules/ tar -zxvf ngx_devel_kit-0.2.19.tar.gz -C /usr/local/nginx/modules/ tar -zxvf echo-nginx-module-0.58.tar.gz -C /usr/local/nginx/modules/ 安装openresty tar -zxvf openresty-1.9.7.3.tar.gz -C /usr/local/src/ cd /usr/local/src/openresty-1.9.7.3/ ./configure --prefix=/usr/local/openresty --with-luajit && make && make install 安装nginx tar -zxvf nginx-1.8.1.tar.gz -C /usr/local/src/ 编译nginx并支持其他模块 cd /usr/local/src/nginx-1.8.1/ ./configure --prefix=/usr/local/nginx \ --with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \ --add-module=/usr/local/nginx/modules/ngx_devel_kit-0.2.19 \ --add-module=/usr/local/nginx/modules/lua-nginx-module-0.10.0 \ --add-module=/usr/local/nginx/modules/set-misc-nginx-module-0.29 \ --add-module=/usr/local/nginx/modules/echo-nginx-module-0.58 make -j2 make install 备注:如果对linux相关操作不熟,请严格按照上述步骤搭建环境,切记心细,心细,再心细。
4. 自定义采集数据实现4.1. 方案一:基本功能实现a) 创建页面index.html,添加埋点代码,放入nginx默认目录nginx/html下。 b) 在默认目录nginx/html下添加一个数据采集脚本ma.js。 c) 修改nginx的配置文件,添加自定义相关业务逻辑。 d) 启动nginx sbin/nginx -c conf/nginx.conf e) 通过游览器访问nginx f) 观察自定义日志采集文件是否有对应的内容输出 tail -f logs/user_defined.log 此时还可以观察nginx默认的输出日志文件 tail -f logs/access.log 停止nginx: sbin/nginx –s stop 4.2. 方案二:页面点击事件详细步骤请参考附件资料。 传智播客·黑马程序员郑州校区地址 河南省郑州市 高新区长椿路11号大学科技园(西区)东门8号楼三层 |