黄良懿的个人博客

总有一些美好,期待着我们去发现
RSS 图标 Email 图标 首页图标
  • 关于PHP性能的那些事

    发表于 2010年08月5日 黄良懿 2 条评论

    PHP之父Rasmus Lerdorf昨日刚发布了一个叫做《PHP Performance》的Online PPT,深入浅出,简单直白,以WordPress为例详细解说了优化的方法和相关的工具(strace, Callgrind, Xdebug, xhprof等),最后总结中有两点让我深有同感:一是说性能实际上是灵活性和成本之间的权衡取舍,另外一个则是真正的去看待性能问题,应该是面向架构(Real performance is architecture-driven)。

    我自己用过好几种编程语言进行企业项目开发,一直以为,对语言的选择应该基于应用场景和业务需求等,与是不是真的OO,性能高不高,代码结构是不是够敏捷等其实没有太多关系。比如频繁更新页面的推广活动,那自然是PHP/ASP之流合适,电子商务等需要事务、安全、稳定的Web应用使用Java,有较多自定义行为(略有差异于系统自身实现)的桌面应用使用VC等等。 而语言的性能差异,往往并不是我们所需要去关心的。 印度人做项目时满足用户性能指标的方式往往是告诉用户什么配置的机器能达到这个指标。这是因为重构算法让代码的性能翻倍往往远不如将CPU换成多核,内存加个几G来得简单、快捷和廉价。

    应该说,PHP从性能上来说是有一些先天劣势的(事实上PHP在语言性能排行榜当中确实不太靠前),每次运行必须重新编译就是一个巨大的成本,从Rasmus的第一个优化就是安装APC就可以看出来这一点。嗯,是的,十分庆幸,我们有APCeAcceleratorXCache等优秀的op-code缓存帮助我们解决了这个PHP最大的性能问题。紧跟其后的一个重要性能问题是由OOP引入的,事实上PHP 5直到PHP 5.2才得到了广泛的应用,因为PHP 5.2在面向对象的开发方面性能大为提高,已经逼近了函数式编程的性能水准。但时至今日,也依然有一些性能障碍难以解决,比如说由于动态语言的特性,PHP即使有op-code cache,也需要在每次请求的时候重新建立常量列表、可调用函数列表、可调用类列表。 这对于大型企业项目来说,简直是个噩梦。而PHP变量对内存利用的低下也是一个进行大数据量处理时会碰到的严肃问题,资深的PHPer能通过pack指令来解决它,但这真的很不优雅,充满着无奈。(你知道一个100万个数字的数组在PHP中占用多少个字节吗?如果是同样数据量的二维数组呢? 查过这个数字的Java程序员和C/C++程序员都用一种怜悯的眼神看着PHPer)

    但,世界上最大的SNS网站,正在挑战Google的Facebook正是用的PHP!而他们能用PHP做到这样的规模,恰恰就是和“Real performance is architecture-driven”相互呼应的“Languages's don't Scale, Architecture Scale”。所以说,“PHP is rarely the bottleneck”,性能的关键在于,你的项目是怎么架构的。


  • PHP 5.3.3发布,内置FPM

    发表于 2010年07月28日 黄良懿 没有评论

    上周 php 官方发布了5.3.3和5.2.14,除了循例的多个bug fix外,一大亮点就是内置了之前我曾在《改用php-fpm+eAcclerator替代spawn-fcgi+xcache跑wordpress》中提到的FPM,这就是说以后再也无需去找合适的版本来patch了。

    相应的,php-fpm的安装、启动方式和设置方法都会有所区别,主要是改用信号来完成php-fpm {reload|stop|restart|start}等操作,以及改用ini的方式设置FPM而不是之前的XML。

    已经有同学写出了配置、使用方法(《php 5.3.3中的php-fpm》),这里直接摘录一下:

    php 5.3.3 源码中开始包含 php-fpm,不用专门再打补丁了,只需要解开源码直接configure,关于php-fpm的编译参数有 --enable-fpm --with-fpm-user=www --with-fpm-group=www --with-libevent-dir=libevent位置。

    这个php-fpm 不再支持 php-fpm 补丁具有的 /usr/local/php/sbin/php-fpm (start|stop|reload)等命令,需要使用信号控制:

    master进程可以理解以下信号

    SIGINT, SIGTERM 立刻终止
    SIGQUIT 平滑终止
    SIGUSR1 重新打开日志文件
    SIGUSR2 平滑重载所有worker进程并重新载入配置和二进制模块

    示例:
    php-fpm 关闭:
    kill -SIGINT `cat /usr/local/php/var/run/php-fpm.pid`
    php-fpm 重启:
    kill -SIGUSR2 `cat /usr/local/php/var/run/php-fpm.pid`

    其次配置文件不再使用的xml 格式,改为了INI,但是配置参数几乎和以前一样,可参照xml格式的格式配置。


  • 修改PHP源代码解决Nginx下WebShell的问题

    发表于 2010年07月22日 黄良懿 没有评论

    Nginx / Lighttpd + PHP FastCGI的方式正在被越来越多的网站应用,其中让需要虚拟主机支持的用户最烦心的一件事情莫过于站点权限隔离。 目前无论是spawn-cgi或者是php-fpm的方式,都无法动态转变执行用户。尽管可以通过给不同网站以不同的用户身份执行FastCGI,但这也同样失去了FastCGI统一管理的优势,需要为每个网站保留足够的处理进程而不是整体规划。

    Google搜之有两个比较广为流传的方法,其中最完美的莫过于直接修改PHP源代码,对打开目录进行鉴权(搜出来的资料最早是anxsoft.com提供的代码)。 方法是在php源代码目录中执行vi main/fopen_wrappers.c,并找到php_check_open_basedir_ex方法,在char *end;和pathbuf = estrdup(PG(open_basedir));之间插入以下的代码:

            char path_copy[MAXPATHLEN];
            int path_len;
            path_len = strlen(path);
            if (path_len >= MAXPATHLEN) {
                errno = EPERM;
                return -1;
            }
            if (path_len > 0 && path[path_len-1] == PHP_DIR_SEPARATOR) {
                memcpy(path_copy, path, path_len+1);
                while (path_len > 1 && path_copy[path_len-1] == PHP_DIR_SEPARATOR) path_len--;
                path_copy[path_len] = '\0';
                path = (const char *)&path_copy;
            }
    
            char *env_doc_root;
            if (PG(doc_root)) {
                env_doc_root = estrdup(PG(doc_root));
            } else {
                env_doc_root = sapi_getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT")-1 TSRMLS_CC);
            }
            if (env_doc_root) {
                int res_root = php_check_specific_open_basedir(env_doc_root, path TSRMLS_CC);
                efree(env_doc_root);
                if (res_root == 0) {
                    return 0;
                }
                if (res_root == -2) {
                    errno = EPERM;
                    return -1;
                }
            }
     

    以上是所有能找到的资料里代码最长也是考虑最完整的代码。 前段是用于去除传入的路径参数中最后的多个斜杠(/)对代码判断的影响。后段则是取得当前站点的文档根目录,并检查要打开的文件是否存在于这个目录下、是否有权限等。

    编译后测试发现确实的解决了WebShell对同级目录的跨站访问。但运行某个基于Zend Framework的项目时则遇到了阻碍,无法读取application目录下的config.ini。原因是root目录位于application的同级目录html下。 其他一些项目也可能会有类似需求,需要访问root同级的upload或其他不开放的目录。这个需求很容易就能解决,参考检查DOCUMENT_ROOT的方式,优先检查另外一个SITE_ROOT的环境变量是否存在且有权,并在nginx的php fastcgi配置里加上一行即可:

    fastcgi_param  SITE_ROOT /web/zend.hly1980.cn/;

    注:该网站的root为/web/zend.hly1980.cn/html。


  • 初为人父

    发表于 2010年07月21日 黄良懿 2 条评论

    7月15日下午,喜得麟儿,至今不过区区一周。 然则,狂喜的兴奋、甜蜜的温馨、时不时的憨笑、静静看着宝宝时油然而生的责任感等等陆续体验了个遍。 放上几张孩子的照片,留做存记:

    阅读全文 »


  • 也说海量数据的快速排序

    发表于 2010年07月11日 黄良懿 没有评论

    年初的时候就有同事面完以后回来和我聊过,5000万无重复32位无符号整数中取出10000个数字的时间+空间最优排序方法,以及存在重复数字时的最优方法。 今天正好有空,把当时考虑的解决思路写下来:

    首先肯定是空间换时间,方法基于位运算创建一个大内存区,单次接受数据,每个数字设置其所属的bit,随后遍历输出。这样子的话所需要的内存空间是32-3=29位,也就是512M。这个算法时间性能是足够保证了,毕竟无迭代,空间通过分块的方式来降低是一个思路,但4亿数据(32位)中有5000万,稀疏的程度远不足够,只能作罢。 这个方案的好处是即使进行全排序,其时间性能和空间性能也不会发生改变。 空间最优则应该是通过维护一个红黑树,先丢10000个数字进去,然后遍历剩下的数,从红黑树中汰选最小值替换之即可,遍历红黑树进行汰选前先比较预存的树中最小数在正常分布下能有效减少性能损失。

    重复数字的简单解决方法是增加一次遍历,首次遍历将需要取出的10000个数字汇总后构建哈希表,再次遍历时仅只计算哈希表中元素的出现次数,最后按照10000个数字的次序遍历,并累计哈希表中的计数至10000次即可。 空间最优方案依然还是选择红黑树。

    从综合考虑上来说当然是红黑树的实现在时间和空间上都有不俗的表现,但实际应用起来还是位运算靠谱,毕竟再怎么高效查找也好,碰上一个悲观分布(数据本来就是有序的且和要求输出序同向),那就是5000万次树遍历了。而位运算的空间占用是恒定的,时间则和总数据量等比,非常稳定。

    当然,如果是有重复,全排序,基本上也只能考虑MapReduce了。


  • 尝试用Windows Live Writer发文

    发表于 2010年03月23日 黄良懿 4 条评论

    听说Live Writer的名头已久,正好今天在PC上重装MSN,顺便试了一下,确实挺不错,可以直接在客户端编辑发文不用再跟在线编辑器死磕,文章分类和标签这两个最重要的属性也可以直接设置,没写完的保存本地草稿或远程草稿都可以。

    配置起来非常简单,现在WordPress后台的“撰写选项”中启用XML-RPC发布功能,然后直接开启Live Writer,选择其他日志类型,并输入WordPress首页地址、用户名、密码即可。

    Word一脉相传的编辑方式,直接拖拽添加图片、链接等,从Excel里直接粘贴表格等等都是熟悉而亲切的使用习惯,下面是来自Excel的表格和直接拖拽进来的图片:

    Name Age Credits
    Richie 29 12
    Tom 22 7

    Blue hills

    这里有一个直接从IE里拖过来的链接,猛击进入Live Writer的扩展插件:扩展程序,不过太少了点。


  • 改用php-fpm+eAcclerator替代spawn-fcgi+xcache跑wordpress

    发表于 2010年03月16日 黄良懿 没有评论

    VPS上一直用的是Nginx + PHP FastCGI,其中FastCGI是用Lighttpd的spawn-fcgi来做管理,稳定性上面倒没什么可挑剔的,一直很正常,就是有一点很不好,三到五天就会出现一次PHP把内存吃光的情况。 VPS是384M的内存,1G+的交换文件,理论上5个Nginx进程加上8个PHP FastCGI是不该超出的,但连虚拟内存都吃光的情况还真出现过。 每次都是ssh连上以后盲敲指令killall php-cgi解决。碰了两三次后索性写了段shell到crontab里缓解此问题,具体作用是每小时的13分和43分检查并杀掉内存占用过多的处于休眠状态的PHP FastCGI进程:

    13,43 * * * * ps aux|grep php-cgi|awk '(($5>150000||$6>60000)&&$8=="S"){print $2}'|xargs kill -9

    查了php.ini中内存限制的配置和xcache的相关配置,算下来的最大内存总占用应该是在500m内的,这还是因为XCache未能实现opcode的共享存储,导致重复占用的缘故。 这里要特别提一下,这个问题是因为内存地址映射关系在多进程中的复杂性所造成的,XCache和APC都没解决,最近刚发现最新版本的eAcclerator已解决了此问题,这次也一起更换了opcode cache模块。

    一开始怀疑是PHP自身的内存管理问题,但一来公司的mod_php同版本代码并未出现该问题,另外同VPS上另一个用户所启动的PHP FastCGI进程却也并未出现该问题。 经粗略的排查,发现当大量使用WordPress中后台的各类功能后内存占用会急剧狂飙,但未安装任何插件的干净的WordPress则无此问题。 由于机器上不止一份WordPress实例,而且插件众多,难以一一排查,只好从PHP自身来考虑解决此问题。

    首先尝试的是将PHP替换为5.2.13和5.3.2分别测试过,不过问题依旧。倒是用5.2.13换了php-fpm来启动后解决了该问题。
    安装方法很简单,在php源码目录下执行以下指令:

    wget http://php-fpm.org/downloads/php-5.2.13-fpm-0.5.13.diff.gz
    gzip -dc php-5.2.13-fpm-0.5.13.diff.gz | patch -p1

    随后在原来的配置参数后面加上--enable-fpm重新make && make install就可以了。

     

    更新php后也用eAccelerator替换掉了XCache,随后查看运行状况,问题确实得到了解决,虽然不确定到底是由于php-fpm还是eAccelerator,但可以确定的是eAccelerator在FastCGI的模式下,其opcode cache确实可以通过shm共享。

    更换php-fpm带来的也不仅仅是这样的好处,当你升级php或者是更改php.ini时,它可以平滑的关闭老进程并启动新进程使服务持续可用,此外还可以根据目前的服务压力,动态的增加或者减少PHP FastCGI进程的数量。 更多的信息可以猛击这里(php-fpm文档中文翻译),英文好的同学则建议猛击这里查看原文

     

    使用过程中多次刷新查看phpinfo,并利用空延迟脚本使执行落在不同的php-cgi进程上,证实其缓存确实是放在共享内存中。

    附上一张eAccelerator的使用情况截图:

    eaccelerator

     


  • 解决Zend Optimizer无法加载及与eAccelerator的冲突

    发表于 2010年03月16日 黄良懿 没有评论

    在VPS上下载了3.3.9的Zend Optimizer,找说明安装后出现错误:

    cannot restore segment prot after reloc: Permission denied

    找了下,问题是出在SELinux上,关闭SELinux即可解决:

    1. 修改/etc/sysconfig/selinux,修改为SELINUX=disabled
    2. 执行/usr/sbin/setenforce 0立即关闭,且无需重启系统

    如果你不希望关闭SELinux的话,也可以

    chcon -t shlib_t ZendOptimizer.so

    chcon -t texrel_shlib_t ZendOptimizer.so

     

    操作后php-fpm start启动,一切正常。 但ShopEx网站返回502错误,修改php.ini输出错误日志查看后发现访问Zend Guard做了encode的php文件均无法正常执行,错误是Connection reset,但命令行查看php -v时显示Zend Optimizer已加载,phpinfo()也显示正常。
     反复尝试多次后发现是装载次序的问题,修改php.ini,使eAccelerator在Zend Optimizer之前装入即可


  • 如何用两个立方体表示所有日子的组合

    发表于 2010年02月4日 黄良懿 4 条评论

    今天碰到一个很有趣的问题,如何用两个立体表示所有的日子(不需要年和月)。

    一开始就考虑123是可能需要重复出现的,而12种组合里扣除3个以后无法表示10个数字组合,故此路不通。 而且这问题应该没这么简单吧?

    花了下空间图,结果发现了一个很取巧的方法,其中一个立方体上写上456789,另外一个则是310221。通过仔细的组合,其实能够让第二个“奇怪”的立方体表示出01、02、03、10、11、12、20、21、22、30、31,其他也就不用多说了。思考的时候也考虑过把数字颠倒或者是用LED字体,通过两个立方体组合显示等,不过都没办法解决。

     

    啊哈,当然没那么简单。不能这么的取巧,不同的数字要分步在两个立方体上。  又再考虑了下,其实3在重复组合中是个特例,仅需要30和31 。 所以,只要让3和0分开,两个立方体上都有1和2,那么就刚好可以匹配所有选择。 不过这个答案还是不对,因为无法表现01-09的所有组合。 0也是个特殊化的数字,没有00,然而需要从1-9并没有办法减少对数字的需求,所以我面对质疑还是肯定的说,如果需要表现01-09,则应该是无解的,因为10个基本数字外仅容许两个冗余,1和2已经把名额用光了。 嗯,当然,如果我们说的立体不是正六面体的话,那自然是很简单的啦,哈。

     

    好吧,我也承认这个只是取巧,不可能采用。  回到电脑前查了一下,发现这个问题其实还是有解的,恰恰是我思考中考虑过的,通过字体来“欺诈”,呵呵,很多字体特别是LED字体中的6和9颠倒过来是一样的,可以省出一个空格,只要这个空格放上0并且和之前那个0不在同一个立方体上就可以做到了。 当然,这就不再是数学的解法了。

     

    有些时候,完成一件事情所需要的条件我们都具备或者是不费力的拥有,却无法得出一个最优的答案。 事实上我考虑这个问题的时候一开始并没有限于数学领域,否则也不会有相邻组合的取巧做法和非正六面体组合的想法,然而得出最接近真相的方法是数学方法,于是在得到需要01-09的时候也自然的用了数学方法准确的证明此题无解。

    记录下来,提醒自己不要钻进一个胡同后,蹲在胡同里考虑所有前面巷子转弯的组合就断言罗马不可达,不要忘了递归算法能解决问题最重要的办法就是算不出来就回溯,换个方法再算!


  • MyEclipse 7.5下如何安装Aptana

    发表于 2010年01月21日 黄良懿 1 条评论

    Aptana是一个基于Eclipse的开源Web开发环境,其JavaScript支持相当出色。Aptana包括了HTML、JavaScript、CSS的代码建议功能,JavaScript 自定函数也都会出现在代码建议中,还支持代码语法错误提示。

    除此之外,Aptana还支持十几个热门AJAX框架的代码建议:JQueryDojoYahoo UIPrototypeRicoscript.aculo.us等。

    JavaScript调试功能也是其特色之一,目前仅支持Firefox,想要IE的调试支持则需要付费。

    在MyEclipse 7.5下的安装十分不顺利,查了不少资料均提及MyEclipse 7.5的插件方面的支持做了调整,反正从Update site安装、在MyEclipse默认的下载选择中安装、下载安装包以后Add Archive、下载后解压到common目录下等等方法都无法正确安装Aptana,而link文件的方法MyEclipse 7.5下貌似也不再支持(至少我把文件放在MyEclipse和Common下面都没有任何反应)。

    有一些文章指出7.5的插件安装需要通过修改%MYECLIPSE_HOME%MyEclipse 7.5configurationorg.eclipse.equinox.simpleconfigurato下的bundles.info加入新插件信息才能解决,无奈之下打开此文件,里面的内容节选如下:

    com.genuitec.eclipse.aspphp.core,7.5.0.zmyeclipse75020090612,file:e:softGenuitecCommonpluginscom.genuitec.eclipse.aspphp.core_7.5.0.zmyeclipse75020090612.jar,4,false

    com.genuitec.eclipse.aspphp.ui,7.5.0.zmyeclipse75020090612,file:e:softGenuitecCommonpluginscom.genuitec.eclipse.aspphp.ui_7.5.0.zmyeclipse75020090612.jar,4,false

    com.genuitec.eclipse.ast.deploy.core,7.5.0.zmyeclipse75020090612,file:e:softGenuitecCommonpluginscom.genuitec.eclipse.ast.deploy.core_7.5.0.zmyeclipse75020090612,4,false

    com.genuitec.eclipse.browser,7.5.0.zmyeclipse75020090612,file:e:softGenuitecCommonpluginscom.genuitec.eclipse.browser_7.5.0.zmyeclipse75020090612.jar,4,false

     

    略一看就知道,大致的规则是将文件名的第一个下划线之前的内容放在最前面,紧跟一个逗号,随后是下划线以后的内容,继续逗号,文件的URI,最后的4,false貌似是固定的,至少绝大部分文件都是如此。

    试着将Aptana下的文件列表生成类似的条目并加到此文件中,并重启MyEclipse,Bingo,果然看到了Aptana的欢迎页面。

    最后贴几张图和加至文件中的条目列表:

    HTML的语法建议

    阅读全文 »