Release It! 读书笔记(2)—— Stability Patterns

首先再次推荐一下《Release It!》,下面接着上一篇日志继续这个话题

Pattern 1. Use Timeouts 一定要设置超时
“等待”是大多数系统无法服务的直接原因:不响应的服务,Sync Flood攻击,死锁的IO,太多的并发连接。网络永远是个不可靠因素,设置超时是任何一个涉及到网络的接口需要的参数,而且要设置一个合理的值,太长等于没有,太短会造成很多不必要的错误。

Pattern 2. Circuit Breaker
小时候听大人说了不少农村刚通上电的时候很多中电的惨案,而现在已经鲜有触电身亡的人了,我还被220V AC点到过两次,所幸有断路器的保护,没有造成任何伤害。软件系统设计里也有这么一个“断路器”的设计模式,把一个系统暂时隔离出去,避免造成更严重的损害,在排除危险后再重新闭合。使用超时是一个保护性的措施,但是如果一个服务完全失效了,所有的连接都需要等待到超时也是个不小的代价。这种情况就适合用断路器了:可以指定连续多少次的超时就可以认为这个服务已经死了,就把断路器设为断开状态,然后每间隔一段时间会将连接置为半开状态,来检查服务是否恢复,在半开状态会尝试连接这个服务,成功则改为连接状态,否则立即回到断开状态。 这样可以避免一个不稳定的,或是已经出问题的接口占用大量的连接。断路器

Pattern 3. Bulkheads
Bulkheads的意思是“舱壁”,就像泰坦尼克,有16个隔舱,当船遭遇碰撞时,4个隔舱进水船也是不会沉的,不幸的是… 软件设计里也有这么一种策略,宁可损失一部分,也要保证其他的系统可以运行。比如一个资源有限的网站要搞个秒杀活动,说不好流量能到大到什么程度,为了防止这个新功能影响网站的其他重要业务,可以把这个功能做到几台独立的服务器上,和原有系统的接口也加上流量控制,也就是把这个功能放在一个隔绝的船舱里,这样即使这个系统因为流量过大崩溃了,系统的其他重要业务依然可以正常运转。当然这样做会带来很多额外的成本:不均衡的资源分配,过多的冗余,复杂的管理,这就需要权衡一下利弊了。

Pattern 4. Steady State
这个模式我觉得应该算是个原则:一个稳定的系统应该是可以不需要人工干预的。如果一个系统经常需要有人登录到生产服务器上做一些维护任务,那么这个系统的稳定性非常值得怀疑。威胁到一个系统长时间稳定运行的因素主要是数据,产品往往会要求要保留所有的历史数据,但这对于开发是不现实的,一个合理的数据清理或归档的策略是一个稳定的系统必须的。另外两个因素是磁盘和内存,很多时候磁盘被填满都是因为日志文件,所以项目代码里最好包含一个logrotate的配置,定期清理一下日志。内存是另一个重点,内存泄露是一方面,做Web系统的人一般接触不到,就算是碰到了也是要等底层的服务器软件提供方来解决的.另外一个用内存的地方就是缓存了,对于PHP这个问题也不大,每个请求结束都会回收资源,对于其他语言就要注意了,保存的Session或者缓存的对象最好放在memcached或者redis里。

Pattern 5. Fail Fast
这个是对于服务提供端的要求:如果能预料到失败,就早点儿告诉用户,免得别人等,也浪费自己的时间。设置超时就是要防着没有做到这一点的服务端的。比如在处理数据之前,先验证一下客户端输入是否正确;比如一个需要依赖数据库的服务,在进行复杂计算之前先检查一下数据连接是否就绪,这样就能节省客户端的等待时间,避免自己无用的计算;如果一个服务需要依赖多个资源,提前获得各种资源也可以避免造成相会之间的死锁。这样做也是有缺点的,提前获得了资源也意味着可能占用资源的时间更长,影响并发处理的效率。

Pattern 6. Handshaking
这个一般会用在比较底层的协议中(TCP链接就是需要先握握手的),握手的目的主要是要检查一下相互的状态是否准备好,通信的协议的版本,双方的处理能力,请求速度限制之类的东西,进行一个动态的协商。看起来是比较高端的技术啦,对于Web开发估计用到的机会不多,算是提供个思路吧。

Pattern 7. Test Harness
这个就是要强调测试的重要性了,强壮的系统需要有黑客般彪悍的测试。

Pattern 8. Decoupling Middleware
这儿说的就是中间件的价值,我也不是很懂。中间件也就是运用前面说到的那些Pattern来达到系统间解耦的,提高系统可靠性的工具。各种开源的或者商业的中间件提供了不同的系统集成的架构,在自己实现之前,可以先找找有没有别人做好的。

最后总结一下,健壮的系统并不是不会生病,而是病了之后能很快康复,不会造成大的伤害。所以研究系统的可靠性并不是要杜绝故障,而是训练免疫系统,当故障出现时尽量减少伤害,并且尽快恢复。

Release It! 读书笔记(1)—— Stability Anti-patterns

Design and Deploy Production-Ready Software

Production ready了是个很高的标准,相对于功能的完整,就像是拿Nokia和山寨机对比。生产级别的系统的要求很简单——稳定,但简单和容易可不是同义词。这本书首先传达的一个观念就是,任何系统都是不可靠的,而达到稳定目标的途径就是通过处理好各种不稳定因素。而这本书介绍的就是有那些不稳定的因素和各种模式和反模式。
第一部分的主题是Stability。首先用作者经历过的案例介绍了系统崩溃的统一的模式:从小裂缝开始,一旦出现了裂痕,出问题的这个部分承受压力的能力就会减弱,然后裂缝逐渐蔓延,一个组件的损坏会造成系统其他部分的压力增大,或者是有依赖关系的部分直接崩溃,最终整个系统瘫痪。
书中列出了几个anti-pattern,就是设计中要尽量避免的问题:
Anti-pattern 1. Integration Point 集成点
现在说到”系统”一般都不是一个单机的程序独立的运行,都是会有各种各样的外部依赖,数据库,Web Service,队列,支付,etc. 当然,单机的程序也是要依赖与硬件的正常工作的。而每个依赖都意味着一个风险,在设计系统的时候就需要考虑,如果某个依赖的服务出问题了,我的系统会跟着陪葬吗?缺少了某个接口提供的重要数据,系统其他的组件能否保持一个待命的状态,在那个出问题的接口恢复后立即能够恢复正常(看起来似乎是理所当然,但很多系统都做不到,而可能会需要重启所有服务)。
首当其冲的一个风险就是所有涉及到网络的地方,不论是通过Http,Socket,服务方提供的API,服务是位于同一网段的内部网络还是通过防火墙的外部网络。很多公司会提供很好的服务产品,但是客户端API可能只是没有编程执照的临时工写的,所以要对客户端API保持警惕。防火墙之类的安全系统是系统必须的保护,但很多时候也会制造麻烦,比如关闭了需要长时间保持的连接池里的连接,却两头都不通知,用的时候要小心。
Anti-pattern 2. Chain Reactions 连锁反应
对于一个繁忙的系统,负载均衡会自动从一个集群中把挂掉的节点摘除,保证系统的正常相应。少了个节点系统的行为确实是没变化,但是出问题的节点的负载就分摊到其他的机器上了,而这种时刻,往往是所有服务器都很繁忙的时候,很快又有另一个服务器不堪压力倒下了,剩下的服务器又被分摊了更多的压力,剩下的服务器可以预见都会这样一个个倒下。
Anti-pattern 3. Cascading Failures
现在几乎所有系统都会用到分层的设计,这个问题要说的就是要避免一个层的问题蔓延到其他层,而且被放大,导致千里之堤毁于蚁穴。
Anti-pattern 4. User
这里指的是那些不老实的用户,啥人都有,稳定的系统一定要把这些恐怖分子考虑在内。
Anti-pattern 5. Blocked Thread
一般情况下,过多的并发连接和阻塞的服务进程(线程)是服务不可用的直接原因。
Anti-pattern 6. Attacks of Self-Denial
这个要说的就是要小心别给自己制造DDoS攻击,比如没准备就搞个“秒杀”。
Anti-pattern 7. Scaling Effect
有些架构设计本身就是没法Scale的,访问量或者数据量太大时就得大改了。
Anti-pattern 8. Unbalanced Capacities
说的也就是系统的瓶颈啦,有时候只是个资源严重贫富分化的问题。
Anti-pattern 9. Slow Responses
慢相应比直接拒绝要可怕,因为这样可能会占用所有的服务进程,保持大量等待的链接,把下游的系统也直接拖死。所以作为服务端,如果能提前知道没法成功返回结果,就早点儿说,别让人等着。作为客户端,一定要对请求设置合理的超时,慢就是失败。
Anti-pattern 10. SLA inversion
关于计算系统可用性级别(几个9)的问题,按条件概率计算,如果一个系统依赖一个不可靠的服务,这个系统肯定可靠不了。
Anti-pattern 11. Unbounded Result Sets
简单说就是SELECT没加LIMIT,你可能觉得这个表不会很大,但是没准儿啥时候就大了,后果很严重的。

MySQL里的NoSQL

http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html (需翻墙,这儿有个保存下来的Word文档

http://blogs.innodb.com/wp/2011/04/nosql-to-innodb-with-memcached/

MySQL+Memcached现在几乎已经成了高性能网站的标准配置,不过多一个服务也意味着多一个风险,MySQL的Failover已经有比较完善的解决方案,不过对于Memcached似乎还不太容易,如果是有很多服务器组成的缓存集群,一个Memcached的损失造成不了太大的伤害,如果只有一两个缓存节点,Memcached死的时候很可能会带上背后失去了保护的MySQL。
现在MySQL推出了自家的NoSQL支持(不过还不是GA级别的),就是前面两个链接列出的HandlerSocket和Memcached Plugin。两个东西的工作原理类似,都是略过MySQL繁琐的查询解析,查询优化的步骤——对于简单的直接使用索引的查询,是不需要优化的——直接操作存储引擎,据测试可以达到比Memcached更高的性能(应该是在数据可以全部放在内存里的情况下对比的,InnoDB会这么做)。而且还能利用MySQL的数据一致性、主从同步等特性。
Memcached Plugin是MySQL自家开发的新功能,据说会在5.6中包括这个新特性,估计还没有人在生产环境里用过。而HandlerSocket就是一个经历过实际生产环境考验的插件了,作者是个日本人,原来是MySQL开发团队的成员,现在是在一个叫DeNS的公司,部署了大约600台MySQL服务器,已经在生产环境应用了HandlerSocket,据说是没什么问题。另外,HandlerSocket已经集成在了Percona发布的MySQL包里了,使用还是很方便的,my.cnf里加几行配置,加载一下插件就可以用了。
最近我的一个项目打算试一下这个HandlerSocket,程序逻辑很简单,访问量比较大,但是数据规模有限,完全可以放在内存里,数据访问模式R>>W,而且系统可用性要求比较高。之前的方案是用两台MySQL服务器做Master-Master的复制,只用一个服务器承担读写,另一个做备份,MySQL就用的Percona发布的MySQL,两台服务器通过Semi-Sync进行Master-Master的复制,使用MySQL-MMM负责故障切换,Memcached来支持读数据的压力。MySQL的双主的结构还是比较可靠的,但是Memcached如果出问题还没想到什么很好的处理方式。现在遇到了HandlerSocket用来解决这个问题还是很合适的,为了安全起见HandlerSocket只用来做读访问,写数据还是通过SQL,这个非正式版的工具应付只读应该还是很可靠的了,毕竟也是在人家大规模的生产环境里用过的。
下周将会部署到测试环境运行一下压力测试,再把性能比较补充一下啦。

5.3时代的PHP框架:Symfony2

前段时间学了一些Ruby on Rails,对Rails最大的一个感触就是对Web开发中的每一个事,都提供了一个或多个解决方案,MVC这些基本的就不说了,ORM,多数据库访问,NoSQL支持,各种层次粒度的缓存,Web静态资源管理,模板,Ajax,数据迁移,部署,单元测试,功能测试,依赖管理,等等。Ruby的Web开发圈子里,这些人会专注做好一个小事,贡献到Rails的社区里,形成一套优秀的而且统一的工具集,而且有更好的工具出现的时候,他们也会欣然的接受更先进的新东西,而不是死守自己的一套。
反观PHP的领域,有着多如牛毛的雷同的框架,各占一个山头,谁也长不大,又谁也不服谁。都在做Full Stack的MVC框架,却少有人专注于一个小功能,开发一个便于应用到各个框架中的工具。直到Symfony2的出现带来了一些变化。
从Symfony2的Preview就开始关注这个新的框架了,当时只是因为php5.3,宣称的性能,和Fabien的口碑。后来渐渐发现了越来越多的优点。下面就一个个的推销一下:

1. 简化的设计
就像Java走过的路,从高高的继承关系树,到简单的POJO,减少了不必要的依赖和耦合。Symfony里面,Action不需要继承什么类,只要能处理一个Request,然后返回一个Response,就可以做Action。Model也不需要集成什么类,只需要配置一下到数据库的映射,用EntityManager处理数据库的事,只需要拿着这个POPO当做数据对象用就行了。View使用的是一个叫做Twig的模板系统,当然也可以换成你喜欢的,但是还是强烈推荐这个师从Django的模板工具,有强大的功能,简单的语法,和很好的性能。

2. Explicity over Implicity
意思就是,有事儿明说,而不是在背地里瞎忙活。框架不会背着你干一些多余的事儿,除非你要求它做这个事。例如模板,很多框架会自动的把action的返回值作为数据塞给一个模板去处理,大多数时候这样做没问题,但是当你不像用模板的时候,就需要花点儿时间研究一下怎么做才能不用模板呢?如果是想用别的格式(例如xml,json)返回结果呢?Symfony里就不存在这个问题,返回的Response对象里放着的就是返回给浏览器的结果,至于如何使用模板,是需要在Action里面处理的事,其实也就是一行代码的事情,其他设置Header,返回错误之类的事,都用Response对象里现成的方法就行了。隐藏一些细节是会节省一些代码,貌似简单了,但是又带来了额外的隐藏的逻辑,当默认的行为不适用的时候,用户就必须弄明白这些缺少文档的隐藏逻辑,才能知道该怎么达到目的。所以还是把事儿放在明面儿上比较好。

3. 包含了开发流程
现在都比较流行各种最佳实践(Best Practice),Rails的一个优点就是统一了每个人的习惯。每一件事都有一个推荐的最佳实践,而且都已经作为一个工具集成在框架里了,每个人做同一件事都会用同样的方法,这对于团队协作意义可是相当大的。选择是种自由,但也是有成本的,Symfony2里面就把推荐的习惯也包含进了框架里,就像Rails一样,每件事情都有一个推荐的标准做法,而且这些做法要么是集成了一个优秀的开源的库,或者是基于Symfony悠久的历史和丰富的经验而设计的一种方便且灵活的方案。

4. 生态环境
PHP应该是框架最多的语言了,但都是自成一体,各有各的规律。而sf2是跟重视规范的,而且设计上就是一个个相对独立的组件,你可以去sf官网下载完整的软件包,也可以用pear来安装,也可以用composer(很有前途的一个包管理工具,有望成为取代pear的标准)安装,更新新版本只需要更新一下定义依赖关系的文本文件,执行一个命令就搞定了。sf的作者最近还写了一组博客,介绍如何用sf的组件去搭建自己的框架。这种开放的,重视标准的态度也是一个非常重要的优点。

Phing Build File

自己写了个用来实现自动化部署的Phing的build.xml,放到到gist上了 https://gist.github.com/1216551

把这个build.xml放在项目根目录下面,在根目录执行phing就会执行默认的deploy任务,在phing里面每个任务叫做一个target。
首先会执行deploy任务所依赖的config任务,config会在命令行提示输入要部署到哪个环境,prod or test,根据用户的输入给一些配置属性设置不同的值。例如服务器地址,帐号之类的。
然后会执行一个build任务,这个任务会提示用户输入一个svn版本号,然后把工作目录update到这个版本号,其实这里需要一个干净的工作目录,最好是有意个单独的目录用来发布,避免有未提交的更改,干扰部署的代码。执行晚svn update,会执行一个tar的命令,将代码压缩打包到一个dist.tar.gz文件。
然后执行的就是deploy任务了,这个任务会把生成的dist.tar.gz文件使用scp命令上传到服务器,然后通过ssh在服务器上把dist.tar.gz解压缩到服务器端的对应版本号的一个revs/rev_{$revision}目录下,然后删除dist.tar.gz,最后把一个当前版本的符号链接指向刚刚部署的这个版本。
这是服务器端项目目录下的文件结构会是这样的:
./current -> ./revs/rev_123
./revs/rev_123
./revs/rev_98
./revs/rev_80

后面的一个switch任务就是可以选择把当前版本指向某一个revision,可以实现系统的快速的回滚和版本切换。
最后有意个update_dep任务,使用来执行一些更新服务器端环境的任务,比如执行数据库迁移,更新crontab设置,重启一些服务之类的事情。

Phing能做的事情还很多,项目的运维的很多任务,都可以定义成一些任务,一方面减少重复劳动和健忘造成的错误,另一方面也不熟悉系统的人也可以可靠的处理一些突发的问题。

安装PHPUnit & Phing

Pear是php的包管理工具,不过跟别的语言的gem,maven,easy_install相比,用起来还是太费劲了点儿,PHP是个谁都不服谁的社区,很多项目并不遵从Pear的规矩,而Pear里确实也有一些很有用的东西,还是离不了。比如这PHPUnit和Phing。
PHP发行包里都呆着个go-pear的脚本,不过基本都是不能用的,安装需要到 http://pear.php.net/manual/en/installation.getting.php 取下载一个pear.phar,从命令行执行php pear.phar,按照提示设置一下安装路径就行了。
之后的步骤就是执行下面的命令了:
pear config-set auto_discover true
pear install –alldeps pear.phpunit.de/PHPUnit
pear config-set preferred_state alpha
pear install –alldeps pear.phing.info/phing
pear config-set preferred_state stable

上面几行的意思分别是:
1. 设置为自动发现依赖的仓库,否则要使用pear channel-add一个个添加依赖的仓库。
2. 安装PHPUnit,包括所有依赖。
3. 设置为优先选择alpha测试版的包,因为phing依赖了不少非稳定版的包。
4. 安装Phing,包括所有依赖。
5. 设置为有限选择stable稳定版本的包。

另外PHPUnit会依赖一个xdebug的扩展,Phing里用到ssh的时候会依赖一个ssh2的扩展,Windows用户可以到 http://downloads.php.net/pierre/ 下载这些扩展,Linux用户自己编译就行了。

I’m back

几个月前,博客因为没备案被封了,前段时间服务器换了个机房,一直也没把DNS改过来,昨天试了试这个机房并没有封锁未备案的域名,于是就又恢复了这个博客了。
很久不写东西了,最近刚换了工作,新的工作里还是有不少东西想要分享一下的,最近要多写点儿东西啦,努力把失望的蜘蛛再吸引回来。

用PECL OAuth访问腾讯微博

首先,这里有个不错的文章,推荐先看看http://huoding.com/2011/01/16/42

我遇到的错误是: Invalid auth/bad request (got a -1, expected HTTP/1.1 20X or a redirect)
通过OAuth::enableDebug()打开调试信息,在异常处理中打印出了OAuth::debugInfo的内容:

array
'sbs' => string 'POST&https%3A%2F%2Fopen.t.qq.com%2Fcgi-bin%2Frequest_token&oauth_callback%3Dhttp%25253A%25252F%25252Flocal.yeegt.com%25252Findex.php%25253Fr%25253Ducenter%25252Fcallback%26oauth_consumer_key%3D56b6bd55919d48f883f1ccf80e7a7a17%26oauth_nonce%3D284154d5a2933524426.31722385%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1297754419%26oauth_version%3D1.0' (length=360)
'info' => string 'About to connect() to open.t.qq.com port 443 (#0)
Trying 112.90.140.155... connected
Connected to open.t.qq.com (112.90.140.155) port 443 (#0)
SSLv3, TLS handshake, Client hello (1):
SSLv3, TLS handshake, Server hello (2):
SSLv3, TLS handshake, CERT (11):
SSLv3, TLS alert, Server hello (2):
SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
Closing connection #0
Peer certificate cannot be authenticated with kno'... (length=531)

原来是SSL验证的问题,写了好几年程序了,一直都没弄清楚SSL那套东西,有可能是因为在本地调试,主机名没法解析,而证书一般都和域名有点儿关系。用OAuth::disableSSLChecks()关闭了SSL的检查就正常了。

Nginx下的Yii配置

我的服务器上的Yii的运行环境配置,php-fpm运行在9010端口上,供参考:

server {
    listen       80;
    server_name  .example.com www.example.com;

    charset utf-8;

    access_log  logs/access.log  main;

    location / {
        root   /home/example/yiigt;
        index  index.php;
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ ^/protected/ {
        deny  all;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

    location ~*.(js|jpg|jpeg|gif|png|ico)$ {
        root /home/example/yiigt;
        expires 356d;
        add_header Cache-Control public;
    }

    location ~ .php$ {
        root           /home/example/yiigt;
        fastcgi_pass   127.0.0.1:9010;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /home/yeegt/yiigt$fastcgi_script_name;
        include        fastcgi_params;

        set $path_info $request_uri;

        if ($request_uri ~ "^(.*)(?.*)$") {
            set $path_info $1;
        }
    }
}

另外加上http的设置,主要是gzip的设置:


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  0;
    #keepalive_timeout  65;

    client_max_body_size 10m;

    gzip  on;
    gzip_min_length  1000;
    gzip_proxied     expired no-cache no-store private auth;
    gzip_types       text/plain application/xml application/x-javascript text/css;
    gzip_disable     "MSIE [1-6].";

    passenger_root /home/test/passenger-3.0.1;
    passenger_ruby /usr/local/bin/ruby;

    include vhosts/*.conf;

}