版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011170921/article/details/52165129
问题描述:
声明:仅供分享探讨,不喜勿喷,希望大神们多多指教!
我们的日志系统为分布式日志系统,记录IOS或Android的APP请求后台API的情况,有4台服务器进行日志记录工作。现在的工作就是每天定时得获取分布在服务器的日志然后处理,主要就是做统计工作,这里有两个指标。
PV:页面访问量,这里指的是一个API被访问的次数;
UV:用户访问量,这里指的是一个API被多少个用户不重复访问。
这里列出一条记录案例:
10.130.92.149 - - [07/Aug/2016:03:00:14 +0800] "POST /api/superBike/gpsTrack/uploadGps?access_token=14069e1dc20LWykljPD3r8sbBNj8jFcgIvnXOyjVBE2IT4HY9pem15KUenN447DcKENNKIqpQ&letvId=172434715 HTTP/1.1" 200 49 "-" "lesports"
关键步骤:
这里我将任务划分成3个阶段来实现:1)如何实现自动定时,自动部署触发;2)如何实现PV和UV的统计;3)结果的存储。
1)Jenkins构建定时任务
首先附上Jenkins的入门教程:
Jenkins使用教程
Jenkins定是构建项目
服务器一般都是linux系统,所以可以使用linux下的crontab命令来部署定时任务,到设定时间自动跑脚本等(实际上我也没有用过,以后可以试试)。那我是如何实现定时任务的?答案就是——Jenkins(功能很强大,不过我只懂得皮毛,值得深度学习)。我们公司采用Jenkins工具来构建所有的项目,Jenkins整合了Git、Svn、Maven等主流工具,十分方便。具体使用过程:
1、新建Jenkins项目;
2、进入到设置勾选“Build periodically”;
3、编写“Schedule”,“Schedule”有5项,分别代表:分、时、日、月、年,用空格隔开,如下如“50 02 * * *” 表示每天的凌晨02时50分要自动构建这个项目,其中“*”表示任何时间定。
至此,一个自动化的定时任务就不熟成功了,之后就可以开展真正的工作了。
2)shell脚本处理文本,统计日志
有人会问,我建好Jenkins项目后,设置完定时后该怎么用,上一小节中的教程链接应该多少介绍一些,那我就说说我这里的用处,仅仅是跑脚本而已(应该是大材小用了)。
这就是我这个项目的全部内容了跑了两段脚本分别是Shell脚本和Nodejs脚本。当然Jenkins还支持很多脚本语言的,这里给个截图不赘述。
值得注意是,这里添加脚本的顺序就是脚本的执行顺序。好了该切入主题了,先上代码!
<pre name="code" class="javascript"><pre name="code" class="javascript">set -x
# rm * -rf
ip_list=(<strong>远程服务器的IP地址,用空格隔开</strong>)
for ip in ${ip_list[@]}
do
scp ${ip}:/home/nginx/nginx/logs/access.log <strong>Jenkins的workspace目录</strong>/${ip}.log
done
log_list=(<strong>拷贝过来的日志文件,文件名为ip地址</strong>)
# 获取当前时间的前一天,切割日志时间是凌晨3点
# 所以处理的日志范围为前一天的02:50:00——今天的02:50:00
today=`date +"%d/%b/%Y" -d "-1days"`
# today="08/Aug/2016"
dateTime=`date +"%Y-%m-%d %H:%M:%S" -d "-1day"`
# 转化成unix时间戳,单位为秒不是毫秒
timestamp=`date -d "${dateTime}" +%s`
# 获取昨天和今天的访问日志
# 即昨天的00:00:00——今天的02:50:00
today=${today//\//\\/}
for file in ${log_list[@]}
do
sed -n '/'${today}'/,$p' ${file} >> access.log
done
# 提取ip和api
# transform:返回用户访问的时间戳
# 然后与昨天的当前时间比较,保留昨天02:50:00后的日志
awk '
function transform(date){
day=substr(date,1,2);
month=substr(date,4,3);
if(month=="Jan"){
month=01
}
if(month=="Feb"){
month=02
}
if(month=="Mar"){
month=03
}
if(month=="Apr"){
month=04
}
if(month=="May"){
month=05
}
if(month=="Jun"){
month=06
}
if(month=="Jul"){
month=07
}
if(month=="Aug"){
month=08
}
if(month=="Sep"){
month=09
}
if(month=="Oct"){
month=10
}
if(month=="Nov"){
month=11
}
if(month=="Dec"){
month=12
}
year=substr(date,8,4);
hour=substr(date,13,2);
min=substr(date,16,2);
second=substr(date,19,2);
time=year" "month" "day" "hour" "min" "second;
return mktime(time)
}
{
dateTime=substr($4,2);
split($7,a,"?");
api=a[1];
time=transform(dateTime)
}
{
if (time > '$timestamp')
print $1,api,time
}
' access.log > temp
awk --posix '
$2 ~ /\/[0-9]{15}\//{
gsub(/\/[0-9]{15}\//,"/{id}/",$2)
}
$2 ~ /\/[0-9]{15}$/{
gsub(/\/[0-9]{15}$/,"/{id}",$2)
}
$2 ~ /\/[a-zA-Z0-9]{24}\//{
gsub(/\/[a-zA-Z0-9]{24}\//,"/{id}/",$2)
}
$2 ~ /\/[a-zA-Z0-9]{24}$/{
gsub(/\/[a-zA-Z0-9]{24}$/,"/{id}",$2)
}
$2 ~ /\/[a-zA-Z0-9]{10}\//{
if($2 !~ /\/[a-zA-Z]{10}\//)
gsub(/\/[a-zA-Z0-9]{10}\//,"/{id}/",$2)
}
$2 ~ /\/[a-zA-Z0-9]{10}$/{
if($2 !~ /\/[a-zA-Z]{10}$/)
gsub(/\/[a-zA-Z0-9]{10}$/,"/{id}",$2)
}
{
print $1,$2,$3
}' temp > ip_api
# 计算pu
cat ip_api | sort -t " " -k2 | awk 'a[$2]!=$1{a[$2]=$1;b[$2]++}END{for (i in b)print i" "b[i]}' | sort -t " " -k1 > uv_
# 计算uv
cut -d" " -f2 ip_api | sort | uniq -c | awk '{print $2,$1}' > pv_
# 合并pv和uv,切过滤掉以js,css,png,jpg,gif,cgi,ico,gch结尾,http开头,包含"\"的API
paste -d " " pv_ uv_ | awk '$0 ~ /^\/api/ && $1 !~ /css$|js$|png$|jpg$|igf$|cgi$|ico$|gch|^http|.*\\.*|400/{print $1,$2,$4}' | sort -t " " -k2 -n -r > result.txt
# # 删除中转文件
rm temp ip_api uv_ pv_ -rf
是不是感觉很乱,但我只能说Shell太强大了,以上的代码可以总结以下几个linux关键工具,相关提供链接:
1、sed
Linux之sed用法
2、linux的时间处理
shell date 获取昨天日期及几种特殊格式
3、文本处理神器awk
awk 正则表达式、正则运算符详细介绍
linux awk 内置函数详细介绍(实例)
4、合并文件paste
Linux paste命令使用PS:awk的版本分很多种,不同的版本支持的正则表达式可能会不一样,gawk就不支持/[0-9]{15}/的匹配,这时需要在awk 命令后 加上--posix,这貌似是某个标准,不过还是不太了解,以后有机会在研究一下。
这里再推荐一个linux命令行学习网站
菜鸟教程
3)Nodejs实现连接数据库,保存结果
首先还是上代码;var fs = require('fs');
var MongoClient = require('mongodb').MongoClient;
//创建ApiLog数组
var apiLogs = new Array();
//获取当前时间戳
var timestamp = new Date().getTime() - 18000000;
//日志文件名
var filename = 'result.txt';
var data = fs.readFileSync(filename).toString();
var lines = data.split('\n');
for (var i = 0; i < lines.length; i++){
var line = lines[i];
if(line != null && line != ''){
var api = lines[i].split(' ');
console.log(api);
//封装成Json对象
var url = api[0];
var pv = api[1];
var uv = api[2];
apiLogs.push({"url":url, "pv": pv, "uv":uv, "visitTime":timestamp});
}
}
var DB_CONN_STR = 'mongodb://用户名:密码@IP地址:端口号/数据库名';
var selectData = function(db, apiLogs, callback) {
//连接到表
var collection = db.collection("apiLog");
collection.insert(apiLogs, function(err, result) {
if(err){
console.log('Error:'+ err);
return;
}
callback(result);
});
}
MongoClient.connect(DB_CONN_STR, apiLogs, function(err, db) {
console.log("连接成功!");
selectData(db, apiLogs, function(result) {
// console.log(result.length);
db.close();
});
});
上面的代码相对简单一下,大致的过程就是:1)读取shell脚本处理后的结果文件(result.txt),因为处理过后的文件不大所以一次性读取,之后判断换行遍历每行内容;2)切割每行内容分成3个字段:url、PV、UV,组合成json对象,然后在放到一个数组变量中;3)连接mongo数据库,把数组变量批量插入数据库中。这个过程需要注意以下几点:
1)nodejs是异步的,所以效率高,但是程序很多时候需要同步处理所以有时需要异步转化成同步,这个网上也有很多教程,这里不赘述。
我想说的是在读取文件的时候一定要同步读取,如果不是同步读取的话,那你最后得到的数组变量会为空,等同于你没有处理文本(实际上处理了,但是在连接数据库时还没有处理完)。
2)在nodejs代码的最上面可以看到require了两个模块:fs和mongodb,fs是文件系统模块,nodejs本身自带的。而mongodb模块是第三方模块,需要安装,安装命令如下:
npm install mongodb -g//npm是nodejs的一个模块管理工具,可以直接从网上下载模块,-g 是可选项表示是否全局,即在当前计算机环境中的任何位置都可以引用。有人会问我在本地写代码然后拷贝到Jenkins上,但是我没有权限进入服务器怎么安装办?答案就是先保存Jenkins上shell脚本(处理日志的代码),然后清空换上 npm install mongodb -g,然后构建一下项目就可以安装,之后再把shell改回去就行了。
PS:附上Nodejs的入门教程。
Node.js 教程