易懂,易用,易扩展的PHP5开发框架 注册 | 登陆
浏览模式: 标准 | 列表全部文章

用linux命令提高php的处理能力

最近,需要对用户访问日志作一些统计分析处理,该日志每天生成,大小平均是1.5G,经其它程序进行预处理后,去掉一些无关信息,减小为300M左右,格式是userId`url`clicks,1000万行左右.

要做的工作是按userId,urlId分组保存到数据库,类似这样:

uid    urlId    clicks
------------------------
12    1    3
12    2    4
13    1    3
13    2    5
13    11    3

当然,还有其它关联的表,如url表,需要先通过url在url表查找出urlId,如果没有则插入...满足各种需求后,每条记录操作数据库的平均次数是查询4-6次(包括oracle的获得自增id查询),插入/更新3次

假设数据库的操作已经完全优化,要完成这项目任务,使用php的常规办法是打开日志文件句柄,然后遍历处理,这样,1000万条记录一下来,不少于6个小时(硬件配置为8G内存+Xeon1.60GHz 4核 x 2 ,下同)

这样的处理速度明显是不能满足要求的,必须要进行优化,但php本身的优化手段有效,于是,php的好朋友linux上场了:

1)先对日志文件进行排序处理,排序后,可方便按uid进行合并,大大减少了数据库的操作次数

PHP代码
  1. //对$cTempFile进行排序,生成排序后的$cSwapFile  
  2. $command = "sort {$cTempFile} > {$cSwapFile}";  
  3. system($command,&$returnVar); 


2)对排序后的文件进行切分,启用php模拟进程方式并行处理

PHP代码 
  1. $threads = 15; //启动的进程数  
  2. //查询待处理文件的总行数
  3. $command = "cat {$cSwapFile} |wc -l";  
  4. $nums = system($command,&$returnVar);  
  5. $lines = ceil($nums / $threads); //每个文件的行数,用总行数除文件数后取整  
  6. chdir(LOG_PATH);//进入存放切分后文件的路径 
  7. //切分文件,具体参数可查split帮助
  8. $command = "split -d -l {$lines}  {$cSwapFile} temp_custom"//切成 $lines个文件,文件前缀是tem_custom,以数字后缀结束,如temp_custom00,temp_custom12  
  9. system($command,&$returnVar);  
  10. //切分后,启动进程处理 
  11. $eventLog = LOG_PATH.'eventLog'; //事件日志
  12. for($i = 0;$i$threads;$i++){  
  13.         $command = sprintf("php %s -f %d >> %s &",__FILE__,$i,$eventLog); //必须将输出重定到日志文件,并加入&在后台执行  
  14.         system($command,&$returnVar);  
  15. }  



3)进程处理

主进程通过 php __FILE__ -f id 的方式调用子进程
因为是同一个文件,需要判断一下$argv参数,并根椐id生成当前进程要处理的日志文件,开始正常处理入库


经过这样处理后,进程数开到15,同样的日志,全部完成只需1个小时以内,这时cpu的负载还是在极低的水平

要注意的可能是数据库的处理能力了,如果进程数过大,数据库的吞吐可能会造成瓶颈

总结:
1)当需要遍历一个大文件进行时,可以使用切分的方法切成n个较少的文件,再同时并行调用的方式处理,可以有效的减少处理时间.
2)在遍历入库的操作中,有效的合并可以减少数据库的操作次数

Tags: linux, 排序, 切分, 日志处理

memcache的几点注意

1)已经有for win的memcached了,可以在此下载(http://jehiah.cz/projects/memcached-win32/)
下载回来,直接解压后,进入解压后的目录,使用memcached -d install安装成一个windows的服务,要查看其它参数,可用memcached -h

2)每个被放到memcached的数据,key不能超过250个字节,value不能超过1M,否则会存不进去

3)当分配的内容全部用完后,再往里面放数据时,已过生命期的先被清除(已过生命期的数据,会在get的时候被清除),如果还不够用,就会自动把最先加入的条目删除(可使用-M参数启动,指明超出限制时,发出警告而不是清除数据)

Tags: memcache

将数组定义为常量

近日,在phpclass中看到一个将数组定义为常量的类,本人也比较喜欢使用定义常量作为配置项的开发方式,把相关的一组配置项定义为常量,也会经常用到.于是下来看看,原理很简单,在此记录一下:
1)使用两个静态的方法set和get来设定和获取内容
2)set的时候,将数组使用var_export函数转化,作为define的值
3)get的时候,将常量值使用eval('return '.$constName.";")方式反解

从上面可以看出,实现方法只是将数组转成可反解的字串存到常量中,用的时候再进行反解,由此可见,使用serialize和unserialize可以更方便地实现此要求,另外serialize还可以对object和resource类型的进行处理,也就是说,理论上讲,对象和资源(文件句柄)等都可以定义为常量的.

Tags: 常量

使用file_get_contents提交http post

我曾经发过一篇讲使用curl获取需要登陆内容的文章,但其实,自5.0开始,使用file_get_contents就可以完成.(前提是开启了allow_url_fopen),下面以一个简单的例子说明一下:
1.先看一下目标网页(假设是http://localhost/response.php)
response.php

PHP代码
  1. <?php  
  2. echo "<pre>";  
  3. print_r($_POST);  
  4. print_r($_COOKIE);  
  5. ?>  

本文讲述的只是http post请求的发送,所以,目标页只是回显所收到的post和cookie
2.请求页
request.php

PHP代码
  1. <?php  
  2. $data = array("name" => 'tim',"content" => 'test');  
  3. $data = http_build_query($data);  
  4. $opts = array(  
  5.   'http'=>array(  
  6.     'method'=>"POST",  
  7.     'header'=>"Content-type: application/x-www-form-urlencoded\r\n".  
  8.               "Content-length:".strlen($data)."\r\n" .   
  9.               "Cookie: foo=bar\r\n" .   
  10.               "\r\n",  
  11.     'content' => $data,  
  12.   )  
  13. );  
  14. $cxContext = stream_context_create($opts);  
  15. $sFile = file_get_contents("http://localhost/response.php", false, $cxContext);  
  16.   
  17. echo $sFile;  
  18.   
  19. ?>  

这个文件首先使用stream_context_create()构造了一个http请求,然后使用file_get_contents发送出去,返回的结果是:

XML/HTML代码
  1. Array  
  2. (  
  3.     [name] => tim  
  4.     [content] => test  
  5. )  
  6. Array  
  7. (  
  8.     [foo] => bar  
  9. )  


所以上可以看出,只要你了解http协议,完全可以使用这两个函数构造出所有正常的http请求,比如代理,断点续传等...
对照之前的关于curl的文章,就可以用来取得需要用户验证的内容了.

关于全局变量不能全局的问题

一直以为,全局变量(使用global声明)是在程序的所有地方都可以用的(不然怎么能叫全局?呵),但在最近的工作中,接连碰到几次全局变量无效的郁闷的事,记录一下出现的原因.
1.错误重现
问题出现在用我的简易框架时,在view中使用原来第三方定义好的函数时(此函数比较独立),下面模拟一下:

t1.php

PHP代码
  1. <?php  
  2. run(); //执行  
  3. function run(){  
  4.     include 'func.php';  
  5.     showGlobal();  
  6. }  
  7. ?>  


func.php

PHP代码
  1. <?php  
  2. $vars = 'I am global!';  
  3. function showGlobal(){  
  4.     global $vars;  
  5.     print('我使用全局变量:'.$vars);  
  6. }  
  7. ?>  


很简单的两个文件(第一次发现问题时,远比这复杂,在一层层排错后,得出最少化的问题重现环境),func.php是定义好的第三方函数,该函数使用了一些全局变量,如果这时把这两个文件放在一起,执行一下t1.php,会发现showGlobal里的$vars是显示不出来的,global失效了?
2.错误原因
搜索后发现,php.net上很早就有人提出过,也有人给出了解释(http://bugs.php.net/bug.php?id=2193):
原来,在t1.php的run函数中include func.php时,func.php中的变量$vars的作用域只是在run之内,而在showGlobal中使用global声明的$vars是要求属于t1.php的不是run函数的,所以为空)
3.解决方法
知道原因后,解决就很简单,可以把include 从run中移出来,这样,func.php中的$vars就属于t1.php了;也可以在run里用global声明一下$vars,这样也可以把原来属于run的$vars声明为全局(属于t1.php);

虽然问题可以简决,但用起来还是很不爽, 因为在我的phpec框架中,include的情况比较普遍,不可能把view中按需include的移到外层去,使用global的话,我在使用第三方函数时,又不会也不想去了解它用了什么全局变量,而且,层次结构一多,就....

总结:1)尽量减少多级和函数中include文件.2)尽量不用全局变量

Tags: 全局变量, global

lighttpd+fastcgi(PHP)下使用memcache作session handler的几

在apache下用memcache作session handler已经有一段时间了,感觉还不错,最近移植到使用lighttpd的机器上来,问题就来了,还困扰了

我不短的时间,现记录一下:

1.用法

动态编译memcache的php扩展,在程序中用dl('memcache.so')引入
直接用ini_set('session.save_handler','memcache')来启用memcache handler

2.问题现象

第一次访问正常,刷新时连不上memcached,停止,再刷,又正常...如此反复,一次正常一次不正常

3.查找原因

1.怀疑memcached使用的内存不够,加大n倍后,问题依旧,因如果不用作session save_handle,而是在程序中用作缓存处理,一切正常,所

以确定memcached正常.
2.因在apache下正常,因为apache跟memcache不同机器,而lighttpd与memcache相同,怀疑本机访问有问题(我曾试过mysql有这个问题

,是dns的设置有问题),换用n种地址写法,未果.重编n次lighttpd,加入--enable-memcache,仍然不行.在google上翻了n个十页,未有找到想

要的.(只找到memcache官方文档中,有不推荐直接用memcache作session handler的说明)
3.开始怀疑动态编译的memcache.so有问题,尝试静态方式,但不成功(将pecl下载的memcache源码解到php/ext下,执行./buildconf,

但./configure --help|grep memcache时没有看到被加入)
4.实在没有办法了,采用在php.ini中加入extension=memcache.so的方式替代dl的方式,奇迹出现了,开始正常了,来一个ab -n 1000 -c 50,

效率还不错  ^&^

4.结论
lighttpd+fastcgi下,session.save_handler = memcache并使用dl('memcache.so')会有问题,原因未明(可能是lighttpd的BUG)

Tags: memcache, lighttpd

SQLB : SQL Load Balancer安装试用

SQLB是一个开源的数据库连接池,可以有效地提高数据库的性能,支持oracle,mysql,postgreSQ,虽然最后更新已是四年前,但仍是一个不错的选择...

1.下载

SQLB是c/s结构,其中的C部分提供PHP及perl等接口,源代码及相关需求可在以下地址下载:
http://sqlb.sourceforge.net/frameset.html

2.安装(本文以支持mysql为例,安装SQLB前,请先安装好MYSQL)

a.安装sqlb的server端

tar -zxvf sqlb-2.4.8.taz
cd sqlb-2.4.8

./configure --with-mysql-libs=/path-to-mysql/lib/ --with-mysql-includes=/path-to-mysql/include/
make
make install


#注,configure时,要指定mysql的lib和include目录

安装完成后,要建立sqlb用户,并将源码中的conf/sqlb.conf拷至用户目录:

adduser -g nobody sqlb
chmod 770 ~sqlb/

cp ./conf/sqlb.conf ~sqlb/sqlb.conf
chown sqlb. ~sqlb/sqlb.conf


#sqlb.conf为sqlb的运行配置文件,编辑此文件加入数据库连接参数

至此,可以执行sqlb start启动sqlb了

#如果你编译时指定了prefix将sqlb安装到其它目录,请使用完整路径,要在开机自动运行,将源码中的init/sqlb.init复制到 /etc/init.d/sqlb.init(不能改名成sqlb),修改其中$sqlb_prefix指向你sqlb安装路径,再运行以下命令加入:

chkconfig --add sqlb.init
service sqlb.init start //启动
service sqlb.init stop //停止


#如果启动时提示找不到libmysqlclient.so.15,你需要将此文件(一般在mysql的lib目录)复制至PATH路径中(如:/usr/lib)


3.安装sqlb php 模块

sqlb的客户端可以编译进php内置模块,也可以编译成动态模块加载(so),方法如下:

先解压:

tar -zxvf sqlb-php-module-1.6.tgz
cd sqlb-php-module-1.6


a.静态编译

将源码目录的sqlb目录复制至php源码的ext目录,执行./buildconf
完成后,执行./configure --help|grep sqlb,你可以看到相关信息,此时你可以重新编译PHP,并使用--with-sqlb=/path-to-sqlb来加入sqlb支持,

b.动态模块

在sqlb目录下,执行:

phpize #如果提示无此命令,你可以加上完整路径(在php安装目录的bin下)
./configure --with-sqlb=/path-to-sqlb --with-php-config=/path-to-php/bin/php-config
make
make install

注意:sqlb本身不支持php5,所以在make时会出错,提示类似

sqlb.c:42: error: `BYREF_NONE' undeclared here (not in a function)

需要简单修改一下sqlb的源码,打开sqlb.c,定位到42行

注释掉42行,并将ZEND_FE的第二参数改为NULL

原代码:

static unsigned char argument_type[] = { 5, BYREF_NONE, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_NONE };
zend_function_entry sqlb_functions[] = {
        ZEND_FE(accelsqlb, argument_type)
        {NULL, NULL, NULL}

修改成:

C++代码
  1. //static unsigned char argument_type[] = { 5, BYREF_NONE, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_NONE };  
  2. zend_function_entry sqlb_functions[] = {  
  3.         ZEND_FE(accelsqlb, NULL)  
  4.         {NULL, NULL, NULL} 
经此修改,就可顺利make && make install了

完成后,会生成一个sqlb.so文件,将它加入php.ini的extension中

安装好后,查看phpinfo(),可以看到sqlb的支持已被加入了....

4.php连接测试

sqlb只提供一个方法

 

PHP代码
  1. $result =accelsqlb($query$db_name$db_type$state,[$timeout]) 


其中:
$query为要执行的sql语句
$db_name为要连接的数据库名
$db_type为数据库类型,此处为mysql
$state为执行完成状态码
$timeout为超时设置

state      : return value of the function :
                -2 : timeout occured
                -1 : sqlb error
                 0 : SQL error
                 1 : SQL query OK with resultset
                 2 : SQL query OK with empty resultset
                     ("No row selected")
                 3 : SQL query OK without resultset (DELETE, UPDATE,...)


所有返回结果可以通过查看$result获得,如果$state的值为1,则$result结果为:

array(
字段数n
n个字段名
第一条记录n个值
第二条记录n个值
..
)

其它返回方式请自行测试

Tags: sqlb, load balancer, 数据库, 连接池

关于session和memcache的若干问题

一直以来,由于php本身的session机制不能跨机,令很多phper感到不爽,现在流行的解决方案主要有:
1)使用数据库来实现
2)自己写server端,通过改写session处理函数来请求
3)使用nfs等跨机存储来保存session
4)使用memcache来保存
5)使用zend platform提供的解决方案

其中的1-4都是通过改用可以跨机的储存机制,再使用session_set_save_handler()来实现,5是zend公司的商业产品(不过据之前在使用的同事反映,效果不太满意),以上的方案,各有利弊,不在本文讨论范围

无论是用memcache,还是db,nfs,其原理是一样的,都是通过session_set_save_handler函数来改变默认的处理方式,通过指定回调函数来自定义处理,可以参考手册的session_set_save_handler()函数部分,有例子,比较容易明白

以下是一些我在使用memcache来实现时的一些记录:
1)使用类来实现时,各回调函数都定义为静态方法,在类的构造中使用session_set_save_handler注册回调函数, 如:
session_set_save_handler(
                array('memSession', 'open'),
                array('memSession', 'close'),
                array('memSession', 'read'),
                array('memSession', 'write'),
                array('memSession', 'destroy'),
                array('memSession', 'gc')
          );
memSession为类名,要使用session,则先new memSession,再session_start();


2)生存期和垃圾回收
memCache的set命令有生存期,即使用set命令添加值时,可加上lifetime,此时间可以作为session的生存期,用户在此时间内没有动作,则会失效,但有动作则不会失效(因为每一个脚本结束时,都会执行write和close,此时lifetime就会被更新了),当然,如果使用cookie传递SID,则控制SESSION生存期可以用:ini_set('session.cookie_lifetime',time)来设定,这其实是控制cookie的有效时间,如果session赖以生存的cookie消失了,当然session也就活不了,使用cookie_lifetime来控制的话,无论有无动作,都将在指定的时间后过时

gc是指垃圾回收,在session中是指清理过期的session数据,影响的参数有:
session.gc_maxlifetime 被视为垃圾前的生存期,超过此时间没有动作,数据会被清走
注意的是,gc不是每次启动会话都会被执行,而是由session.gc_probability 和 session.gc_divisor的比率决定的

结论:控制SESSION的生存期有几种方法
一是cookie_lifttime,这种方式无论有无动作,都会在指定时间内销毁
二是在read中根椐保存时间控制,此方法在有动作时时间会一直有效
三设定session.gc_probability 和 session.gc_divisor的比率为1(即每次会话都会启用gc),再设定gc.maxlifetime来指定生存期,此方法也是在用户有动作时时间一直有效

3)回调函数的执行时机
open 在运行session_start()时执行
read 在运行session_start()时执行,因为在session_start时,会去read当前session数据并写入$_SESSION变量
destroy 在运行session_destroy()时执行
close 在脚本执行完成或调用session_write_close() 或 session_destroy()时被执行,即在所有session操作完成后被执行
gc 执行概率由session.gc_probability 和 session.gc_divisor的值决定,时机是在open,read之后,即session_start会相继执行open,read和gc
write 此方法在脚本结束和使用session_write_close()强制提交SESSION数据时执行

结论:
session_start //执行open(启动会话),read(读取session数据至$_SESSION),gc(清理垃圾)
脚本中间所有对$_SESSION的操作均不会调用这些回调函数
session_destroy //执行destroy,销毁当前session(一般是删除相应的记录或文件),相应地,此回调函数销毁的只是session的数据,但此时

var_dump一下$_SESSION变量,仍然有值的,但此值不会在close后被write回去
session_write_close() //执行write和close,保存$_SESSION至存储,如不手工使用此方法,则会在脚本结束时被自动执行

清晰了以上信息,将对你清楚了解SESSION的工作原理有很大的帮助...

4)直接使用memcache作session处理
在我写了一系列的memcache来保存session的代码后,无意中发现,可以直接在php.ini中设定使用memcache作为session处理,而无须另外编码,方法是:
修改php.ini中的以下值
session.save_handler = memcache
session.save_path = 'tcp://host1:11211'  #有多个时直接用","分隔即可
如果只想在特定的应用里使用memcache储存session,可以使用ini_set的方法对以上两个参数进行设定

要测试一下是否真正用上了memcache,可以先捕足到使用的PHPSESSID,再作为KEY用memcach去读一下,就清楚了

Tags: session, memcache, 跨机session, 生存期

Records:4012345