Perlrun 学习笔记[实战]

预备知识:我们有一个HTTP Server响应请求。为了方便tracking,我们保存所有的query在acess log文件中。现在想一下需要log多少信息。

  1. Query String 这样出了问题我们才能replay这些query用于重现。[Query=]
  2. IP 这样我们可以知道是谁发送了query,如何是恶意发送大量expensive query企图拖垮我们的系统,我们可以禁掉该IP。[IP=]
  3. Process Time 每一条query的执行时间,方便我们定位哪些query比较昂贵。[TimeCost=]
  4. Response Size 记录每一条query数据的多少。有时候会发现网络流量很高,会怀疑是不是有大量的数据交换。[RespSize=]
  5. QueryArriveTime 记录query到达时间,比如发现10点到11点之间HTTPD CPU很高,可以抽取该时间段的log。[ArrTime=]
  6. Query Type 这是根据业务划分的。有了这个信息可以帮助我们聚类。[QueryType]
  7. … …

我们可以根据需求添加更多的需要log的信息,只要能够帮助我们分析。好了,有了以上的介绍我们基本上知道 access.log文件用于记录到达的query,文件中的每一行对应一条query,每一行样子都是这个样子的:

[ArrTime=Fri Sep 26 00:00:30 2008][IP=10.254.11.123][Query=...][QueryType=Batch][TimeCost=8.22ms][RespSize=8][...]
表示:在0点从ip=10.254.11.123处发来一条query,该query是取信息给batch应用使用的。耗时8.22毫秒,返回页面大小为8K。

好!现在的问题就是当问题出现的时候,如何在浩瀚的access log文件中找到线索。perl派用场的时候到了。

查看Sep 26号的11点到12点之间的请求类型的分布。
grep ‘[ArrTime=Fri Sep 26 11' access.log | perl -ne '/[QueryType=([^]]+)/; print $1,"n" ‘ |sort| uniq -c
先使用grep找出11点到12点之间的log,([^]]+)匹配所有不是’]'的字符,这样就取道了QueryType的内容。然后交给sort排序后统计输出。

提取QueryType=Batch的请求,改为QueryType=Test后输出,用于生成实验log。
cat access.log | perl -ne ‘ /(.+)[QueryType=([^]]+)(.+)/; print $1."[QueryType=Test".$3."n" ' > Test.log

找出所有Response Size > 1M(1024K) 的query。
cat access.log | perl -ne ' /[RespSize=([d.]+)/; print if $1>1024 ‘  

找出最近1000条query中昂贵的Query,统计他们的QueryType分布。
tail -10000 access_log | perl -ne ‘/[QueryType=([^]]+).+[TimeCost=([d.]+)ms/; $QT{$1} +=$2; END{for $key (keys(%QT)) {print $QT{$key},"n" } }’ | sort -nr | less
$1中存放QueryType的内容,.+匹配在[QueryType=]和[RespSize=]之间的内容但是并不需要保存,$2中保存TimeCost的内容;我们用一个Hash表%QT来保存QueryType和它所对应的Total TimeCost,计算的方法是对于每一个query,都进行
$QT{QueryType}+= TimeCost
END 语句块表示在perl结束之前执行的语句,所以能执行到END,perl -n的循环已经结束了,也就是所有的匹配记录都已经完成了。

哈哈,有点昏了。不要紧,perl的一大特点就是不好读,它本来就不是给C程序员读的。如果我们使用在线帮助:
perldoc perlcompile | less

我们会看到一个选项, -MO=Deparse ,使用这个选项可以把perlrun的看似诡异的代码打印成可读性比较好的形式:

-bash-3.00$ perl -MO=Deparse -ne ‘/[QueryType=([^]]+).+[TimeCost=([d.]+)ms/; $QT{$1} +=$2; END{for $key (keys(%QT)) {print $QT{$key},"n" } }’

LINE: while (defined($_ = <ARGV>)) {
    /[QueryType=([^]]+).+[TimeCost=([d.]+)ms/;
    $QT{$1} += $2;
    sub END {
        foreach $key (keys %QT) {
            print $QT{$key}, "n";
        }
    }
    ;
}
-e syntax OK

这样就好理解多了。

其实按照Query Type进行统计的原因是 如果突然发现某段时间内CPU很高,我们首先怀疑就是会不会有人在这段时间内向http server发送了expensive query。所以我们希望统计这段时间内各种query的时间消耗。其实我们应该统计平均值才对:
perl -ne ‘/[QueryType=([^]]+).+[TimeCost=([d.]+)ms/; $QT{$1} +=$2; $CNT{$1}++; END{for $key (keys(%QT)) {print $key, ": ", $QT{$key}/$CNT{$key},"n" } }’ | sort

同时检查100台http server对同一个query的响应是否相同。
如果要你手工发送同样的query到100台不同的http server要死人的。而且每次只是修改一点点内容,还要用眼睛看结果是不是一样。假设在http response中我们事先约好Match: X 表示匹配X个记录。我们就检查这100台server是不是都匹配一样数目的记录。server的命名规则是 arrowpig1.vip.com ~ arrowpig100.vip.com
perl -e ‘print "$_:" and system qq{wget -qO- http://arrowpig$_.vip.com/Query=QyString | grep  "^Match" } for 1..100 ‘

-bash-3.00$ perl -MO=Deparse -e ‘print "$_:" and system qq{wget -qO- http://arrowpig$_.vip.com/Query=QyString | grep  "^Match" } for 1..100 ‘
;
foreach $_ (1 .. 100) {
    system qq[wget -qO- http://arrowpig$_.vip.com/Query=QyString | grep  "^Match" ] if print "${_}:";
}

这里注意一定使用and连接 print 和 system调用。如果使用;结果就不一样了。

-bash-3.00$ perl -MO=Deparse -e ‘print "$_:" ; system qq{wget -qO- http://arrowpig$_.vip.com/Query=QyString | grep  "^Match" } for 1..100 ‘
print "${_}:";
;

foreach $_ (1 .. 100) {
    system qq[wget -qO-

}
-e syntax OK
注意到区别了吗,print语句被移到foreach循环外面去了。

但是还是有3个问题:

  1. 如果server命名规则是arrowpig001.vip.com~arrowpig100.vip.com就不对。我们需要打印001,002而不是1,2…
  2. system系统调用会fork很多子进程,如果使用Ctrl-C无法一下子终止整个操作。
  3. 我们没有为wget操作设置超时,如果某个http server down了,整个脚本就hang在那里。

对了,上面的脚本我并没有每一个都run过,所以很有可能有语法错或者运行结果和预期的不一样-:)

 

你可能感兴趣的:(server,perl,System,query,Access,qt)