ububtu server 14.04上,PHP5-FPM服务的启动方式变更 在一位前辈微博上看到,他总是给自己找点事做,让自己忙碌起来,严于律己。我甚有感触,相比之下自惭形秽,一直做不到,这应该就是大神跟我等小菜之间的差距吧。如今,事情主动找到我,而且是一而再再而三的找上来,我再去逃避,再不认真去面对这些问题,实在说不过去。 朋友在群里问了问题,ubuntu server 14.04版本,apt-get install的PHP5-FPM,自己编译了phpredis,却发现始终没成功加载phpredis这个扩展。 ubuntu@shnj-b-batch-30-222:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.2 LTS Release: 14.04 Codename: trusty 以及他的操作方式 而结果,却无任何回显。ps aux也看到所有FPM原来进程都还在,尝试sudo /etc/init.d/php5-fpm stop也无法停止掉进程,也无回显。无奈之下killall php5-fpmkill掉了所有FPM进程。当他再尝试sudo /etc/init.d/php5-fpm start,却发现PHP5-FPM启动不起来了。 那么问题来了,为什么sudo /etc/init.d/php5-fpm stop无法停止掉PHP5-FPM,是SIGNAL信号没传到FPM那边吗?为什么KILL之后不能重新启动了?是php加载了拓展时遇到意外,进程崩溃退出了吗?如何排查?群内有朋友建议上gdb来查看是不是php加载扩展出现意外了。但我觉得,php加载拓展前,还有很多事情要做,php加载扩展是/etc/init.d/php5-fpm 脚本执行/usr/sbin/php5-fpm去启动的,在执行之前,还有很多事情去做,比如获取系统shell变量,获取获取系统其他配置。执行后,还会去去读ini配置,读取拓展的so文件等等。我想先看下程序大约终止之前的文件操作走到了哪里。故我决定先用strace看下。 从strace结果可以看到,程序/etc/init.d/php5-fpm依次访问(open\stat)了 /usr/sbin/php5-fpm /etc/default/rcS /lib/init/vars.sh /lib/lsb/init-functions /sbin/initctl 之后,就以+++ exited with 1 +++结束了。并没有执行/usr/sbin/php5-fpm来操作对应的pidfile,执行stop指令。下面看下/etc/init.d/php5-fpm里如何写 大约可以看出,脚本执行到 init_is_upstart() 函数判断后,就退出了,exit 1 。从代码中文件访问(这里包括open、stat)顺序,以及退出是exit(1)的错误产生后退出。这也可以在执行echo $?来确认。既然知道这里退出的,那么修复方法就很简单了,注释掉 if init_is_upstart; then \ exit 1 \ fi这三行,就好了。 注释掉/etc/init.d/php5-fpm的27、28、29行之后,尝试/etc/init.d/php5-fpm start\stop\restart“果然”均正常... 注释掉/etc/init.d/php5-fpm的27、28、29行之后,就可以了???? 为什么代码里要加上那句判断,为什么判断后就退出了,不执行FPM的相关操作了?这么改之后,虽然FPM的启动、重启、停止都声场,但总觉得哪里不对,回到虚拟机上,我多测试了几遍,出现了更奇怪的事情。在上面的程序启动后,我随手敲了service php5-fpm stop返回了stop: Unknown instance:,嗯?service服务管理程序不能管理/etc/init.d/php5-fpm启动的程序?我记得是可以的,又立刻到服务器上做了测试,服务器是UBUNTU server 12.04 的,测试结果为他们可以互相管理对方启动的进程。 问题又来了,为什么ubuntu 12.04上都正常,而在ubuntu 14.04上不可以呢? 是不是我注释掉的那三行代码导致的?系统更新,变更了哪些内容?为此,我认真的看了service脚本的代码。 解释这段代码时,$SERVICE以php5-fpm为例,代码中写着,若系统中存在/etc/init/${SERVICE}.conf、同时initctl存在,且initctl version结果中包含upstart字符串,则执行start\stop\reload ${SERVICE} ${OPTIONS}。若不符合条件,则执行$SERVICEDIR/$SERVICE脚本来管理该程序。这里根据当前操作系统当前使用的init系统,来选择使用upstart还是sysvinit形式来管理服务。 同时,service在手册里的注释如下: DESCRIPTION service runs a System V init script or upstart job in as predictable environment as possible, removing most environment variables and with current working directory set to /. The SCRIPT parameter specifies a System V init script, located in /etc/init.d/SCRIPT, or the name of an upstart job in /etc/init. The existence of an upstart job of the same name as a script in /etc/init.d will cause the upstart job to take precedence over the init.d script. The supported values of COMMAND depend on the invoked script, service passes COMMAND and OPTIONS to the init script unmodified. For upstart jobs, start, stop, status, are passed through to their upstart equivilents. Restart will call the upstart 'stop' for the job, followed immediately by the 'start', and will exit with the return code of the start command. All scripts should support at least the start and stop commands. As a special case, if COMMAND is --full-restart, the script is run twice, first with the stop command, then with the start command. This option has no effect on upstart jobs. service --status-all runs all init scripts, in alphabetical order, with the status command. This option only calls status for sysvinit jobs, upstart jobs can be queried in a similar manner with initctl list'. FILES /etc/init.d The directory containing System V init scripts. /etc/init The directory containing upstart jobs. 根据man手册的解释,upstart是ubuntu特有的init管理系统,最初也是使用system v init系统来管理服务,现在debian好像也用了。system v init是最初大多数类unix系统上的init系统。后来system v init系统的性能问题,对热插拔的USB支持问题等诸多问题,大部分类unix系统改为了systemd。 但大多数linux都对system v init做了兼容。所以,service脚本为了支持两种init系统,做了封装,根据当前系统的init系统,来选择执行响应的操作。在ubuntu上, upstart系统的程序是initctl,而且initctl start\stop\restart\reload可简写为start\stop\restart\reload。 system v init系统的程序是start-stop-daemon(centos 上是daemon程序),service是对两种init系统执行的代码封装。/etc/init.d是System V init系统脚本的执行文件目录。/etc/init是upstart的配置文件目录。我特意对比虚拟机跟服务器上的service脚本的区别,只发现对upstart的判断更严格了,并没有太大区别...不过,我发现了一件奇葩的事情...就是service php5-fpm status指令跟service --status-all|php5-fpm的结果(在ubuntu 14.04上)并不一样,前者是upstart管理的服务状态数据,即upstart init系统的 status 指令来查看php5-fpm状态的;而后者是system v init的结果,即从$SERVICEDIR/$SERVICE下的脚本程序获取状态的,这...... 但是。。。也就是说知道service脚本的封装操作后,然而,这并没有解决我刚刚的疑问「为什么ubuntu 12.04上service脚本跟/etc/init.d/php5-fpm可以相互管理,而14.04上不可以?」,尽管我知道system v init跟upstart不能对同一个服务的状态互通,但为什么12.04上却可以?这到底是为什么呢?在我做了很多测试(走了很多弯路)之后,决定去服务器上再检查一遍环境。在我写出检查结果之前,我说下走弯路期间的发现吧。 之前提到过一个错stop: Unknown instance:,这也比较好理解,php5-fpm程序是用(注释那3行代码后)/etc/init.d/php5-fpm启动的,即system v init管理的。而使用service php5-fpm stop时,显然不能获取到PHP5-FPM服务的状态的,故返回stop: Unknown instance:。如果我反过来,使用system v init来关闭upstart启动的php5-fpm会发生什么呢? 细心的朋友会发现,原来的FPM主进程已经不存在,残留了2个子进程(父进程为1,是因为其父进程意外退出,被PID 为1 的init进程接管),而且, 从最后剩余的pid来看,也不是原来的子进程了。 同时,原来的pid file 跟 unix domain socket 监听文件都也残留....可以看出start-stop-daemon程序可以关闭原来的PHP5-FPM进程(直接执行kill指令,发送SIGTERM信号,当然可以关闭其他进程)。为什么会出现进程残留,原来子进程ID都不一样的问题。查看/var/log/upstart/php5-fpm.log中,有这么几条日志 system v init系统的start-stop-daemon调用kill指令,发送SIGTERM信号给FPM进程,让其退出。然而upstart发现进程退出,进行保护又启新的进程,然后又被start-stop-daemon 发送SIGKILL信号杀了。然后子进程还存活着...同时,原来的pid file 跟 unix domain socket 监听文件都也残留....也导致后来再启动时,发现sock文件依旧存在,无法再次启动。 回到刚刚的话题上,我去重新确认了配置,在ubuntu 12.04上,/lib/lsb/init-functions中,并没有init_is_upstart函数的定义。最主要的是,在/etc/init/目录下,根本没有/etc/init/php5-fpm.conf这文件的存在,也就是说,ubuntu 12.04上 是以system v init 来管理php5-fpm的。。。知道真相的我,眼泪都要留下来。。。 好了,最初的修改/etc/init.d/php5-fpm注释掉的3行代码,显然要还原。解决PHP5-FPM(包括但不限于)启动、停止、重启的方式,就用系统封装好的service吧。 总结本次获得的新技能 推荐使用service来管理服务,让service根据当前操作系统,自己确定选择用upstart、sysvinit、systemd来管理程序,而非我们主观上的认定。 ubuntu 1404 上 service XXXX status 跟 service --status-all|grep XXXX 的获取XXXX服务状态的方式不一样。前者是upstart,后者是sysvinit. UBUNTU 1404 使用sysvinit方式(/etc/init.d/php5-fpm stop)来关闭upstart启动(start php5-fpm或service php5-fpm start)的FPM进程时,会造成FPM进程无法全部杀死,直接发送SIGTERM信号给FPM主进程,然后upstart监控发现原来PID的进程不存在了,立刻创建。然后sysvinit(start-stop-daemon进程)二次检测,又继续kill。新启动的FPM子进程的所属主进程被kill了,他们的父ID没了,就被PID为1的init进程接管...发现类似的情况时,多数是主进程被意外终止掉了。 类似案例: upstart script ignores some php-fpm.conf parameters, cannot be bypassed in preference of SystemV init script. Ubuntu14.04重启网卡不生效 对于这些案例,相应的程序提供了一定的信息输出,比如“Stopping or restarting the networking job is not supported. ”起码告诉我们说不支持这种方式了,FPM应该echo一句话,告诉使用者不支持这种方式管理此服务了,这样,大家都方便了。 参考资料: 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 浅析 Linux 初始化 init 系统,第 2 部分: UpStart 浅析 Linux 初始化 init 系统,第 3 部分: systemd Operating system service management Linux init 2015-07-08 3 0
nginx上,http状态200响应,PHP空白返回的问题 最近的大半年中,编程语言从PHP换到了Golang后,就很少接触PHP,当然,更多的还是恋恋不舍。尽管如此,每当有人在群里聊起PHP的话题时,我总是想插几句,怀念怀念,同时也温故温故知识点,可不能把她给忘了。 昨天朋友tywei问我一个关于PHP奇怪问题,查到原因解决后,没有详细的解释。夜里睡觉时,老是回想这事,早上醒来,决定还是认真记录一下这些问题。也让自己回归正常状态,多写点博客,总结自己,记录自己。 问题描述 PHP+nginx的环境,任何PHP处理的结果,都是空白页面。OS是ubuntu 14.10 ,nginx 1.6.2 ,PHP5.5.12, 问任何PHP的页面,返回的HTTP状态200,但页面内容是空的,什么都没有,不管PHP页面里写的是什么,正常响应。初步怀疑这是拓展的问题,处理请求后,输出内容有冲突,输出为空之类。想查看拓展列表,看看加载了哪些拓展,但任何PHP代码都返回空,万不得已,在CLI模式下运行,确定ini是同一个。粗略的看了加载的模块,列表如下: 大约如上的模块加载,怀疑xdebug跟opcache冲突,尝试关闭后,仍未解决。 strace看系统调用信息 觉得好简短,好奇怪,访问的是SCRIPT_NAME index.php,怎么都没 lstat \open 这个PHP文件呢?直接返回了?起码要判断SCRIPT_FILENAME是否存在吧,要读取SCRIPT_FILENAME,解析里面的代码吧? 等下.....SCRIPT_FILENAME全部地址是啥?发来的CGI协议包中怎么没有SCRIPT_FILENAME? 仔细看下CGI包的内容 缺少: 那么问题来了 PHP-FPM接收CGI请求时,如果没有SCRIPT_FILENAME怎么处理的? 发来的CGI协议包中,为啥没有SCRIPT_FILENAME? (SCRIPT_FILENAME是什么,干啥用的,这个就不要问了吧。) 问题1:PHP-FPM接收CGI请求时,如果没有SCRIPT_FILENAME怎么处理的? fpm源码里(PHP5.5.x 为例) 同样是 fpm_main.c中init_request_info函数的代码如下: 从代码里看出,script_path_translated变量就是cgi协议包中SCRIPT_FILENAME的结果,其中1115行左右,判断如果script_path_translated为空,并且env_path_translated不为空,则用env_path_translated赋值到script_path_translated上。 之后,对request_info的几个属性给予了默认值,包括request_method为null,以及http_response_code默认200的http响应。 在后面的代码中if (script_path_translated) ,因为CGI包中没有SCRIPT_FILENAME,也没有PATH_TRANSLATED,即script_path_translated为空,故没有对request_method进行赋值,其默认值为NULL。 回到main函数中1839行附近if (!SG(request_info).request_method) ,则直接goto到了fastcgi_request_done,直接结束当前request请求,由于之前有设置过默认的http 响应状态为200 ,也就导致了每次返回http状态200成功响应的空白页面的问题。 同时也解释了strace系统调用中,没出现lstat、open 操作SCRIPT_FILENAME的记录了。 问题2,发来的CGI协议包中,为啥没有SCRIPT_FILENAME? PHP-FPM接收到的CGI协议包都是来自前面nginx的,cgi协议包中没有这个,肯定是nginx没发来,查看nginx配置看到fastcgi_params中没有这项。加上后就可以了。 解决办法: 在nginx配置的 fastcgt_params中加上SCRIPT_FILENAME的配置(在ubuntu的apt-get形式安装nginx配置中,默认是有这条的),比如 题外话: 在FPM的fpm_main.c文件的main函数中,1848行对SG(request_info).path_translated的判断,晚于1849行SG(request_info).request_method的判断。而在init_request_info函数中,对SG(request_info).path_translated的赋值却是比SG(request_info).request_method早的。 而且,明明找不到需要执行的脚本,却还返回200的http响应,很奇怪,也不方便排查。我觉得把对SG(request_info).request_method的判断放到SG(request_info).path_translated后面更合适一些。或者若找不到SCRIPT_FILENAME的话,http响应状态改为404,同时,写入LOG日志,便于排查。 后记: 从fpm代码里可以看到,其实作者是有考虑到没有SCRIPT_FILENAME的问题的,只是判断顺序搞错了。所以,我觉得这应该是个bug,就提了一下:BUG #69625:php-fpm return http 200 response on nginx without SCRIPT_FILENAME,不知道官方是否认为这是个BUG。 新的问题: 为什么如果SCRIPT_FILENAME不存在时,用PATH_TRANSLATED来代替它?PATH_TRANSLATED是每个CGI前端都要发送的吗? 这个问题,后来认真看了下,感觉还挺复杂,跟CGI客户端有关,PHPFPM针对IIS\APACHE\NGINX的处理都不一样。以后再写吧。 参考资料: RFC3875 - The Common Gateway Interface (CGI) Version 1.1 如果你在阅读PHP源码,或者阅读PHP SAPI、PHP拓展源码,可以关注一下PHP源码执行流程图 关于FPM的执行流程,可以看下下面这幅图: 2018年07月09日更新: Fix bugs #75120 and #69625 #3226里修复了我在2015年3月提的 bug report Fix bug #69625 php-fpm return http 200 response without SCRIPT_FILENAME #1270 2015-05-05 +3 7 0
PHP API中,MYSQL与MYSQLI的持久连接区别 很久很久以前,我也是因为工作上的bug,研究了php mysql client的连接驱动mysqlnd 与libmysql之间的区别php与mysql通讯那点事,这次又遇到一件跟他们有联系的事情,mysqli与mysql持久链接的区别。写出这篇文章,用了好一个多月,其一是我太懒了,其二是工作也比较忙。最近才能腾出时间,来做这些事情。每次做总结,都要认真阅读源码,理解含义,测试验证,来确认这些细节。而每一个步骤都需要花费很长的时间,而且,还不能被打断。一旦被打断了,都需要很长时间去温习上下文。也故意强迫自己写这篇总结,改改自己的惰性。 在我和我的小伙伴们如火如荼的开发、测试时发生了“mysql server too many connections”的错误,稍微排查了一下,发现是php后台进程建立了大量的链接,而没有关闭。服务器环境大约如下php5.3.x 、mysqli API、mysqlnd 驱动。代码情况是这样: 从这个后台进程代码中,可以看出“$Property”变量以及“$Mounts”变量频繁被创建,销毁。而ROLE对象的getModule方法是这样写的 可以看出getModule方法只是模拟单例,new了一个新对象返回,而他们都继承了Afx_Module_Abstract类。Afx_Module_Abstract类大约代码如下: 类Afx_Module_Abstract中关键代码如上,跟DB相关的,就setAdapter一个方法,回到“后台进程A”,setAdapter方法是将Afx_Db_Factory::DbDriver($config['mysql']['driver'])的返回,作为参数传了进来。继续看下Afx_Db_Factory类的代码 一看就知道是个工厂类,继续看真正的DB Adapter部分代码 从上面的代码可以看到,我们已经启用长链接了啊,为何频繁建立了这么多链接呢?为了模拟重现这个问题,我在本地开发环境进行测试,无论如何也重现不了,对比了下环境,我的开发环境是windows7、php5.3.x、mysql、libmysql,跟服务器上的不一致,问题很可能出现在mysql跟mysqli的API上,或者是libmysql跟mysqlnd的问题上。为此,我又小心翼翼的翻开PHP源码(5.3.x最新的),终于功夫不负有心人,找到了这些问题的原因。 从mysql_pconnect的代码中,可以看到,当php拓展mysql api与mysql server建立TCP链接后,就立刻将这个链接存入persistent_list中,下次建立链接是,会先从persistent_list里查找是否存在同IP、PORT、USER、PASS、CLIENT_FLAGS的链接,存在则用它,不存在则新建。 而php的mysqli拓展中,不光用了一个persistent_list来存储链接,还用了一个free_link来存储当前空闲的TCP链接。当查找时,还会判断是否在空闲的free_link链表中存在,存在了才使用这个TCP链接。而在mysqli_closez之后或者RSHUTDOWN后,才将这个链接push到free_links中。(mysqli会查找同IP,PORT、USER、PASS、DBNAME、SOCKET来作为同一标识,跟mysql不同的是,没了CLIENT,多了DBNAME跟SOCKET,而且IP还包括长连接标识“p”) MYSQLI为什么要这么做?为什么同一个长连接不能在同一个脚本中复用? 在C函数mysqli_common_connect中看到了有个mysqli_change_user_silent的调用,如上代码,mysqli_change_user_silent对应这libmysql的mysql_change_user或mysqlnd的mysqlnd_change_user_ex,他们都是调用了C API的mysql_change_user来清理当前TCP链接的一些临时的会话变量,未完整写的提交回滚指令,锁表指令,临时表解锁等等(这些指令,都是mysql server自己决定完成,不是php 的mysqli 判断已发送的sql指令然后做响应决定),见手册的说明The mysqli Extension and Persistent Connections。这种设计,是为了这个新特性,而mysql拓展,不支持这个功能。 从这些代码的浅薄里理解上来看,可以理解mysqli跟mysql的持久链接的区别了,这个问题,可能大家理解起来比较吃力,我后来搜了下,也发现了一个因为这个原因带来的疑惑,大家看这个案例,可能理解起来就非常容易了。Mysqli persistent connect doesn't work回答者没具体到mysqli底层实现,实际上也是这个原因。 代码如下: 查看进程列表里是这样的结果: 这样看代码,就清晰多了,验证我的理解对不对也比较简单,这么一改就看出来了 如果你担心被close掉了,这是新建的TCP链接,那么你可以打印下thread id,看看是不是同一个ID,就清楚了。(虽然我没回复这个帖子,但不能证明我很坏。)以上是CLI模式时的情况。在FPM模式下时,每个页面请求都会由单个fpm子进程处理。这个子进程将负责维护php与mysql server建立的长链接,故当你多次访问此页面,来确认是不是同一个thread id时,可能会分别分发给其他fpm子进程处理,导致看到的结果不一样。但最终,每个fpm子进程都会分别维持这些TCP链接。 总体来说,mysqli拓展跟mysql拓展的区别是下面几条 持久链接建立方式,mysqli是在host前面增加“p:”两个字符;mysql使用mysql_pconnect函数;。 mysqli建立的持久链接,必须在mysqli_close之后,才会下面的代码复用,或者RSHOTDOWN之后,被下一个请求复用;mysql的长连接,可以立刻被复用 mysqli建立持久链接时,会自动清理上一个会话变量、回滚事务、表解锁、释放锁等操作;mysql不会。 mysqli判断是否为同一持久链接标识是IP,PORT、USER、PASS、DBNAME、SOCKET;mysql是IP、PORT、USER、PASS、CLIENT_FLAGS 好了,知道这个原因,那我们文章开头提到的问题就好解决了,大家肯定第一个想到的是在类似Property的类中,__destruct析构函数中增加一个mysqli_close方法,当被销毁时,就调用关闭函数,把持久链接push到free_links里。如果你这么想,我只能恭喜你,答错了,最好的解决方案就是压根不让它创建这么多次。同事dietoad同学给了个解决方案,对DB ADAPTER最真正单例,并且,可选是否新创建链接。如下代码: 看来,开发环境跟运行环境一致是多么的重要,否则就不会遇到这些问题了。不过,如果没遇到这么有意思的问题,岂不是太可惜了? (图:天冬) 2013-08-15 +2 6 0
php与mysql通讯那点事 BUG、BUG,似乎末学的学习历程都是因为BUG引起的: 在我们的一款WebGame的生产环境中,一次无意的strace抓包时,发现了php与mysql大量通讯的数据。这种情况,在游戏服务器刚启动时,是正常的,但如果是运行一段时间之后,出现大量SELECT的SQL查询,绝对是有问题的,而且,所操作的数据库并不是配置库,那意味着,我们程序员的程序出现了违规的操作。具体结果大约如下: 如上图所示,php持续接收读取进程内描述符为3的响应包数据,描述符为3的为php与mysql建立的TCP通讯链接,这点也可以从313行的SELECT语句来确认。(原始数据丢失了,我模仿了一条。所以是配置库的SQL语句) 这是什么程序,想实现什么逻辑?为何要取这么多数据? 跟着这里的SELECT的sql语句,我定位到了相应的程序段: 我们从代码上来看,好像明白程序员想根据对应的role_id到role_items表里取一条想符合的数据,所以,他调用了row方法,来取一条。看上去,这里好像正常,我们都以为框架会给我们只取一条。但实际上,框架是如何处理的呢? 我们来看下框架的对应row方法的实现过程。对了,我们是CodeIgniter框架的一个较老的版本。 我们可以看到CodeIgniter框架的resultArray方法使用mysql(我们的php调用mysql的api用的是mysql函数,有点绕,后面解释)的mysql_fetch_assoc函数对缓冲区的数据进行遍历转换。将所有缓冲区的数据全部复制给$this->resultArray属性,再判断row方法中所需要的key的结果是否存在,再与返回的。 也就是说,框架层并没有只从mysql server(潜意识上的mysql server)那边取一条给我们调用者,而是取了所有结果,再返回一条。(先别喷,后面解释) 当然,CI这种做法,也不是错。但我觉得有更好的改进方法。 这个问题,我们组的dietoad (征婚) 发现了这个问题,并给了修复方案。有些同学认为,这是程序员的错,程序员的SELECT语句没有加limit来限制条数。这我绝对赞同,而且,觉得写出这种代码的人都得死。 业务层:为这种业务需求的SQL语句加上limit限制 框架层:框架对于这种需求,自动控制,发现这种情况,直接返回1条 对于解决方案1,我写了一个正则,匹配select()方法被调用之后,row()方法被调用之前,中间没有使用limit()方法的所有代码,结果,发现量并不小。后来,我们决定两种方案同时实施,防止第二种出现漏掉的情况。 dietoad给出如下改进: 在今年的4月末,鄙人写过另一篇关于CodeIgniter框架的设计缺陷问题,给我们游戏项目带来较大的影响,后来提交到github issues,并没得到回复,想了想,虽然官方的2.1.3版本中,也存在这个小问题。不过我觉得,这就不提交了,或许,我们的做法也符合他们的设计初衷。不过,我们还是在我们的项目中改进了。 如此改进之后,我们使用php的memory_get_usage()函数观察前后两个row()方法的结果时,果然发现内存使用情况有较大改善(改善幅度取决于SELECT的返回数据量)。 似乎,到这里就应该结束了,问题就这么被发现,被解决了。 但,我总觉得少了些什么呢?当我再次strace抓包时,发现仍然存在大量的数据通讯,就像文章开头的那副截图一模一样。然而,这又是什么原因呢? 我顺手写了个内存占用的测试代码如下: 看到结果时,我不禁XX一紧,什么?这你妈什么情况?查询完之后,内存大小居然只增加了不到1k?我那个表可是几十M的数据啊?遍历结果集之后,怎么突增几十M啊?尼玛这到底是什么情况?strace返回的大量数据到底存在哪的?算不算php进程申请的? 后来,我再次执行如上程序,再定时用free、/proc/PID/maps 之类系统工具,查看系统的内存使用情况,确认了当前进程的内存占用确实存在。那么可能的情况就是memory_get_usage()函数并没有获取到mysql_query之后的内存占用情况。由于比较怀疑,末学跟进了memory_get_usage()函数的源码,该函数直接交给zend_memory_usage函数处理。 php的内存管理 (中文地址:php-zend的内存管理中文版)这块,对于末学来说,太复杂了,只是稍微看懂直接 返回了mm_heap结构体的real_size/size的值。(两篇都是鸟哥写的,中文的地址也就是鸟哥博客最近一直打不开,抽风得厉害) 那mysql_query的结果集,存在哪的呢?如何申请内存的,莫非不是调用zend的_emalloc内存分配函数的?这得先明确mysql客户端类库问题,也就是我们使用哪个类库?libmysql还是mysqlnd,通过查看编译参数,发现(我的虚拟机)是libmysql,编译参数是这样的 有点乱: mysql、mysqli、pdo-mysql、libmysql、mysqlnd 好多名词,有点乱,没关系,一张图让你清晰起来: mysqlnd跟libmysql一样,都是直接与mysql server通讯的驱动类库。 而php程序员使用的mysql、mysqli、pdo-mysql是面向程序员调用的API接口。。 继续: libmysql类库是MYSQL官方提供的类库,每次PHP编译都是指定参数来确定mysql\mysqli\pdo-mysql所使用的连接驱动是哪个。并且,前提你的得先装好mysql的客户端(libmysql类库),以确保有libmysqlclient.so , 末学抱着试试看的心态,心情沉重的打开了libmysql的源码,终于在Safemalloc.c的line:120附近找到类似libmysqlclient申请内存的代码 也就是说,libmysql没有调用zend的内分分配函数_emalloc,就没法将内存的使用情况记录到mm_heap结构体中,也就是PHP的memory_get_usage()函数统计不到的原因。好了,虽然末学不是很能读懂源码,但似乎符合问题发生的现象了。 好像,末学又想到一个问题,如果libmysql保存的结果集所占用的内存的话,那么php的配置文件中的memory_limit也就无法限制他的内存使用情况了?也就是说,如果我们很理想的根据系统剩余内存分配了若干个php-fpm进程来启动运行的话,如果发生这情况,将会出现内存不够用的情况,libmysql占用的内存没有被统计到。。。结果是显然的,果然限制不了它。 那mysqlnd可以吗?mysqlnd的内存分配是使用zend的_emalloc函数吗?是的,没错mysqlnd 是我们的大救星。Mysqlnd_alloc.c line:77里代码中,明确看到了。各位SA在编译php时,一定要使用mysqlnd作为php连接mysql server的类库驱动哦。 Mysqlnd的好处可不止这么一点点啊。 内存还是内存: 末学苦于薄弱的英语,冒死翻过GFW,终于在“万恶的资本主义”国家的网站上找到了这些资料,mysqlnd将比libmysql节省将近40%的内存占用哦。如图: ,而且,memory_limit参数可以管的了它哦... 速度,速度: 国外友人给了一份测试结果,比较的API是mysql\mysqli,比较的驱动是libmysql\mysqlnd 使用mysqlnd驱动的ext\mysqli接口速度最快 使用libmysql驱动的ext\mysqli接口慢了6% 使用libmysql驱动的ext\mysql接口慢了3% 并且给出了mysqli在两个驱动下的执行时间: 还有,还有哦... mysqlnd还支持各种debug调试哦,各种strace跟踪哦...还支持....算了,你自己下载mysqlnd相比libmysql的优点看吧。末学可是搜了很久才搜到这个ppt。 推荐: 1,再推荐一片关于mysqlnd持久链接的文章:PHP 5.3: Persistent Connections with ext/mysqli 2,你的应用的cache的存储是程序员自己根据DB数据结果,查询条件,hash取值,存到memcache中的吗?想不想尝试下自动实现的?mysqlnd的插件可以尝试下:PHP: Client side caching for all MySQL extensions ,支持memcached,apc,sqlit哦。 回到开始: 有人说,当php调用mysql_query时,mysql server会返回本次查询的结果到php所在服务器的缓冲区中。当程序调用mysql_fetch_assoc/mysql_fetch_row/mysql_fetch_array/mysql_fetch_object之类函数时,都是调用php_mysql_fetch_hash函数去缓冲区读取数据的。我要是用mysql_unbuffered_query()函数呢?让结果集不直接在查询之后返回,当调用mysql_fetch_x函数时,再拉回来呢? 这...你让mysql server的缓冲区来存储这些数据么?你以为客户端就你自己么?其他的客户端也要连的啊,尤其是php,如果用mysql_unbuffered_query()函数,他们都会将结果集放到mysql server的缓冲区的,mysql server的内存占用岂不是成本增长...你想让DBA砍死你? 手册上也说了,mysql_unbuffered_query返回的结果集之上不能使用 mysql_num_rows() 和 mysql_data_seek()。我几乎没用过这个函数,这算非主流的函数么? 有人说我们方案1节省了从结果集取出,遍历赋值给新数组的内存占用,并没有减少网络数据的传输。没错,你说的对,一点都没错。也就是说,我们的解决方案2只能稍微缓解这种问题的负面效果,彻底解决的话,还得程序层上去正确的调用,取回该要的数据。(其实,如果使用mysqlnd驱动的话,我们的改动基本没有优势,节省不了内存。mysqlnd时,结果集的读取只是引用缓冲区的数据。libmysql的话,有明显效果。)我更加鉴定的赞同的那句话“写出这种代码的人都得死”。不使用mysqlnd作为php连接驱动的SA都是耍流氓。 结论: api推荐mysqli,驱动推荐mysqlnd. 温故而知新? 在回家之后,末学刷了几局《保卫萝卜》,除了几个需要养成才解锁的关卡之外,均可耻的"全清"+"金萝卜",玩着玩着,突然想起一件事情,就是末学在去年写过一篇博客php5.3.8中编译pdo_mysql的艰难历程中,之前运维的编译参数中,mysqli使用的是mysqlnd,而mysql使用的是libmysql,后来再装的pdo-mysql也使用了libmysql了....3个api,指定两个连接驱动,莫非上次的错误是因为这个?而末学的编译参数虽然巧合的解决了问题,当初并没有理解真正的原因?下周验证一下... [2012/12/15 23:31更新] 知耻而后勇? 今天刚写完这篇学习笔记后,回家玩游戏时,想起鸟哥曾提到过mysqlnd,再次回去看看,看鸟哥如何讲解mysqlnd的,我理解的是否有误,才发现鸟哥这里已经有了个Ulf Wendel博客的链接,末学却在网络搜索N久才找到那篇文章,同时,发现其blog上有大量mysqlnd的文章,还暗自偷笑,以为自己发现了大金矿,现在才发现….哎,惭愧惭愧…[2012/12/15 23:58更新] 末学对于本次学习经历中遇到的知识点,有大量的盲区,将会在以后的时间里,慢慢摸索熟悉,也欢迎各位前辈的点拨。 好像...好像...末学的问题太多了... 2012-12-15 +4 8 0
使用APC来保护PHP代码 问题就那么发生了: PHP的项目在生产服务器运行时,基本都是明文的文件,如果要保护代码的话,多数使用商业的加密软件,比如Zend Guard或者其他软件。zend也就是php程序语言Zend Engine语法分析引擎的公司出品。该产品会对php代码进行加密转换,费用大约是$600每年(大约4000人民币),按照公司授权。zend guard收费倒不是主要问题,如果单单购买加密功能的话,项目性能会奇差,相信很多朋友都遇到了,尤其是webgame中,刚开服时,几乎都是cpu 100%,load 奇高。使用时,需要服务端在装一个解密的程序,每个脚本文件,在每次被访问时,都需要解码、验证授权。这种繁琐的步骤,无疑浪费了时间、系统资源,也是我们一个项目当初在“友好邻邦”服务器上出现超高占用CPU的原因。(OS load占用高达100倍) 这些都是些什么玩意: 这产品是加密,如果想要程序执行流程优化的话,Zend又很“周到”的为我们提供另外一款产品 Zend Server,每年只需要最低支付$1695美元(大约11000人民币)(其他黄金版、白金版需要上万美元)。。。这样一来,我们的产品被绑架在zend上,如果我们需要定制功能的话,那是不可想象的。 如果是zend产品有bug,那我们只能干等着,等他们解决。除了zend这款,还有另外一款代码加密产品:ioncube,费用倒是不高,大约400美元每年。但用户量少,产品是否稳定不清楚,不开源,社区支持不是很好。 找寻: 有没有一款可以保护代码,社区支持好,用户量大,反馈处理及时,费用低的产品吗? 幸运的是,有这么一款开源产品APC:http://pecl.php.net/package/APC Alternative PHP Cache (APC)是一款php代码加速产品,该产品作为php程序的一个拓展,是一个开放自由的PHP opcode 缓存。它的目标是提供一个自由、 开放,和健全的框架用于缓存和优化PHP的中间代码。 早在3-4个月之前,鸟哥博客上一篇文章《关于PHP的编译和执行分离》中提到APC来作为PHP代码保护的方案。从文中可以看出,鸟哥的想法是每个php文件,导出一个opcode 的bin文件,加载时,也是挨个加载,这样也实现了代码保护,但一个项目几百个php文件的话,也得相应存在几百个bin文件,量比较大,操作比较复杂,管理不方便,不好做版本验证(以后会提到)。末学比较倾向于单个bin文件的导出,单个opcode bin文件的加载。而且,单个bin文件的加载,可以避免项目中出现部分文件跟整体版本不一致的情况发生,运维同事再也不用担心个别文件跟整个项目版本不一致的情况了。 原来是你: APC是替换了php zend引擎的zend_compile_file函数,来决定是否重新对php脚本文件的读取,扫描、解释、编译等步骤。启用apc之后,将跳过读取、扫描、解释、编译这几步,节省内存、CPU开销,直接执行OPCODE。Apc还提供对php源码的opcode的缓存,导出到一个二进制文件中,还提供加载这个二进制文件。起初,我们开始使用这个功能时,遇到该拓展的多个BUG,都已经提交到php官方,分别是: BUG #62757 、BUG #62765 、BUG #62825 ,并积极配合php内核组开发成员重现BUG,抓获coredump,提取bug demo代码,在PHP官方成员-APC拓展开发组长Xinchen Hui(以下称鸟哥 laruence)的帮助下,很快的修复了这些BUG 。 如此繁琐: 这样仍存在一个繁琐的步骤,即php-fpm每次启动时,没有自动加载bin文件,需要管理员去手动执行或者访问脚本,(该脚本内使用apc_bin_load/apc_bin_loadfile函数去加载bin文件)让php-fpm加载bin文件,对于单大区多台前端,以及单服务器运行多个php-fpm主进程来说,如何判断那个php-fpm主进程是否成功加载,这是个很麻烦的问题。对于程序执行opcode清除之后,又需要再次加载,这样是个非常复杂繁琐的过程,也容易出问题。 自力更生: 可否在fpm启动时,自动加载bin的功能呢?末学阅读了APC的源码,照葫芦画瓢的添加了自动加载的opcode bin文件的功能。 我们是在 APC SVN http://svn.php.net/viewvc/pecl/apc/trunk/ 的 r327454版本基础上,在apc_module_init函数中,模块初始化时,增加了opcode bin文件的自动加载功能,patch代码见:https://github.com/cfc4n/cnxct/blob/master/apc_r327454_add_preload_binfile.patch 。有兴趣的朋友可以git使用下。当然,末学水平有限,难免存在BUG,还请见谅。 后来,末学将此patch提交到PHP官方,请他们审核一下,是否可以收入apc官方中,免得我们以后都一直作为patch来使用。或者协助完善,或者给出更好的解决办法。 很伤心,未被采纳,原因是这是小众的需求。但末学表示不解,那apc_preload_path这个自动加载user cache的功能不也是小众需求吗?或许,这是他们委婉的拒绝方式。 (同时,还发现了APC自带的一个未公布的功能,自动加载user cache的功能,配置项为apc.preload_path,配置的值是目录地址,目录下可有多个文件,这些文件也应该是apc_bin_dump/apc_bin_dumpfile函数导出的user cache。看来,官方也有这么个打算。但为何没有实现 opcode bin文件 的自动加载呢?) 如何使用: opcode bin文件导出与导入: 导出的php脚本,末学提供一份,需要的朋友,可以参照修改一下即可: https://github.com/cfc4n/cnxct/blob/master/apc_dump.php 导出成功后,会在PROJECTROOT目录下面生成一个dumproot目录,dumproot目录中的所有文件,就是你需要上传到生产服务器上的文件。bin目录中,会生成两个文件,一个是opcode的bin文件,一个是生成之前的php文件的md5 hash值。其他目录就是项目被保护的目录,目录中文件都是空的php文件。将dumproot目录上传至生产服务器,保持项目路径正确,重启fpm即可。可通过apc拓展源码包内的apc.php查看被缓存文件数。如图: 版本更新、回滚: 遇到bug需要修复时,只需要在opcode bin文件导出服务器上,重新导出一个bin文件,将此bin文件上传到生产服务器,替换到之前的bin文件即可。偶尔会遇到项目因种种原因,需要对项目进行回滚,如果使用我们的方案之后,会发现所有php文件都已经是空的了。而我们的回滚更方便,只需要将之前需要回滚的版本的bin文件,替换到当前的文件即可。或者更改php.ini中apc.preload_binfile的路径。 版本检测 当怀疑个别文件不是某个版本的程序是,怎么办呢?apc提供了另外一个参数 apc.file_md5来存储被cache文件的md5 hash。但当使用已到处的opcode bin文件时,apc却没有重新抓去md5 hash,也没有重新存入apc中,导致我们看到的md5值是个错误的hash值。为此,末学斗胆做了修改,并提供了patch,且提交该BUG #63491到pecl apc官方,斗胆请官方采纳。当打上该patch之后,您可以在apc.php 看到这些信息。并且,跟导出bin文件时,生成的md5 hash文件中校对以确认哦。(2012/12/17 更新,官方已修复) 批量检测也是可以的,参加末学另一个脚本:https://github.com/cfc4n/cnxct/blob/master/check.php APC其他已知bug: 一、php5.4.x中,class中的array()静态成员属性在跨服务器使用时,产生core,已经提交到 BUG #63636(此bug由recoye发现) 临时解决方案如下: 使用php5.4.x时,不要在class中定义 $_user = array(),直接定义为 $_user; 不要使用php5.4.x 删除或注释apc_compile.c的1992、2003行(apc 3.1.13),即 zval_ptr_dtor(&src->default_static_members_table[i]); 字样的代码。 等待PHP官方解决,或微博@鸟哥,催鸟哥解决 二、web访问apc_dump.php 去生成的bin文件,在cli模式下使用时,会在RSHUTDOWN时,即请求结束时,回收系统资源时,会产生core。(该结果的php版本为php5.3.x,初步确认为上一个bug有密切关系,暂未提交至bugs.php.net) 这两个bug以末学的理解来看,与末学的patch无关,为apc的bug。 注意事项: 导出bin文件的服务器上,不要开启 apc.preload_binfile ,避免bin文件存在时,使用bin文件内的代码,而不使用php文件内的代码,导致导出的代码不是你想要的。 php5.4.x class中array 静态成员以及属性 操作系统32bit、64bit的导出的bin文件不能相互乱用。(乱用将产生core) 导出服务器的web路径跟目标生产服务器的web路径一致 生产服务器使用bin文件之后,必须存在同名的php文件,内容可以为空。 apc.ttl、apc.gc_ttl参数保持为0 apc.serializer 如果改用其他序列化函数发生问题,那么请保持为空,或者为 apc.serializer=php apc.num_files_hint的值务必大于等于cache files 的数量,可以先设置大点,再通过生产服务器上观察一段时间来确认 啰嗦一下: 关于32bit\64bit服务器上导出opcode bin文件的乱用问题,将产生如下core 因为long之类类型,在不同bit操作系统上的长度不一致导致内存越界的BUG。 末学不知道这算不算bug,或者您别混用,或者APC程序上也可以解决... 惊喜: 不光可以代码保护,性能提升,方便运维,还可以防黑客入侵哦。(项目目录不允许创建新文件,那么即使黑客改了php脚本,php仍会去apc里读,也不会去读取它。哪怕黑客执行了 apc_cache_clean清除opcode cache,我们的补丁仍会自动加载,那么黑客的行为还是阻拦了。) 备注: 此patch已经在我们的“友好邻邦”服务器上稳定运行3-4个月左右,无异常案例,各位可放心使用。 Apc不算一个php encoder,算是个cache,一个opcode cache,起到中间码缓存的作用,故本文中用“代码保护”,而不是“代码加密”。 但实质上实现了我们的目的,起到保护源代码的作用,那怕可以逆向,也是有一定难度的。 PS:感谢Ivon的openstack,感谢终极修炼师的协助测试。 PPS: 如果你能保证php文件不会被SAPI形式访问到的话,只会被include/require等访问到的话,甚至连同名空文件都不用放。比如单入口框架的项目,只要放个空的入口文件即可。 (感谢recoye纠正) PPPS: 更新日期:2014/10/17 近来,好多朋友问我要完整版,鉴于apc已经被官方删了,那个SVN地址无法正常checkout,故我在硬盘里找了个压缩包,应该是可用的版本,如果有其他异常,请留言,谢谢。 适用于PHP5.3的完整拓展:点击这里下载包含patch的完整版apc 2012-11-29 +39 43 0
CodeIgniter框架中关于DB事务处理的设计缺陷 起因: 在我们线上的某个业务中,使用较老版本的CodeIgniter框架,其中的DB类中,对DB事物处理部分存在着一个设计上的缺陷,或许也算不上缺陷吧。但他却影响了我们生产环境,导致连锁反应。对业务产生较大影响,且不容易排查。这个问题,我在今年的3月中旬,曾向http://codeigniter.org.cn/的站长Hex 报告过,之后,我也忘记这件事情了。直到今天,我们线上业务又一次以为这个问题,害的我又排查一次。具体原因,各位且先听我慢慢说完。(这个问题同样存在于最新版本Version 2.1.0中) 分析: 以CodeIgniter框架Version 2.1.0为例,在system\database\DB_driver.php的CI_DB_driver类中第58行有个$_trans_status属性。 同时,这个类的query方法中,有赋值此属性的代码,见文件306、307行 这里也给了注释,告诉我们,如果使用了事物处理,那么这属性将成为一个回滚的决定条件。 在520行的事物提交方法trans_complete中,如下代码 在535行中,如果_trans_status属性如果是false,那么将发生回滚,并且返回false。 在我们的业务代码中,由于程序员疏忽,没有判断trans_complete()方法是否正确执行,直接告诉用户操作成功,但实际上,程序已经向DB下达回滚指令,并未成功更新DB记录。当用户执行下一步操作时,程序又发现相应记录并未更新,又提醒用户上个操作没有完成,通知用户重新执行。如此反复... 排查的过程,也是挺有意思的,起初从PHP代码中,总是不能确定问题所在,并没有把焦点放到trans_complete()方法的返回上。直到后来strace抓包分析,才知道是因为此属性而导致了回滚。 可以看到,在22:54:08.380085时间点处,发送更新SQL语句指令,在22:54:08.380089时间读取返回结果,得到SQL执行错误,不存在字段"cfc4n_user_lock";22:54:08.381791和22:54:08.382703两个时间点,PHP发送停止“自动提交”与“开始事务处理”指令,在 22:54:08.433954 发送“事务回滚”指令。 配合如上的代码分析,可以清楚的知道,因为“UPDATE `cfc4n_user_info` SET `cfc4n_user_lock` = 1 WHERE `cfc4n_user_id` = '6154' AND `cfc4n_user_lock` = 0”这句SQL执行错误,导致$_trans_status属性被设置为FALSE,当代码提交事务时,被trans_complete()方法判断,认为“上一个事务处理”(下面将仔细分析)中存在SQL语句执行失败,决定回滚事务,不提交。 刚刚提到“上一个事务处理”,可能有些朋友不能理解,我们先继续回到代码中来,继续看该属性,同样在trans_complete方法中,542-545行: 也可以很容易的从注释中看明白,设置CI的设计者,为了更严谨的处理 同一个脚本中,存在多个事务时,事务间彼此关系重要,一荣俱荣,一损俱损。这里的trans_strict属性,是个开关,当 trans_strict为false,便是非严格模式,意味着多个事务之间,彼此关系不重要,不影响。当前一个事务中有SQL语句执行失败,影响不到自己。便将_trans_status 设置为TRUE。 毫无疑问,这是个非常周全的考虑。考虑了多个事务之间的关系,保证业务跑在更严谨的代码上。 可是,我们的代码中,错误的SQL语句是执行在事务处理以外的,并不是事务之内。按照我们对事务的认识,可以很清晰的知道,事务之外的SQL相比事务之内的SQL来说,事务之内的SQL更重要,之外的可以允许出错,但事务之内的,务必要正确,不受外界干扰。但CI的框架中,因为一个事务以外的语句执行失败,却导致整个事务回滚...当然,我们的程序员没有对事务提交方法的返回做判断,这也是个问题。 问题已经很清晰了,那么解决方法想必对你来说,是多么的简单。 比如在trans_start方法中,对_trans_status 属性赋值,设置为TRUE,不理会事务外的问题。 结束: 在不明白对方设计意图的情况下,不能盲目的定义对方的代码评价,不管程序作者的水平如何。比自己强,也不能盲目崇拜;比自己弱,更不能乱加指责;理解读懂设计意图,学习他人优秀的设计思路、代码风格、算法效率,这才是一个好习惯。当然codeigniter框架是优秀的。 2012-04-30 +3 7 0
php5.3.8中编译pdo_mysql的艰难历程 我们的一个项目,用了PDO_MYSQL拓展,准备迁移服务器,新环境需要编译安装环境。昨天,运维同事抽空编译了一下,一直编译不上pdo_mysql,同时,公司的一款新webgame临近上线,他们实在太忙,我这个三流运维技术的程序员来试试吧。 运维同事描述: 服务器系统版本:Linux version 2.6.32-71.el6.x86_64 (mockbuild@c6b6.centos.org) (gcc version 4.4.4 20100726 (Red Hat 4.4.4-13) (GCC) ) #1 SMP Fri May 20 03:51:51 BST 2011 编译PHP的参数 之后再次编译 pdo_mysql 拓展 之后, shell里执行 php -i 和 php -m都没有看到 pdo_mysql拓展。搜pdo_mysql,在将路径添加到php.ini中,仍找不到这个扩展,判断为编译失败。 这里是将pdo_mysql作为一个拓展引入使用的。在php5.3中,PHP开发组把mysqlnd作为默认的连接MYSQL的数据库驱动来使用,据官方描述,节省内存40%,速度更快,当然或许是为了解决许可协议的问题。之前PHP连接MYSQL,是调用MYSQL官方提供的C/C++编写的lib_mysql的dll/so,来实现。这个类库同样可以给PYTHON等脚本语言调用,只要按照API规范来。我们改用mysqlnd之后,就不用再为了lib_mysql去安装mysql client了。详情见:mysqlnd插件mysqlnd_ms的介绍。 两种方法都可以,运维同事都尝试了,由于时间关系,他们没做过多的尝试研究,就转向更紧急的项目了。 运维同事下载使用的PDO_MYSQL拓展的地址是 http://pecl.php.net/package/PDO_MYSQL ,里面用很耀眼的颜色,标注如下几行字 This package is not maintained anymore and has been superseded. Package has moved to channel http://svn.php.net/viewvc/php/php-src/trunk/ext/pdo_mysql/, package ext/pdo_mysql. 也就是说,早在2006年5月1(我是根据最后一个打包文件日期猜的,或许不准)之后,PHP已经将这个pdo拓展放到PHP源码的 ext/pdo_mysql下内置了。这里的这个包,将不会在更新维护了。 在PHP官方文档上对pdo_mysql使用mysqlnd的时候,是这么描述的 在php5.3中,已经支持mysqlnd作为数据库连接驱动了,无需再使用libmysql。而在将来的php5.4中,将变为默认的连接驱动。如图: 开启这个类库的 之后,我的编译参数如下 区别是使用php内置的pdo_mysql类库,使用mysqlnd作为连接驱动,不在使用之前的MYSQL官方提供的libmysql.so。 make之后,提示如下错误 这种错误,让我手足无措,GOOGLE搜了下,也没找到相关案例,而且,一直被墙,打不开国外网页。百度搜的结果,还是算了。(中文还行,E文的话,百度确实不行,尤其是程序相关) 之后,尝试make clean,清除之前的编译结果缓存之类。 再次make,有个小意外 再次搜索,这倒是很多网友遇到过,大部分的建议就是无视这个错误,不影响编译。照做。make install ,一路挺顺畅。 接着,php -i / php -m 也没发现pdo_mysql模块。 郁闷无比,决定看下web下的phpinfo结果,发现居然有了。问题终于解决了。。。万岁。。 可是!!! 1,为啥php -i 、php -m 的结果里看不到呢? 思考,为什么呢? 结合刚刚的报错,联想到CLI 模式下的php 脚本(以及相关的php.ini)跟刚刚从web下访问的php程序不是同一个。检查环境变量,以及切换到编译好的目录下执行php -i,发现pdo_mysql、mysqlnd等相关添加的模块了。 2,php.ini里没启用pdo_mysql拓展,为什么还能看的到呢? 这次使用的是PHP内置的类库,不是以新拓展方式加载运行的,所以,不用更改php.ini,再添加相关so路径。 综上所述,文章没有高深的东西,只是有几个需要细心的点。 1,以官方文档为准,一切跟着官方文档来,不轻易采信网络上网友提供的编译参数,包括这边博文。不论对方是老手、大牛,还是其他什么什么有威望的人。他们提供的方法或许跟你当前的环境不一致,时间也相差很大,或许相隔好几年了。 2,确认得到的结果是准确的,怎么说呢,文中的例子中php-i的路径不是我们新编译的,而是之前编译,或者yum安装的,一定要到自己编译的程序目录下,用自己新编译的脚本去执行测试,获得测试结果,下结论,不为了偷懒,不敲路径,直接写程序名进行测试。 3,遇到诡异的错误,我总会想要一个全新的系统,进行安装,以确保不被各种冗余、缓存等垃圾文件干扰。例子中用了make clean进行清除相关缓存,来解决文件缓存问题。(感谢@ivon_lee 的帮助) 4,自动安装脚本要及时更新,当然,不是意味着追求最新版本。例子中的pdo_mysql的拓展,官方提供了更好的方式,不论是效率,资源占用,都有更好的提升,为啥不使用呢。 备注:mysqlnd 的相关有点对比见http://developer.51cto.com/art/200903/115995.htm PS:文中出现的我搜索无果两次错误,如果有人知道,请告诉我,谢谢。 2011-11-25 4 0
WEB开发安全与运维安全浅见 前段时间,同事LeeQueen为公司同事做了个《安全意识防护》的PPT,效果很好,各位新同事也知道日常公工作中,不太注意到的隐私保护问题。PPT中也提到了一些社会工程学的案例,提到了某团购网站前台人员被声称是新开礼品公司的工作人员,以填写信息就送礼品为由,获取到此团购网站的所有员工联系方式。后此团购网站的高层打电话回访,得知对方是猎头公司[详情见乌云网的链接]。此案例非常典型,讨论也尤为激烈,可谓是非常成功的培训。 此次培训之后,职业欠钱[sina微博][腾讯微博]让我给我们开发部门的PHP程序员同事们做一次PHP开发、运维时候一些安全注意事项。由于我在安全这块资历较浅,没有经验,我了解的大家都懂,这个话题太高深,我的水平完全不够,没法写这个PPT。而且,同事里有好几位安全界的前辈,我怎可关公面前耍大刀呢。上次写过一次关于正则表达式PPT,也就是一个月之前,深刻的体会到写PPT的痛苦,尤其是排版。实在不想写了。 后来,经常看到网上的安全工程师提到程序员安全意识较差,以至于同样的危险代码,危险程序,却屡次不改。当然,不是程序员不想改,而是程序员不太理解代码形成安全漏洞的原因。为此,我也觉得有必要提高一下我们程序员的安全意识了。 PPT中写了网络上常见的漏洞,形成原因,漏洞原理,防护建议等;也列举了近年来跟WEB相关的安全漏洞,以程序员的视角理解漏洞原理,并给出修复建议。这些建议仅供参考,不是正确答案。 由于本人水平原因,PPT中难免有错误,请见谅。也请安全界工程师指正。 借用网友的一句话,作为程序员,尽量要做到“知其然,知其所以然”,多多关注web安全,不要给安全工程师添麻烦,不能丢了程序员的脸。 同时,公司招聘PHP程序员,地点上海,公司规模为150人+,做网络游戏研发、运营。招聘条件以及待遇之类的,可以通过以下方式联系我,我们私聊。 http://weibo.com/cfc4nx http://t.qq.com/cfc4nx 邮箱:cfc4n@博客域名点COM 最后,PPT是周末两天写的,这两天老婆挺着6个月的大肚子,为我做饭,老婆实在是太辛苦了,感谢我老婆。 PS:讨厌写PPT[流泪]。 在线阅读: Web开发与运维安全浅见View more presentations from cfc4n. 下载到本地: PPT下载:web开发与运维安全浅见PPT下载,猛点这里 更多亮点在PPT的备注里,各位一定要看备注。转载的朋友麻烦保留PPT完整,包括招人信息。尤其是备注要保留。 ------- PPT在2011/09/19 更新了,更换匹配NGINX 对URI中PHP脚本的正则,考虑到了拓展名为 .phpa .phpb .phpc之类的情况。主要是更换了正则匹配的图,更改了正则内容。 2015/06/09 备注, nginx匹配URI时,对于PHP的解析,更改为 location ~ ^(?:(? 2011-09-19 +13 17 0
编译xhprof时的一个小意外 新项目基本开发完毕,剩余收尾工作。趁美工调整新UI的时间,赶紧在测试机上安装PHP的性能检测利器xhprof。 下载地址http://pecl.php.net/package/xhprof,这里有tar包,我下了http://pecl.php.net/get/xhprof-0.9.2.tgz, 之后,遍完成了。一路很“顺利”。 php.ini如下设置 代码中,程序头部如下设置: 程序最下面: 之后遍看到统计数据结果了。 可是,当我查看[View Full Callgraph]的时候,却提示如下:Error: either we can not find profile data for run_id 4d7f0bd99a12f or the threshold 0.01 is too small or you do not have 'dot' image generation utility installed.,这TMD神马玩意?是什么错呢? 关于dot的介绍,xhprof在这里写出来了: http://mirror.facebook.net/facebook/xhprof/doc.html dot (image generation utility): The callgraph image visualization ([View Callgraph]) feature relies on the presence of Graphviz "dot" utility in your path. "dot" is a utility to draw/generate an image for a directed graph. GOOGLE了半天,也没找到可用的信息。问了下群里的同学,大牛老王告诉俺,xhprof绘制的是png图,系统(graphviz-2.24.0)不支持。才知道绘图的dot拓展没装成功。我的操作系统是UBUNTU 10.10 SERVER版的,也就是dot 不支持PNG。赶紧再次编译下graphviz,看看提示信息有什么缺少的。果然: png: No (missing png.h)果然。。。。 赶紧到libpng官网down分源码,再次编译一下。 SF.NET上地址是 http://sourceforge.net/projects/libpng/files/libpng15/1.5.1/,我下的是http://sourceforge.net/projects/libpng/files/libpng15/1.5.1/libpng-1.5.1.tar.gz/download ,一气呵成,很顺利。 再次编译graphviz的时候,提示如下: 好了,内牛满面。之后就是 make&make install了。 当打开[View Full Callgraph]的时候,果然是性感的资源占用统计图了。 展示下效果图: 优化前: 找到问题所在,稍微调试: 最终代码优化结果: 2011-03-15 +2 6 0
广发英雄帖-招聘广告(DBA、PHP开发、运维工程师、BI专员) 首先,我已经改行了,因此这个招聘和安全无关,不是招安全工程师的。 其次,我现在所在的团队,是一个比较年轻的团队,无论是同事的平均年龄,还是公司的成立时间,或者是公司的收入状况。 所以,如果你现在觉得自己充满激情,渴望学习和成长,耐得住性子,能做好在小公司待2年以上的准备,不太在乎加班多(不一定有加班费),不太计较眼前得失,希望和公司一起成长,渴望自己的努力在公司的成功道路上占据重要的比重(相对于大公司比较细分化的工作岗位),并且有志于往下列岗位发展,欢迎投递简历至 cfc4n # cnxct {dot} com ,最好来信注明薪资期望 公司性质:网游 规模: 100人左右 工作地点:上海 待遇:根据个人能力而定,本次招聘主要面向中、基层岗位 除BI专员外,其余岗位无学历限制 职位:运维工程师 招聘人数:3-4人 职位描述: 1. 负责生产网络、服务器、数据库与支撑系统的建设、运维和监控,保证各业务系统正常运营; 2. 负责突发性事件的快速响应和处理,解决服务器和网络故障; 3. 研究运维相关技术,制定运维技术方案、部署统一化、集中化的运维系统和工具; 4. 负责对现有运维系统的性能改善。 职位要求: 1. 计算机或相关理工科专业专科或以上学历,为人真诚正直; 2. 熟悉Linux操作系统,熟悉Linux脚本编程; 3. 熟悉Apache、nginx、Mysql等应用的配置与维护; 4. 熟悉防火墙,交换机等网络设备的安装、配置; 5. 2年以上相关工作经验,深入理解Linux系统的和部署,有系统调优经验者优先; 6. 具备良好的沟通能力和强烈的责任心,热爱运维技术,有良好的文字表达能力和自学能力,有良好的团队合作意识。 职位:PHP开发工程师 招聘人数:3人 职位描述: 负责如下系统的设计,编码,测试,文档记录,开发优化维护等: 1. passport中心 2. 支付平台 3. 论坛整合插件(discuz!) 4. 在线活动项目 5. 资产管理系统 6. 广告统计系统 7. 服务器监控系统 8. 周边产品的 职位要求: 1. 精通PHP5开发(2年以上工作经验,有WebGame或者社区开发经验优先) 2. 扎实的编程基础。代码构造清晰,易读。擅长代码和算法优化。 3. 熟悉MySql数据库设计,了解Memcached优化。 4. 了解PHP与FLASH(或者其它非浏览器客户端)的常用通信方式。(如AMFPHP)了解HTTP协议。 5. 熟悉javascript,善用ajax等技术,熟悉Jason数据格式。 6. 熟悉各浏览器的安全机制上的差异,能够解决兼容性问题,如:iframe,cookies,ajax。 7. 熟悉PHP(APC之类)缓存技术,对异步机制下的状态维持、更新有一定的了解 8. 熟悉SVN等版本控制开发环境。 职位:数据库管理员/DBA 招聘人数:1-2人 职位描述: 1. 负责公司游戏产品、支撑平台的数据库日常维护和监控; 2. 负责数据库的更新和运营相关数据的统计分析。 职位要求: 1. 熟悉MySQL,Oracle的体系结构,复制,高可用,监控和备份机制; 2. 具备较强的数据库故障解决能力; 3. 至少熟悉Shell或Python脚本语言之一; 4. 熟练使用PL/SQL,对大数据量下的数据处理有一定经验,有PL/SQL, SQL调优经验者优先; 5. 熟悉数据仓库的ETL开发和数据建模优先; 6. 能阅读各类英语文档。 职位:BI&UCD专员 招聘人数:1 职位描述: 1. 辅助运营流程建设; 2. 辅助运营数据统计、分析、挖掘; 3. 参与实施BE/UCD管理及团队建设; 4. 数据化衡量运营质量、提供决策参考依据; 职位要求: 1. 本科学历是必须条件,计算机及相关专业优先; 2. 学习能力强,具有较强的沟通与领悟能力; 3. 具备自我驱动能力,抗压能力强,服务意识及团队合作精神佳; 说明:招聘详情是我直接从我同事BLOG上复制过来的,只改了邮箱地址,别的都照搬。文中头部描述也是我的现状,我也不做安全了,回归到开发上来了。 2010-11-12 +3 7 0