为何选择Stackless? http://www.stackless.com
Stackless可以简单的认为是Python一个增强版,最吸引眼球的非“微线程”莫属。微线程是轻量级的线程,与线程相比切换消耗的资源更小,线程内共享数据更加便捷。相比多线程代码更加简洁和可读。此项目是由EVE Online推出,在并发和性能上确实很强劲。安装和Python一样,可以考虑替换原系统Python。:)
为何选择MongoDB? http://www.mongodb.org
可以在官网看到很多流行的应用采用MongoDB,比如sourceforge,github等。相比RDBMS有啥优势?首先在速度和性能上优势最为明显,不仅可以当作类似KeyValue数据库来使,还包含了一些数据库查询(Distinct、Group、随机、索引等特性)。再有一点特性就是:简单。不论是应用还是文档,还是第三方API,几乎略过一下就可以使用。不过有点遗憾的就是,存储的数据文件很大,超过正常数据的2-4倍之间。本文测试的Apache日志大小是2G,生产的数据文件有6G。寒…希望在新版里能有所缩身,当然这个也是明显的以空间换速度的后果。
本文除去上面提及到的两个软件,还需要安装pymongo模块。http://api.mongodb.org/python/
模块安装方式有源码编译和easy_install,这里就不再累赘。
1. 从Apache日志中分析出需要保存的资料,比如IP,时间,GET/POST,返回状态码等。
fmt_str = '(?P<ip>[.\d]+) - - \[(?P<time>.*?)\] "(?P<method>.*?) (?P<uri>.*?) HTTP/1.\d" (?P<status>\d+) (?P<length>.*?) "(?P<referere>.*?)" "(?P<agent>.*?)"' fmt_name = re.findall('\?P<(.*?)>', fmt_str) fmt_re = re.compile(fmt_str)
定义了一个正则用于提取每行日志的内容。fmt_name就是提取尖括号中间的变量名。
2. 定义MongoDB相关变量,包括需要存到collection名称。Connection采取的是默认Host和端口。
conn = Connection() apache = conn.apache logs = apache.logs
3. 保存日志行
def make_line(line): m = fmt_re.search(line) if m: logs.insert(dict(zip(fmt_name, m.groups())))
4. 读取Apache日志文件
def make_log(log_path): with open(log_path) as fp: for line in fp: make_line(line.strip())
5. 运行把。
if __name__ == '__main__': make_log('d:/apachelog.txt')
脚本大致情况如此,这里没有放上stackless部分代码,可以参考下面代码:
import stackless def print_x(x): print x stackless.tasklet(print_x)('one') stackless.tasklet(print_x)('two') stackless.run()
tasklet操作只是把类似操作放入队列中,run才是真正的运行。这里主要用于替换原有多线程threading并行分析多个日志的行为。
补充:
Apache日志大小是2G,671万行左右。生成的数据库有6G。
硬件:Intel(R) Core(TM)2 Duo CPU E7500 @ 2.93GHz 台式机
系统:RHEL 5.2 文件系统ext3
其他:Stackless 2.6.4 MongoDB 1.2
在保存300万左右时候,一切正常。不管是CPU还是内存,以及插入速度都很不错,大概有8-9000条/秒。和以前笔记本上测试结果基本一致。再往以后,内存消耗有点飙升,插入速度也降低。500万左右记录时候CPU达到40%,内存消耗2.1G。在生成第二个2G数据文件时候似乎速度和效率又提升上去了。最终保存的结果不是太满意。
后加用笔记本重新测试了一下1000万数据,速度比上面的671万明显提升很多。初步怀疑有两个地方可能会影响性能和速度:
1. 文件系统的差异。笔记本是Ubuntu 9.10,ext4系统。搜了下ext3和ext4在大文件读写上会有所差距。
2. 正则匹配上。单行操作都是匹配提取。大文件上应该还有优化的空间。
前言:
扩展Apache模块开发网上大部分教程都是围绕Perl语言,老外的《Writing Apache Modules with Perl and C》可以算是经典之作了,可惜一直都是针对老版本开发,而且主力语言是Perl,C语言部分只是略有介绍。不过相比较而言用Perl来扩展模块功能确实比 C语言来的快速以及便捷多了,也简单容易。我自己也在工作里应用了一部分,主要是在防盗链上面写了两个简单都模块,可以参考我写的另外两篇文章:apache+mod_perl防盗链以及apache+mod_perl实现url rewrite。说了那么多题外话,回到正题,这里只是用C语言实现一个简单的hello模块,模块功能是查询MySQL自带mysql数据库里都user表。
系统环境:
ArchLinux Apache2.2 MySQL 5.0
具体开发步骤:
1. 利用Apache自带都apxs建立hello模块:
[root#localhost] apxs -g -n hello
这样就会在当前目录下新建一个hello模块的文件目录,可以看到里面有:Makefile mod_hello.c modules.mk这样的文件,具体apxs路径查询下本机apache/bin目录。
2. 预览下mod_hello.c,可以看到里面apxs自动帮你生成一堆代码了,我们需要的只是修改里面的代码部分,先简单都介绍下里面的函数说明。
include 部分就是引入了一些必要都头文件
hello_handler 这个就是hello模块都主体部分,所有的显示、处理请求什么的都在这里。
hello_register_hooks hello_module 这俩个是需要导出的函数所必须的,先可以不管他们,按照生成的不动即可。
3. 修改hello_handler函数,里面可以看到request_rec *r,r有很多函数和变量,具体要参见文档了。里面的ap_rputs是输出,可以简单的理解为把字符串输出到r。
static int hello_handler(request_rec *r) { if (strcmp(r->handler, "hello")) { // 判断apache配置文件里handler是否等于hello,不是就跳过 return DECLINED; } r->content_type = "text/html"; // 设置content-type if (!r->header_only) ap_rputs("The sample page from mod_hello.c\n", r); // 输出一段文字 return OK;// 返回 200 OK状态 }
增加#include “mysq.h”,查询需要用到这个头文件。
具体代码参见本文结尾部分。
4. 编译模块
[root#localhost] apxs -c -a -i -I/usr/include/mysql/ -lmysqlclient mod_hello.c
可以看到一堆编译指令,加上-I和-l是编译mysql必须的,编译完会自动在httpd.conf加上 LoadModule hello_module modules/mod_hello.so
5. 修改httpd.conf
<Location /hello>
SetHandler hello
</Location
6. 重启apache,访问http://localhost/hello,看是否成功。
=====================
完整代码:
#include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" /* 头文件,本文用到了ap_rprintf函数 */ #include "apr.h" #include "apr_lib.h" #include "apr_strings.h" #include "apr_want.h" #include "mysql.h" /* 定义mysql数据变量 */ const char *host = "localhost"; const char *user = "root"; const char *pass = "smallfish"; const char *db = "mysql"; /* The sample content handler */ static int hello_handler(request_rec *r) { if (strcmp(r->handler, "hello")) { return DECLINED; } r->content_type = "text/html"; /* 定义mysql变量 */ MYSQL mysql; MYSQL_RES *rs; MYSQL_ROW row; mysql_init(&mysql); /* 初始化 */ if (!mysql_real_connect(&mysql, host, user, pass, db, 0, NULL, 0)) {/* 连接数据库 */ ap_rprintf(r, "<li>Error:%d %s</li>\n", mysql_errno(&mysql), mysql_error(&mysql)); return OK; } char *sql = "select host,user from user order by rand()"; if (mysql_query(&mysql, sql)!=0) { /* 查询 */ ap_rprintf(r, "<li>Error : %d %s</li>\n", mysql_errno(&mysql), mysql_error(&mysql)); return OK; } rs = mysql_store_result(&mysql); /* 获取查询结果 */ while ((row = mysql_fetch_row(rs))) { /* 获取每一行记录 */ ap_rprintf(r, "<li>%s - %s</li>\n", row[0], row[1]); } mysql_free_result(rs); /* 释放结果集 */ mysql_close(&mysql); /* 关闭连接 */ return OK; } static void hello_register_hooks(apr_pool_t *p) { ap_hook_handler(hello_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA hello_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ hello_register_hooks /* register hooks */ };
环境:Linux Apache Python(mod_python)
换了一台新机器,没有配置Mod_Python了,在一些应用里import MySQLdb出现了下面错误:
ExtractionError: Can't extract file(s) to egg cache The following error occurred while trying to extract file(s) to the Python egg cache: [Errno 13] Permission denied: '/root/.python-eggs' The Python egg cache directory is currently set to: /root/.python-eggs Perhaps your account does not have write access to this directory? You can change the cache directory by setting the PYTHON_EGG_CACHE environment variable to point to an accessible directory.
解决办法有两种:
1.设置PYTHON_EGG_CACHE环境变量
$ SetEnv PYTHON_EGG_CACHE /tmp/aaa/
目录权限注意要是apache用户,或者简单点就777
2.把egg格式转成目录
$ cd /python-path/site-packages/ $ mv MySQL_python-1.2.3c1-py2.5-linux-x86_64.egg foo.zip $ mkdir MySQL_python-1.2.3c1-py2.5-linux-x86_64.egg $ cd MySQL_python-1.2.3c1-py2.5-linux-x86_64.egg $ unzip ../foo.zip $ rm ../foo.zip
环境:Linux Apache2.2 (路径 /usr/local/apache)
步骤:
1. 修改 conf/httpd.conf,找到如下位置,去除 # 注释符
# Virtual hosts Include conf/extra/httpd-vhosts.conf
2.修改 conf/extra/httpd-vhosts.conf
<VirtualHost *:80> ServerAdmin [email protected] DocumentRoot "/usr/aa" ServerName ww.aa.com ServerAlias ww.aa.com ErrorLog "logs/ww.aa.com-error_log" CustomLog "logs/ww.aa.com-access_log" common </VirtualHost>
注意CustomLog这行,默认给的配置是:”logs/dummy-host.example.com-access_log common”
这个其实是错误的,Apache启动时候会报错,common这个应该放在双引号外面:
Syntax error on line 32 of /usr/local/apache/conf/extra/httpd-vhosts.conf: CustomLog takes two or three arguments, a file name, a custom log format string or format name, and an optional "env=" clause (see docs)
另外还有个问题,在Apache+Tomcat时候出现的,在配置好mod_jk之后,通过默认80端口访问jsp总会提示403禁止访问,纳闷了!后来 才发现是Directory配置问题,因为每个VirtualHost配置的目录不是默认的DocumentRoot目录,在Apache2以后对于权限 和安全有了更高的要求,所以必须手动配置下每个Directory属性。
<Directory "/usr/aa"> Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all </Directory>
OK,到这里虚拟主机部分已经配置完成!
补充一点,加上日志按天保存,避免一个巨大无比的文件!
CustomLog "|/usr/local/apache/bin/rotatelogs /usr/local/apache/logs/ww.aa.com-access-%y%m%d.log 86400 480 " combined
相信apache的mod_rewrite模块都很熟悉了,今天这儿换个思路,利用mod_perl来实现下,发现竟然是如此的简单!
首先得保证apache已经安装了mod_perl模块,具体安装配置可以看上一篇文章哦。
修改下http.conf配置,添加一下内容:
PerlTransHandler MyTrans # MyTrans 这个是自己添加的处理模块名
具体MyTrans.pm脚本如下:
package MyTrans; use strict; use Apache2::Const qw(DECLINED); sub handler { my $r = shift; my $uri = $r->uri; my ($id) = ($url =~ m|^/news/(.*)\.html|); $r->uri("/news.php"); $r->args("id=$id"); return Apache2::Const::DECLINED; } 1;
实现就是:/news/12345.html => /news.php?id=12345
大体思路是这样的,比如有 一个地址:http://www.aa.com/down/1.mp3,不幸搜索引擎或者迅雷扒到了,就无偿为他们奉献流量了。 但是假如在http://www.aa.com/down/1.mp3?key=123,key参数每天变化或者几分钟变化一次,在apache服务端校 验下这个参数,不正确则显示拒绝访问或者找不到的话,那样防盗链的效果就算达到了把。
modperl强大到可以任意应用apache内部API,官方地址是:http://perl.apache.org 。根据apache版本选择相应的modperl版本,现在大部分都apache2了,就选择modperl2把。具体安装配置可以看官方文档。
先建立/home/httpd/modperl/startup.pl(目录请自行修改),内容如下:
use strict; use lib qw(/home/httpd/modperl); # 把这个路径加入到perl lib路径 use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::Connection (); use Apache2::RequestUtil (); use Apache2::ServerUtil (); use Apache2::Log (); use Apache2::Request (); 1;
部分本机httpd.conf配置:
LoadModule perl_module modules/mod_perl.so LoadModule apreq_module modules/mod_apreq2.so PerlPostConfigRequire /home/httpd/modperl/startup.pl <Location /down > SetHandler modperl # 设置该目录交给modper处理 PerlAccessHandler Down # Down是模块名称 PerlSetVar key 123 # 设置校验参数值 </Location>
mod_apreq2.so这个模块需要安装Apache2::Request,具体安装:http://pyh7.spaces.live.com/blog/cns!47D8D44208AC51E5!128.entry
startup.pl文件一般modperl应用都有,加载一些常用库,可以在apache启动时预先载入,避免重复加载。
修改这些后可以重启下apache,看下logs/error_log里最后是否有mod_apreq和mod_perl字样,如果有就说明成功了。剩下都就是写校验的perl脚本了,/home/httpd/modperl/Down.pm,内容如下:
package Down; use strict; use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::Connection (); use Apache2::RequestUtil (); use Apache2::ServerUtil (); use Apache2::Log (); use Apache2::Const -compile => qw(OK FORBIDDEN); use Apache2::Request (); sub handler { my $r = shift; my $req = Apache2::Request->new($r); my $ip = $r->connection->remote_ip; my $k = $req->param('key') || ''; # 判断访问时是否带key参数 my $key = $r->dir_config('key') || '123'; # 加载httpd.conf配置中的key值 if ($key eq $k) { # 相等可以正常访问 return Apache2::Const::OK; } else { # 否则显示拒绝访问 my $s = Apache2::ServerUtil->server; $s->log_error("[$ip--------forbidden.]"); return Apache2::Const::FORBIDDEN; } } 1;
提示一下,以上两个perl脚本文件末尾记得加上1;
重启下apache。现在可以通过http://www.aa.com/down/1.mp3和http://www.aa.com/down /1.mp3?key=123测试下。可以看见不加参数拒绝访问了把。这里只是简单的判断,实际上可以根据日期或者IP加密生成一个字符串来判断。
写这个帖子完全是无意中搜索modperl应用时候发现了,具体可以参见:
http://pyh7.spaces.live.com/blog/cns!47D8D44208AC51E5!140.entry
上面的文档已经写都很详细了,包括怎么安装modperl、Apache2::Request等模块以及配置apache的http.conf就不在累赘都重复了。
功能简述
统计出日志里一个或多个页面总共访问的次数,比如aa.jsp, bb.jsp这样页面分别多少次。
实现简述
Apache单个日志文件800M。
最初程序使用Python编写,按行来统计,分别使用in(最慢)和index方法去查找,然后使用了正则匹配,程序运行时间从最初的1分50多秒优化到1分10秒左右,参考了qyb博客中提到的gc.disable(),有了一定的提升,最终还是需要1分左右。
然后随意用了Perl写了一个,用了最土鳖的<LOG>这样的按行分析,最后正则匹配,然后++,速度竟然在40-50秒之间,惊叹!后来经过shucho指点,在正则部分采用了预编译,效果那是相当惊人!800M文件只用了7秒左右。卧槽!
程序片段
# -------------------------------------------------------------------- use strict; use Benchmark; my $LOG_FILE = '/usr/local/apache/logs/access.log'; # 下面qr部分起了关键作用,预编译了表达式 my @EXT_LIST = map {qr/$_/} qw{ aaServlet bbServlet }; my $startime = new Benchmark; my %result; map {$result{$_} = 0} @EXT_LIST; open LOG_FILE, $LOG_FILE; while (<LOG_FILE>){ foreach my $ext (@EXT_LIST){ $result{$ext}++ if $_ =~ /$ext/; } } close LOG_FILE; while (my ($key, $value) = each(%result)){ $key =~ s/\(\?-xism:(.*?)\)/$1/g; print "$key:\t$value\n"; } printf "** %s\n\n", timestr(timediff(new Benchmark, $startime));