网上闲逛的时候看到两篇文章,有点小意思。一个是关于Web编程异步模型的白日梦 【FarmVille(美版开心农场)谈架构:所有模块都是一个可降级的服务】
在地铁上被前前后后那些特种男女逼到车角,无奈。又想起早上那个白日梦,遂上网搜索了一番。得老赵的佳作一篇《F# 与ASP.NET(1):基于事件的异步模式与异步Action》。之前看过,由于对微软无爱,未能 细品。今日一读,如醍醐灌顶,豁然开朗。
遂整理思路如下,以待后用。
在说异步模型之前,先说说最常见的同步模型吧。例如下面的 PHP 代码:
- // 获取数据
- $userInfo = getUserInfo();
- $newsList = getNewsList();
- $topRateNewsList = getNewsList('DESC `rate`');
- // 创建模板,绑定变量
- $tmp = CreateTemplate();
- $tmp->bind('userInfo', $userInfo);
- $tmp->bind('newsList', $newsList);
- $tmp->bind('topRateNewsList', $topRateNewsList);
- // 渲染模板,输出
- $tmp->render();
- echo $tmp;
在这段代码中,所有调用都是顺序的。也就是说,如果 getUserInfo($userId) 没有返回用户信息的话,getNewsList(5) 永远都不会被调用。实际情况,可能是用户信息是从用户库取数据,新闻是从新闻库取数据。假设新闻库运行良好,而由于某种原因用户库宕机了,那么这次请求也 就废掉了。故障也从一个库的宕机扩散到整个站点。
现在,我白日做梦的创建一种新语言 go-php,引入 go-lang 的关键字 go 到 php 中:
- // 创建一个数据通道
- $chan = CreateChan();
- // 获取数据,并放到数据通道上去
- go getUserInfo($chan);
- go getNewsList($chan);
- $params = array('DESC `rate`');
- go getNewsList($chan, $params);
- // 创建模板
- $tmp = CreateTemplate();
- // 从通道上取数据
- while($d = $chan->read()) {
- if ($d['error']) {
- // 数据有错误 ……处理一下吧
- } else {
- $tmp->bind($d['key'], $d['data']);
- }
- }
- // 渲染模板,输出
- $tmp->render();
- echo $tmp;
好了,这其实不是什么新奇创造,这只是一个二段式异步调用(Begin/End)。这有点像大宗采购,采购商并 不一件一件的商品进行采购,而是拿着清单说:“好了,兄弟,这是我要的货,你们帮我找齐,放到码头406号仓库去……”,然后他就在 406 号仓库等着点货了。这段代码还可以改进,就像采购清单一样,将这个清单推到数据层,数据层把数据返回到数据通道上去。恩,应该不少人在自己的应用中使用 MQ,Memcache 甚至 pipe 实现了这种异步了吧。
再来看另外一段 go-php 代码:
- /**
- * 回调函数
- * @param $tmp 模板
- * @param $d 返回的数据
- */
- function callbackBind($tmp, $d) {
- if ($d['error']) {
- // 数据有错误 ……处理一下吧
- } else {
- $tmp->bind($d['key'], $d['data']);
- }
- }
- // 创建模板
- $tmp = CreateTemplate();
- // 获取数据,并设置数据回调
- go getUserInfo(callbackBind, $tmp);
- go getNewsList(callbackBind, $tmp);
- $params = array('DESC `rate`');
- go getNewsList(callbackBind, $tmp, $params);
- // 如果不是所有数据都回调了,则阻塞
- $tmp->wait();
- // 渲染模板,输出
- $tmp->render();
- echo $tmp;
这就是老赵的事件回调的异步处理的 go-php 版本。这有点像渠道商订货(或者淘宝上的无货代理?):“我需要XXXX,你帮我送到XXXX去”。然后坐等,所有的内容都送到了,就收钱走人。
对于数据读取的错误,只要处理得当也不是致命的。最多在渲染页面的时候,少某块数据,用户只会奇怪:“这次怎么打开没有新闻那部分的内容了呢……刷 新一下看看……”。而不会说:“烂网站,又打不开了……”用户体验直线上升啊!
好了,梦就做到这里。相信这两种方式其实已经有实际案例了。我比较孤陋寡闻一些,了解的不多。而且有的东西,未经许可也不好多说……大家私下打听 吧。
总之呢,两种异步模型各自有各自的好处。并行的数据存取,提高 I/O 利用率是其本质。王道啊……
-----------------------------------------------------------------------------------------
开始【FarmVille(美版开心农场)谈架构:所有模块都是一个可降级的服务】
所有模块都是一个可降级的服务
For any web application, high latency kills your app and highly variable latency eventually kills your app.
由于大型的网络应用需要依赖各种底层及内部服务,但是服务调用的高延迟是各种应用的最大问题,在竞争激烈的SNS app领域更是如此。解决此问题的方法是将所有的模块设计成一种可降级的服务,包括Memcache, Database, REST API等。将所有可能会发生大延迟的服务进行隔离。这可以通过控制调用超时时间来控制,另外还可以通过应用中的一些开关来关闭某些某些功能避免服务降级造 成的影响。
上面这点我也有一些教训,曾碰到过由于依赖的一些模块阻塞造成服务不稳定的现象。
1. 某Socket Server使用了ThreadPool来处理所有核心业务。
2. 不少业务需要访问内网的一个远程的User Service(RPC)来获取用户信息。
3. User Service需要访问数据库。
4. 数据库有时候会变慢,一些大查询需要10秒以上才能完成。
结果4造成3很多调用很久才能执行完,3造成2的RPC调用阻塞,2造成1的ThreadPool堵塞,ThreadPool不断有新任务 加入,但是老的任务迟迟不能完成。因此对于最终用户的表现是很多请求没有响应。部分用户认为是网络原因会手工重复提交请求,这样会造成状况并进一步恶化。上 面的问题根本是没有意识到远程服务可能会超时或失败,把远程服务RPC调用当成一个本地调用来执行。
解决思路一:RPC增加Timeout
解决思路二:将RPC改成异步调用。
另一分布式大牛James Hamilton谈 到(2)上面这种做法就是他论文Designing and Deploying Internet-Scale Services中的graceful degradation mode(优雅降级)。
FarmVille其他数据
FarmVille基于LAMP架构,运行在EC2上。读写比例是3:1。使用开源工具来做运维监控,如 nagios报警,munin监控,puppet配置。另外还开发了很多内部的程序来监控Facebook DB, Memcache等。到Facebook接口的流量峰值达到3Gb/s,同时内部的cache还承担了1.5Gb/s。另外可动态调整到Facebook 与Cache之间的流量,Facebook接口变慢时,可以利用cache数据直接返回,终极目的是不管发生了那个环节的故障,能够让用户继续游戏。
小结
尽管FarmVille公布了上面一些技术资料,凭借上面这些资料无法全部了解FarmVille的架构。但是所有模块都是一个可降级服务 的概念值得设计大规模应用的同行参考。
-------------------OVER-----------------
上面两篇都讲到了一个顺序执行所导致的问题,所以才想到是采用异步,瀑布型的网页架构事实上本来就会造成当一小部分没正常取出或堵塞时,影响整个网站的运行,异步是否真的必要?如果真有必要,我们的PHP在没有线程、没有进程等的处理状态下,是否能够完美实现?我这只是记录一点资料,请不要尝试与我讨论。我自己也处于迷惘中