Archive for 四月, 2011

我的理想,是做一个校长。

我的学校里,有很多猫,很多狗,偶尔还会来几个小朋友。

猫团团和猫噜噜是学校里王,他们会带着小崽子们追逐打闹,过无忧无虑的生活。外面的流浪宝宝们,都知道团大王和噜大王的名字,知道被人欺负了,没有家了,冷了饿了,孤单了,就来找大王。

我这儿的门,一直敞开着,坏人都不要来。猫大王们会带着猫卫兵和狗巡逻队把他们赶得远远!

我要在小院里,种一小片麦子。也许他们永远都长不出麦粒,因为猫大王会带着猫朋狗友来啃麦苗。

我的屋后,会有一块没有字的碑。我经过的小生命们,永远留在我的脑海里,映在碑上。小黄,噜蒂,小博美,lucky,你们一路走好。

—————————不华丽的分割线————————————-

我希望发财,但是我相信,即使一辈子发不了财,我还是可以为他们做很多事情。

我不要念叨着“等我有了钱,怎样怎样”,我要从现在做起,已经做起。

最近做了一个项目,提交给性能测试结果如下:

List: TPS =3400
Detail: TPS =480

Detail页面 资源消耗大:  CPU idle75%, Load average:10

服务器配置为8g内存,4cpu,linux 2.6的核,lighttpd+php,本机跑了memcached。另有一台同样配置的服务器跑mysql。

由于对方要求load必须降下来,所以开始调优。

首先分析为什么load会大呢?vmstat查看io和阻塞都不大,但是system cs即上下文切换峰值为万数量级。top看了下内存占用率也正常。而load代表的含义是,cpu中正在执行的进程与等待执行进程的数目。它过大的原因,要不就是进程数太多,要不就是某些进程占用时间片太长。

先从代码入手,把detail静态信息做缓存,动态信息ajax异步加载。但其实测试环境下,mysql数据量并不大,这样优化没啥效果。

第二步,想会不会是lighttpd并发数不够,所以增大了PHP_FCGI_CHILDREN,使总共派生的php-cgi数目达到500.还是没进展,而且貌似偶尔还有小下降。这时查看lighttd的errorlog,发现有mysql too many connection的错误。

第三步,调整mysql的配置,将Max_connections配置到500,wait_timeout配置为1000,即增大可用连接,防止长连接。这时看着有所进步。

第四步,既然我的tps已经达到要求,而load过高,那么调整lighttpd的并发并没有好处,所以仍旧修改php-cgi数目为100。

第五步,将memcached迁移到mysql服务器上,因为该服务器cpu、内存使用都比较低。

再次测试,load降到了4以下!bingo!

ps:记录一些命令

查看cpu信息:grep ‘model name’ /proc/cpuinfo

据说load average的值保持在  Load Average < CPU个数 * 核数 *0.7 比较好!

这里有一篇文章,把load average说的很明白:http://www.gracecode.com/archives/2973/

context switch rate(上下文切换)的说明,节取自:http://blog.csdn.net/marising/archive/2010/01/12/5182771.aspx

就是Process(Thread)的切换,如果切换过多,会让CPU忙于切换,也会导致影响吞吐量。《高性能服务器架构 》这篇文章的第2节就是说的是这个问题的。究竟多少算合适?google了一大圈,没有一个确切的解释。Context Switch大体上由两个部分组成:中断和进程(包括线程)切换,一次中断(Interrupt)会引起一次切换,进程(线程)的创建、激活之类的也会引起一次切换。CS的值也和TPS(Transaction Per Second)相关的,假设每次调用会引起N次CS,那么就可以得出

Context Switch Rate = Interrupt Rate + TPS* N

CSR减掉IR,就是进程/线程的切换,假如主进程收到请求交给线程处理,线程处理完毕归还给主进程,这里就是2次切换。也可以用CSR、IR、TPS的值代入公式中,得出每次事物导致的切换数。因此,要降低CSR,就必须在每个TPS引起的切换上下功夫,只有N这个值降下去,CSR就能降低,理想情况下N=0,但是无论如何如果N >= 4,则要好好检查检查。

对于ligttpd来说,一个请求,先发送至lighttpd,然后交给fastcgi模块(这个模块是编译在ligttpd里的,不知道会不会导致cs切换),最后交给php-cgi,应该最终还会把处理结果返回给lighttpd,从而传送给用户。这样算来,一次请求至少导致2次cs切换。

这个文章不错,存之:http://blog.csdn.net/tianlesoftware/archive/2011/02/21/6198780.aspx

13 Apr 11 使用fastcgi_finish_request提高页面响应速度

本文地址: http://www.laruence.com/2011/04/13/1991.html
文章转自: 火丁笔记

当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法.按照文档上的说法,此方法可以提高请求的处理速度,如果有些处理可以在页面生成完后再进行,就可以使用这个方法.

听起来可能有些茫然,我们通过几个例子来说明一下:


通过浏览器访问此脚本, 结果发现并没有输出相应的字符串,但却生成了相应的文件.由此说明在调用fastcgi_finish_request后,客户端响应就已经结束,但与此同时服务端脚本却继续运行!

合理利用这个特性可以大大提升用户体验,趁热打铁再来一个例子:


代码里用sleep模拟一些耗时的操作,浏览时没有被堵塞,程序却都执行了,具体看日志.

末了给您提个醒,Yahoo在Best Practices for Speeding Up Your Web Site中提到了Flush the Buffer Early,也就是利用PHP中的flush方法把内容尽快发到客户端去,它和本文介绍的fastcgi_finish_request有些许的类似.

转载附言: 我看了下这个方法, 在调用的时候, 会发送响应, 关闭连接. 但是不会结束PHP的运行. 相比调用flush, 或者我之前介绍的加速你的Echo来说, 这个方法能更加干脆一些.

另外, 从代码的可移植性讲的话, 可以在代码中附上如下代码:

if (!function_exists(“fastcgi_finish_request”)) {
function fastcgi_finish_request() {
}
}
不会造成代码部署在非fpm环境下造成问题.

zz from: http://huoding.com/2011/04/10/62

目前使用MySQL的网站,多半同时使用Memcache作为键值缓存。虽然这样的架构极其流行,有众多成功的案例,但过于依赖Memcache,无形中让Memcache成为故障的根源:

  • Memcache数据一致性的问题:当MySQL数据变化后,如果不能及时有效的清理掉过期的数据,就会造成数据不一致。这在强调即时性的Web2.0时代,不可取。
  • Memcache崩溃后的雪崩效应:作为缓存的Memcache一旦崩溃,MySQL很可能在短时间内承受高负载而宕机。据说前段时间新浪微博就遭遇了这样的问题。

注:关于清理过期数据的问题,可以在程序架构上想办法,如果数据操作有统一DAO封装的话,可以利用Observer模式来清理过期数据,非主题内容,资料自查。

面对这些问题,HandlerSocket项目是个不错的解决方案,它通过插件的方式赋予MySQL完整的NoSQL功能,从原理上讲,它跳过MySQL中最耗时的语法解析,查询计划等步骤,直接读取数据,如果内存够大,能装下索引,MySQL的查询效率能提高若干倍!

性能测试:Using MySQL as a NoSQL – A story for exceeding 750,000 qps (F*ck GFW)

因为HandlerSocket的性能足够好,所以就没有必要使用Memcache了,能节省大量的硬件资源,相当低碳!而且HandlerSocket操作的是MySQL放在内存中的索引,没有额外的缓存,所以自然就不存在数据一致性的问题。

安装

如果使用Percona Server版本的MySQL就简单了,因为它已经内置了HandlerSocket支持,不过考虑到其内置的版本不够新,存在一些早已修复的BUG,所以最好采用源代码编译。

注:旧版本HandlerSocket的一些问题可参见:What’s up with HandlerSocket?

官方已经有了一份简单的安装文档,但在我实际安装时,遇到了一些其他未说明的问题,所以这里就把相应的安装过程再写一遍。

首先要确保已经安装了MySQL5.1以上的版本,我用的是Ubuntu操作系统,事先已经用apt安装了MySQL5.1.37,同时还需要相应的mysql_config,如果是Ubuntu的话,可以:

5951710b980761_

注:如果你用的MySQL是从源代码编译的或官方提供的二进制版本,可以略过此步。

接着下载一份和系统MySQL版本一致的MySQL源代码和HandlerSocket源代码:

5951710b980762_

其中的参数含义如下:with-mysql-source表示MySQL源代码目录,with-mysql-bindir表示MySQL二进制可执行文件目录(也就是mysql_config所在目录),with-mysql-plugindir表示MySQL插件目录,如果不清楚这个目录在哪,可以按如下方法查询:

5951710b980763_

运行命令后,如果你使用的是MySQL5.1.37版本的话,会遇到如下错误信息:

MySQL source version does not match MySQL binary version

明明我们的MySQL源代码版本和二进制版本都是5.1.37,为什么还会出现这个错误呢?通过查询HandlerSocket的编译脚本,发现原来它会检索MySQL源代码目录中的VERSION文件,可MySQL5.1.37的源代码目录里不知何故竟然没有这个文件,所以就报错了,既然知道了原因,那我们就照猫画虎做一个VERSION文件放到MySQL源代码目录,内容如下:

5951710b980764_

再次运行configure脚本,应该就OK了,把剩下的步骤进行完:

5951710b980765_

接着需要配置一下HandlerSocket,编辑MySQL配置文件,加入如下内容:

5951710b980766_

此外,InnoDB的innodb_buffer_pool_size,或MyISAM的key_buffy_size等关系到缓存索引的选项尽可能设置大一些,这样才能发挥HandlerSocket的潜力。

注:apt包管理下的配置文件一般是/etc/mysql/my.cnf,否则一般是/etc/my.cnf

最后登陆MySQL并激活HandlerSocket插件:

5951710b980767_

如果没有问题的话,就能在MySQL里看到HandlerSocket的线程了:

5951710b980768_

也可以通过查询刚配置的端口是否已经被MySQL占用来确认是否安装成功:

5951710b980769_

完活儿!现在你的MySQL已经具备NoSQL的能力了!

实战

首先创建一个测试用的表:

5951710b9807610_

注:理论上HandlerSocket支持MyISAM,InnoDB等各种引擎,不过推荐使用InnoDB。

HandlerSocket的协议非常简单,指令通过TAB分割,一行就是一个请求。本文用到了:

  • 打开索引:P <索引标识> <数据库> <表> <索引> <字段>
  • 插入数据:<索引标识> ‘+’ <参数个数> <参数1> … <参数N>
  • 读取数据:<索引标识> <操作> <参数个数> <参数1> … <参数N> <条数> <偏移>

SQL原型:INSERT INTO test.t (id, a, b) VALUES (1, ‘a1′, ‘b1′), (2, ‘a2′, ‘b2′)

5951710b9807611_

注:使用HandlerSocket时,因为没有实际运行SQL,所以Binlog记录的是Row格式。

SQL原型:SELECT id, a, b FROM test.t WHERE id = 1 LIMIT 1

5951710b9807612_

SQL原型:SELECT id, a, b FROM test.t WHERE id >=1 LIMIT 2

5951710b9807613_

SQL原型:SELECT id, a, b FROM test.t WHERE a = ‘a1′ AND b = ‘b1′ LIMIT 1

5951710b9807614_

对HandlerSocket一个常见的误解是只能执行PRIMARY类型的KV查询,实际上只要支持索引,一般的简单查询它都能胜任,篇幅所限,这里就不多说了,如果你觉得直接操作telnet有些吃力,也可以使用自己熟悉的客户端来测试,官方文档里有介绍。

注:HandlerSocket作者写了一个不错的PPT可以参考:HandlerSocket plugin for MySQL

互联网技术发展犹如一列高速运行的火车,下一站:HandlerSocket!大家做好准备吧。

WordPress是一款非常流行的blog开源php代码,本站就是使用它搭建的。

最近,越来越感觉到,之前的我,是专注于术,局限在语言或者工具层面上, 不知道算法是否属于这一层面呢?如果我还在某个安定的公司的一个大team里,这样也无所谓,可以往深入了去发展。但是目前,我需要一个人搞定一切的话,就需要更多的了解道,比如架构。所以,希望通过学习优秀的源码入手,正好手头上有wordpress,那就从这里开始吧。

但是,最开始,还要先了解wordpress的术,然后才能分析它的道。

在真正处理页面显示前,wordpress做了很多的准备工作。

首先,检查wp-config.php是否存在,如果不存在,则引导配置。wp-config.php主要是配置一些静态的数据,比如数据库、环境变量等。而wp-config.php又include了wp-setting.php,这个文件主要处理的是动态的配置。

wp-config.php可以被用户修改,而wp-setting.php则相对稳定。使用这样的加载顺序,并且在wp-setting.php中先检查if defined,就可以使用户在wp-config.php中定义的常量覆盖wp-setting.php中的值了。

看到这里,觉得有个需要注意的地方:切记,不要把函数执行与函数定义混杂在一起,这样会给阅读代码的人造成困扰。比如,在wp-setting.php先include了一堆文件,然后再执行函数,这样就比较明晰。而语言包wp-content/languages/zh_CN.php中不但有函数定义,也有函数执行,就会觉得稍显怪异。

wp-setting.php:

在源码中,大量的使用了global变量,比如wpdb数据库对象,就是作为全局变量,在wp-setting.php中初始化的。如果每一个页面都需要数据库访问的话,这样可以使用同一db连接,而且数据库方法都封装在一个对象里,也比较方便扩展,使代码更紧凑。但是,如果某些页面不需要数据库访问呢?而且这里,就有上面提到的问题,wp-include/wp-db.php中定义并初始化了wpdb对象。

继而,开始处理缓存,在没有任何外部插件的情况下,使用wp-include/cache.php初始化全局变量$wp_object_cache。wp_cache_add_global_groups和wp_cache_add_non_persistent_groups应该是设置哪些需要缓存,还不确定,继续看。

源码中有趣的是,针对一些全局对象的method,封装了function,这样调用的时候,就不用先global,再call了。

include文件default-filters.php,这里针对数据库字段,建立了一些filter规则,这样,在之后使用的时候,直接apply_filter就可以了。

wordpress成功的一个因素,是它对于plugins和theme的支持。晚上细看这一块。