-
关于改善xhprof使用情况的设想
自从去年将xhprof用在生产环境以来,对生产环境的程序调试,性能优化都带来很多便利。但是在使用过程中,还是有一些细节需要改善。 问题 xhprof的profile日志直接以文件形式保存在生产服务器上,需要定时清理,或者收集起来移动到查看日志的工具机上。 由于xhprof生成的profile是一个大数组,所以保存到文件时使用了标准的php serialize,日志文件偏大,一个不留神就容易占用很多服务器磁盘空间。 查看日志列表时,一个个点开查看比较费劲。 针对这几个问题,我有一些小小的设想。 日志存放 部署一个中央日志服务器,采用facebook的scribe来收集日志。生产环境的服务器产生的xhprof日志,都写入到scribe的客户端,由客户端自动同步到中央日志服务器的scribe上,不占用本地的存储空间。在代码上的改动也比较小,只要基于iXHProfRuns接口实现一个XhprofRuns类,调整save_run方法的存储方式即可。 更换序列化方法 xhprof默认是将profile信息用php原生的序列化方法处理后进行保存,而我在前两天比较过igbinary vs serialize vs json_encode的性能和占用字节数,这个测试里igbinary在各方面都有一定优势,尤其是占用存储空间会大幅度减小,所以我只要更换序列化方法为igbinary_serialize即可获得改善。 优化列表展示 我已经厌倦挨个查看profile日志的大图,费时费力还没有针对性。所以我现在的做法是,在profile日志的列表中将前1000个日志的总体执行时间直接输出到列表中,并且将执行时间过长的日志用红色粗体标识。做了这个小小的改动之后,当我想要去视察一下运行情况时,就把日志列表中那些红通通的链接点开看看就行了,真正的省时省力。 如何从xhprof日志文件中获取执行时间?简单的代码如下 /** * 由xhprof日志获得执行时间 * * @param string $log xhprof日志的文件路径 * @return int 执行时间 */ function getSpentTime($log) { $profile = unserialize(file_get_contents($log)); return $profile[‘main()’][‘wt’] / 1000; }
-
igbinary vs serialize vs json_encode
最近看到memcached扩展支持额外的序列化方式 — igbinary,这是一个未收录到pecl的php扩展,它提供的两个主要方法: igbinary_serialize igbinary_unserialize 据称可以用它来代替php自带的序列化函数serialize,性能更好,而且占用的字节数也更少。下面我就 igbinary ,serialize ,json_encode三者的性能做了一个简单的测试。 测试 以一个包含1000000个元素的数组做为原始数据,分别以json, serialize, igbinary进行序列化和反向操作。 <?php ini_set(‘memory_limit’, ‘512m’); $array = array_fill(0, 1000000, rand(1, 9999)); $start = microtime(true); $export = json_encode($array); $end = microtime(true); $duration = $end – $start; print(‘JSON Encode: ‘ . $duration . PHP_EOL); $start = microtime(true); $import = json_decode($export); $end = microtime(true); $duration = $end […]
-
memcache连接慢又一例
继上次解决memcache连接慢问题以来,好长一段时间没在这个问题上翻过跟头。这一次我又在生产环境观察到php和memcache的连接时间经常会在50ms以上。 作为一个cache,占用了这么长的执行时间,天理何在? 实际的运行环境如下: apache + mod_php php-memcache扩展版本为2.2.5 memcache的并发连接数在400左右,相当少 这次memcache扩展用的是最新的稳定版,无可挑剔。所以刚开始我认为是网络环境的问题,于是直接采用telnet工具直连memcache进行测试,发现速度飞快!一点便秘感都没有!所以把目光仍然放回到memcache扩展上来,集中对比较慢的addServer方法各项参数进行排查。 Memcache::addServer方法 bool Memcache::addServer ( string $host [, int $port = 11211 [, bool $persistent [, int $weight [, int $timeout [, int $retry_interval [, bool $status [, callback $failure_callback [, int $timeoutms ]]]]]]]] ) 比对结果表明,$weight参数对memcache的连接时间有显著的影响,$weight的默认值为1,一旦设置为别的数值,连接时间便会由毫秒级变成50ms左右,立竿见影。 鉴于php-memcache扩展一贯恶劣的表现,俺不得不痛下决心迁移到新的memcached扩展上。memcached扩展基于libmemcached开发,而且提供了丰富的接口方法,应该是更好的选择。
-
php的echo为什么这么慢
作为一个行走江湖多年的老中医,今天受命去解决一例前端页面展现缓慢的问题。问题页的情况如下: apache + php 使用smarty模板输出内容 页面最终输出内容较大,80k+ 页面执行时间在500ms以上 祭出法宝xhprof对问题页面做了细致检查,发现页面的瓶颈竟然是模板(编译后的)中的一个echo语句,这个echo语句输出的字符串比较大,大概是50k+字节,花费时间为400多毫秒,占整个页面执行时间的80%。这样的echo输出在站点首页中其实是很常见的事情,没有数据库操作,按道理执行时间不应该这么长。 于是猛力使用搜索技能,最终在php手册的echo部分找到了一些蛛丝马迹,早在2003年就有前辈认为通过echo输出大字符串到客户端会引起服务器的性能问题,据我测试,在这个场景下使用print其实也是一样的慢。建议的解决办法是把字符串切割成更小的字符串输出,展现速度会有提升,输出函数如下: <?php function echobig($string, $bufferSize = 8192) { $splitString = str_split($string, $bufferSize); foreach($splitString as $chunk) echo $chunk; } ?> 但是上面的处方不太对症,整个echobig的输出时间仍然在400毫秒左右,没有太大改善。 考虑到是输出大量内容到客户端比较慢,于是检查了apache的配置,原来还没打开deflate进行压缩,遂启用之。再次使用xhprof进行检查,这条echo的输出时间降低到5ms左右。 400ms到5ms,一个配置问题会产生80倍的差距,还真是省老钱了。这个故事告诉我们,压缩输出真的很重要。 Update 实际上这个问题也可以通过调整webserver的output buffer来解决一部分,参考: Why echo is slow in PHP and how to make it really fast 加速PHP的ECHO
-
用git从svn里clone最后几个版本
一般情况下git svn clone这个操作会从第一个版本开始同步,如果版本号已经到了好几万(或更高?),这个操作会相当的费时。 当时还想着能不能hack一下git-svn脚本,其实后来看看文档,clone操作可以使用参数-r$REVNUMBER:HEAD检出指定版本后的代码,因此,更好的步骤应该是这样: svn info http://your-svn, 并记录最后的版本号,假设是260 假设要检出最后10个版本,做个简单的减法: 260 – 10 = 250 开始clone操作了 git svn clone -r250:HEAD –prefix=svn/ http://your-svn 按这个办法,clone的时间的确是减少了许多。
-
mysql的partition与auto_increment
mysql 5.1之后,开始新增了partition功能,可以做到对代码透明的情况下进行数据分区。但是从实际使用中来看,限制颇多: 如果用来分区的字段和主键不是同一个,则不能分区 见很早前的一篇译文mysql 5.1新功能 -- 按日期分区,如果查询条件中没有正确利用到分区的字段,那么partition的效果为零 mysql 5.1.31之前的partition功能有较大的bug,会导致自增字段不能正常自增,怎么个不正常法?auto_increment值会非正常增长,突然比当前的最大id小很多,插入数据时导致duplicate key错误。 这些问题都能凑合着对付过去,但是partition功能和auto_increment似乎是天生的敌人。从使用习惯上来说,我们会把auto_increment字段设置为主键,这个字段要是unique的才比较稳妥,这个情况下如果希望用其它字段来进行分区的话,几乎就成了不可能的任务。以这张表为例: CREATE TABLE user ( id int auto_increment PRIMARY KEY, uid int, foo varchar(32) ) engine=myisam; 这个时候试图以uid进行分区会怎么样?是以怎样的惨淡结局收场?变通的办法是对自增字段做一些调整,从mysql的手册来看,自增字段不一定要设置成主键,仅仅是普通的index也可以,那么把表结构换成下面这个样子,就可以分区成功。 CREATE TABLE user ( id int auto_increment, uid int, foo varchar(32), index (id) ) engine=innodb PARTITION BY HASH (uid) PARTITIONS 4; 我在实际环境中有一张表达到了几亿条数据,如果要用时下流行的sharding策略,必须在代码上做一些改动,当时偷了点懒直接使用partition功能,用了大半年倒也相安无事。只是分区表占用内存相当的大,如果有其它的表经常出现慢查询耗费服务器资源,那么雪崩效应就出现了,所有的查询都会堵塞变得非常慢。
-
在生产环境中使用php性能测试工具xhprof
xhprof是facebook开源出来的一个php性能测试工具,也可以称之为profile工具,这个词不知道怎么翻译才比较达意。跟之前一直使用的xdebug相比,有很多类似之处。以前对xdebug有一些记录还可以供参考,但是它的缺点是对性能影响太大,即便是开启了profiler_enable_trigger参数,用在生产环境中也是惨不忍睹,cpu立刻就飙到high。 而xhprof就显得很轻量,是否记录profile可以由程序控制,因此,用在生产环境中也就成为一种可能。在它的文档上可以看到这样一种用法: 以万分之一的几率启用xhprof,平时悄悄的不打枪。 if (mt_rand(1, 10000) == 1) { xhprof_enable(XHPROF_FLAGS_MEMORY); $xhprof_on = true; } 在程序结尾处调用方法保存profile if ($xhprof_on) { // stop profiler $xhprof_data = xhprof_disable(); // save $xhprof_data somewhere (say a central DB) … } 也可以用register_shutdown_function方法指定在程序结束时保存xhprof信息,这样就免去了结尾处判断,给个改写的不完整例子: if (mt_rand(1, 10000) == 1) { xhprof_enable(XHPROF_FLAGS_MEMORY); register_shutdown_function(create_funcion(”, “$xhprof_data = xhprof_disable(); save $xhprof_data;”)); } 至于日志,我暂时用的是最土的文件形式保存,定期清除即可。 BTW:xhprof生成的图形方式profile真是酷毙了,哪段代码成为瓶颈,一目了然。
-
启用memcached压缩注意事项
在php开发中,开启memcache的数据压缩存储是一件很简单的事情。在多数情况下,压缩数据不仅不会降低程序的执行效率,反倒会因为网络传输的开销降低,带来速度提升。看看最常用的Memcache::set方法: bool Memcache::set ( string $key , mixed $var [, int $flag [, int $expire ]] ) 在这个方法中,将$flag设置为MEMCACHE_COMPRESSED即可启用memcache压缩存储。 这样做有什么弊端? 如果没有做额外判断,每一次写入memcache都会启用压缩,不管数据的大小。对应的,每次获得数据都需要做一次解压缩的操作,这是典型的一刀切手法。实际上在数据很小的情况下,不需要压缩,在这个基础上压缩省不了多少空间。 更好的压缩策略? 好了,我的想法是在数据超过一定大小(比如2k)的情况下,才开启压缩。这个好办,捋起袖子就干,在调用Memcache::set方法之前,首先判断一下数据的大小,一个strlen就搞定了,再简单不过了。 $memcache = new Memcache; $memcache->connect(‘localhost’, 11211); $flag = strlen($data) > 2048 ? MEMCACHE_COMPRESSED : 0; $memcache->set(‘mykey’, $data, $flag); 有人可能会问了,array和object怎么办,这玩意可不能用strlen判断长度。 这还真能难住我一阵子,要知道把array/object写入memcache的时候,php会自动做serialize,再把它当作字符串插入memcache。 $flag = strlen(serialize($data)) > 2048 ? MEMCACHE_COMPRESSED : 0; 谁会采用这段代码?看起来非常山寨,而且serialize也不快,赔本买卖。 更好的办法! 上面的文字都是废话,直接看这段就好。Memcache::setCompressThreshold方法可以包办之前所有的逻辑。 Memcache::setCompressThreshold […]
-
在centos 5.2下安装最新的mysql proxy
mysql proxy的代码树已经迁移到lauchpad,采用bazaar进行版本管理。参考了一些文档,在centos 5.2下编译安装最新mysql proxy成功。步骤记录如下(在centos 5下应该也适用): 首先让EPEL (Extra Packages for Enterprise Linux) repository 生效 # rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-2.noarch.rpm 确定这些包已经安装:GNU Autotools, flex, pkg-config, bazaar, MySQL client libraries # yum install autoconf automake libtool flex pkgconfig bzr mysql-devel centos下自带的libevent版本超老,这个没有别的办法,只能自己重新编译,版本需要在1.4.0以上,越高越好 $ wget http://monkey.org/~provos/libevent-1.4.9-stable.tar.gz $ tar zvfx libevent-1.4.9-stable.tar.gz $ cd libevent-1.4.9-stable $ ./configure $ make # make install centos自带的glib版本也比较老,mysql proxy […]
-
解决memcache连接奇慢问题一例
最近用xdebug观察线上程序的运行时间统计,发现往日里跑起来像飞的memcache居然是系统中拖后腿的耗时大户,连接时间特长。 运行环境 webserver是apache + php php memcache extension版本是3.0.2,当时是最新的beta版… 有4个memcache server可供使用 代码中会利用php的Memcache::addServer依次连接四个memcache,长连接方式 现象 完成四次addServer一共需要300ms以上,但是一旦连接上,获取单个item飞快,时间在3ms以下。 更可恶的问题在于,虽然执行了四次Memcache::addServer,但是实际使用的始终是最后一个memcache,这实在让人崩溃。 问题解决 使用了一点搜索技巧,在pecl.php.net上找到了类似的bug: First get slow when using multiple memcached servers 这个bug的描述如下: We are monitoring memcached performance and noticed that when we added a second memcached via Memcache::addServer the first get request is always slower than the subsequent ones although we are […]