作为一种典型的超大规模WEB应用框架,维基百科的架构图流传很广,在前端使用LVS负载均衡,在请求服务层使用基于缓存反向代理,在核心业务块自然是使用数据库缓存memcached,而mysql数据库复制、读写分离自然无需熬言。
我更关注核心业务内的数据库缓存实现,这对从单机到大规模集群的站点都适用,在数据库问题没解决前,是相对长远的命题。
在windows平台上,因为稳定性,eaccelerator和xcache成为可能的选择,据kolidon测试,memcache服务器在windows上运行似乎会对CPU有较高要求(这个比较奇怪)。而在nix平台上,xcache+memcached当仁不让。
那么,缓存到何种程度为佳?基本上,目前通行的是:
1. 对开放式系统,若查询方式未作严格限定,则缓存到表级为佳,即:以表的更新为自动destroy所有相关缓存的信号;
2. 对专有系统,不妨针对每一种可能的选择决定如何缓存,而对每一个写查询,确定destroy哪些类型、哪些表、甚至哪些行级对象的缓存。
但前者的问题在于,在web2.0交互为王的时代,缓存生成后往往即刻被销毁,效率极低;
后者的问题在于,对专有系统的缓存计划,所耗人力成本相对较高。
但是,能否找出相对通行的准则?
关于这方面的讨论应该不少,个人思考如下:
1. 可以考虑配置信息不存储在数据库中而直接存成php文件或缓存对象,这能被xcache/apc/eaccelerate直接缓存在内存中执行,效率最佳(当然,在首次请求时从数据库中读出后写成php文件亦为合理方式)[DISCUZ],或缓存运算中间文档为PHP文件;
2. 另外,对一些结构简单且数据完整性并不重要的表,如session表,则可直接(读取后)利用eacc或xcache缓存机制在内存中建立对象,使用对象机制存取(失败则重新建立内存映象)(这和mysql基于内存的表性质相近,但应能节省sql语句编译时间和mysql引擎响应时间);
3. 尽量让一个表在一个页生命周期仅读或写一次,若有两个以上的交叉表查询,反复测试性能以比较它和事后的代码过滤机制何者开销为小(特别是数据库引擎严重承压的情况下);
4. 在某些基本命题下,事情可以简单化:如,若写表时若不涉及key字段(举例为:update时以key为条件但不包含目标行、insert新行、delete或replace以key字段为条件但非目标行),则以key字段为索引的单行查询不受影响(可以断定的是,有一半的查询是此类查询),除此之外,为节省人力成本计,均计算为完全更新所有目标表相关缓存;
5. 单一页面承载的业务逻辑不可过多(这是开放系统容易出现的状况),而专有系统亦不可将操作流程人为过度延长,前者是为节省单次请求时间,后者是为节省用户总点击时间。
6. 最疯狂的举动是,80%的用户读数据库请求发生在缓存内,也就是说,缓存系统自动为数据库绝大多数表建立内存镜像,一旦有写数据则更新所有相关缓存。
若单页逻辑过于复杂,而为了挽救这种复杂致缓存用得过多,通行的20ms单次页面执行时间的期望极可能落空。
因此,设计时多想想——没有缓存,怎么办?重建缓存的成本多大?
合理设计架构,减少单页逻辑单元是终极办法。
在浏览openSuSe时(相当漂亮的站点)发现,底部有“This site uses the YAML CSS framework”字样。
位置在这儿:
http://www.yaml.de/en/
兼容IE5.x/6.0/7.0 /Firefox /Safari /Opera
后来google了一下,才发现原来yaml已经相当知名。
版权许可,自2.2起,采用Creative Commons Attribution 2.0 License (CC-A 2.0)
意外收获在这儿:
CSS框架汇总-21andy (除yaml外还提到elements http://elements.projectdesigns.org/和blueprint http://www.blueprintcss.org/)
CSS框架利与弊 -blueidea (kolidon对其中谈及的CSS框架之弊持保留意见,论调实属多余)
WEB应用程序的性能提升和应用编码同样重要,它往往在设计上应一并考虑,然后在编码工作整体完成时花大量精力来调整,kolidon近一年在此问题上殚精竭虑并略有心得。
众所周知,对代码的细微调整常常有显著作用,在LAMP平台上,使用xdebug+cachegrind这样的profiling工具是最佳选择,在ASP.NET应用中,系统自带的DEBUG功能已经非常强大。而本文讨论范围不仅限于代码优化和profiling。
1. MYSQL/MSSQL数据库优化
在WEB2.0时代,数据库服务器面临重大挑战,在典型的社区平台应用中,数据库查询基本上在每一次页面请求发生多次,在这里,数据库服务器的磁盘性能基本上是决定一切的关键。但基本上,一台一般服务器(双xeon5210+4G内存量级),每秒数千次查询勉强能应付高峰时段一千至两千次页面请求,若应用量级更大,对MYSQL来说,采用双机或三机主从结构,分离读写是第一步选择,对MSSQL来说,目前能见到的公开资料和应用讨论并不多,但在某个开源应用DNN与MSSQL SERVER EXPRESS的结合,有令人惊讶的效率;
2.查询语句优化,简化SQL语句,减少关联查询、索引、减少全表扫描次数(MSSQL 尽量使用存储过程)
统计每一类可能出现的查询并记录到专门编程日记上以对在可能是优化之,国内几家CMS厂商对这一类话题有较多的贡献,特别是在MYSQL数据库架构和SQL查询优化的细节上(但我更相信是出自一些大型厂商如IBM的资料整理工作);
3.程序中间代码编译缓存(这一点上,ASP.NET完胜)
OP码缓存功能如apc、Eacc、Xcache一定要开启,据kolidon反复测试,在IIS平台下首选eacc,稳定性不错,性能可以接受,Linux/Unix平台首选Xcache,性能超强(很多专用的WEB2.0应用将20ms当作目标,这一条和下一条经验比较关键);
4.程序内cache
在经验2中,我们已经提到每一条可能发生的查询都应该被记录,当然,此处就是尽可能的缓存结果了,在专用应用如discuz中,所有能够被缓存的对象均已被缓存,因此,不论系统访问量多么巨大,查询数量都能维持在很代的水平,而每次查询确实非常简洁,缓存的destroy和generate策略被精心设计,在每一次查询前后被核对,事实证明,这已经成为当前专有WEB应用的基本设计原则。
而开放框架应用所遭遇的最大挑战,在于插件生成的查询方式和数目完全不可控——在drupal及joomla一类的开放构架应用中,查询数量关系一切。因此,kolidon以为,在开放框架应用的设计上,也应有类似准则和规范以控制查询数目,这个规范,我有空时将专门讨论,当然,还会有小技巧如:kolidon——修改数据库类,建立SELECT查询的缓存策略(待撰写);
5.不要忽略浏览器HTTP请求顺序——重视HTTP请求顺序和速度
一般来说,若核心内容已经被输出,则它的格式应该在无js及css的情况下即具有一定可读性(那么,少量的内嵌CSS和JS代码可以被接受),而随后的CSS和JS,因一般采用标准的框架,代码量相对较大,会需要被外挂,外挂前,首先当然是合并以消来多余的请求,不少工具可以帮助我们做到这一点。甚至,有一套名为PHP_SPEEDY的应用,可以通过直接在当前应用内嵌入指定代码发挥作用,它可以自动合并的有js请求,合并所有css请求,自动压缩,并设置过期时间(原理是扫描母应用中的相应字串,读文件并生成特殊唯一的文件名)。而在ASP.NET的开源应用中,有些框架如DNN自行管理UI元件,用一套精心设计的机制来决定何时分发何种JS及CSS,效果也不错。
最关键的,IE浏览器会有并发HTTP请求的限制,这使大量HTTP请求需要更长的时间完成;
6.页面内容的代码顺序也很重要——重视浏览器生成页面的速度
我们需要仔细审核TEMPLATE(虽然程序部分的TEMPLATE分析器的选择也很重要——smarty或者pattemplate都不错),这一般是考虑到制作者更侧重于UI设计而可能忽略浏览器显示效率这个基本面——减少图片数目,审核所有HTML代码以确定浏览器能够顺利地绘制(简单而言,就是不要套太多层表格、不要使用太大的图片作为修饰,不要在页面上使用JS或CSS做过我的特殊显示效果等)。
另外,若必要,所有背景图片应合并成单张图并使用position属性以仅使用图片中的不同位置,这样,图片总大小不变或减小,而请求数大大减少(关于5和6,YAHOO性能团队有一个名为YSlow的插件及配套的“性能优化XX条准则”,基本上,能够解决这方面的一切问题)。
好吧,行文至此,我得承认,WEB应用的本质就是HTTP请求,因此,题目仅仅是夸张地表述。
7.最后,你在使用什么WEBSERVER呢?
OK,kolidon自己在某台服务器上使用的是IIS6+PHP+FASTCGI+ASP.NET2,数据库用MSSQL SERVER EXPRESS2005,因为有不少旧的应用需要运行在这个平台上,但几年来,遭遇的问题多不胜数——当然,管理员经验丰富能避免一些,并且你知道,这些问题都是可以解决的,只是每次解决后,问题总会再次出现,然后,你没办法知道究竟是因为因而造成了恶果。而能够提供解决办法的来源仅此一家(也可以用google去和大量的垃圾站长分享他们的未作保证的文献),而IIS与系统其他组件有太多的耦合。
关于IIS的不满主要有:1) URL REWREITER(有两个,一个商业的有免费应用,一个开源的但稳定性欠佳,另外,ASP.net似乎已经解决了些问题)。2) 配置文件metabase.xml文件的读写API及可靠性,这个集中式的配置文件很大,我的几年下来有两次损坏经历,解决办法是重装IIS然后恢复以前的备份),而读写API仅有少量商业软件的实现。3) 安全设置和应用程序池回收也让人迷惑:默认的,WINDOWS2003需要更改大量系统文件夹的权限以减少通过WEB被攻击的机会:上传木马-权限提升-系统被爆,而应用程序池这个发明或需进一步加强稳定性和性能(有专门文章讨论IIS6下的虚拟主机设置,特别是ASP.NET虚拟主机设置,但我更愿意在专门时间研究微软的所有默认帐户的权限问题)。4)辅助工具欠缺,比如集成的负载均衡和反向代理等。
IIS7已经释出多时,国外的虚拟主机提供商已有win2008+IIS7平台,据说对nix平台上的WEB服务器思路多有借鉴.
因此,如果可能,Apache/Lighttpd/Nginx+FASTCGI+PHP可能会是上佳选择,当然,除了性能、简洁,更重要的是,我们总有无数的工具可以帮助我们找到和解决问题,如,LVS、Squid一类负载均衡,反向代理,Memcached一类的专用缓存服务器。还有,无尽的开源应用。
我的开发平台上的配置是:Ubuntu sever,Nginx+PHP(fastcgi)+fpm+Xcache+Memcache,运行Memcached+Mysqld守护程序。
据信,金山的爱词霸社区应该是Apache双机负载均衡(来源:该项目组某成员博客)。
前文提及:开放框架应用所遭遇的最大挑战,在于由插件产生的查询方式和数目完全不可控
在享受开源的开放式CMS系统众多插件的使得时,要同时保持页面响应速度,减少插件查询次数的绝对必要。
而开源系统本身已经有的缓存机制,但一般仅针对框架核心做特别优化,插件提供者,很多时候,寄希望于简单的模块缓存机制(Joomla),另一些模块作者,认为上百次查询是无关紧要(joomla有个很关键的商城应用virtuemart就是如此,其他几个流行的最新文章一类的模块也存在这个问题)。
好吧,单独审核每一个模块修订其代码,违背了我们的本意,因此,我们应该考虑的是在核心框架内减少这些查询。
一个可供借鉴的思路是,mysql会尽可能的将查询缓存(query_cache_size),以快速响应请求,但这还不够,成本高过利用memcache一类的缓存机制。
因此,我们的思路是,在框架的database类中,拦截查询请求,不要送往mysql而是在缓存中查找有无直接命中可能——缓存的后端可以是memcache\apc\eacc\file无论经什么。
而对update/insert/replace/delete/alter一类的查询,放行的同时,还需要监控它影响到的是哪些表(OK,我们先考虑影响到的表而不是单独的行和列吧,那是专有系统需要考虑的事情)。一旦这个表发生变更,则我们需要destroy所有和这个表相关的查询请求(有些专有系统会立刻重新生成,但我们等需要时再生成罢)。
那么,接下来很简单了:
1. 构建自有的缓存类,用于和缓存后端交互,读、写、删除,这是至少三个函数,另外,检查是否可缓存,这是一个函数,检查缓存是否过期(随机发生,事实上,若仅通过单一数据库类存取数据库,这一步基本可活力),检查每闪写查询影响哪些表(上文描述,每个缓存的查询和表相关);
2. 找出数据库类中处理查询的相关函数,在查询前添加检查语句和读函数,查询和添加检查、写和删除函数;
3. 测试并找出被错误缓存或被遗漏的查询,修正检查函数(那么,这一处不同的系统有所不同了,预置一些,高级用户可自行增添)。
代码片断:
此处代码不日添加(joomla已完工但drupal尚在研究中)。
代码量简短,我已经在feminist.cn等几个站点试用,效果颇佳(MYSQL的CPU占用率显著下降),源代码将于近期整理上传。
最初是MAMBO,在若干年前——大概是mambo3.5时代,我就迷上了这个开源系统。
曾做过些苦力的工作,比如翻译MAMBO4.4.x的发布公告,比如翻译它的语言文件,但水准仅限于此,因此无法明确判断当时的代码质量。而从知名度为看,这个系统当时即已频繁获奖,用户量巨大。
这个系统最初即为开源,但由私人公司组织开发,公司生存模式与初期的REDHAT比较类似。
原因未知,开发团队中的主力,和所在公司分道扬镳,因着MAMBO本身的GNU/GPL协议,迅速发布完全no-profit的JOOMLA,其1.0系列基本与MAMBO相同,每一版的代码都在更清晰,其本身的函数质量和结构性亦逐渐提升。这一个系列,基本上是修正bug,但仍然一年内获得了50万注册用户(此项记录原有专门博客记录,但博文因develop.joomla.org的博客功能的重大变更而消失,变更原因同样未知)。因为这些开发者在开发圈内的广泛资源和有效的组织工作,其后一年,全世界用户就只知有joomla而不知有曼波了。
当前,曼波/MAMBO仍然在有效开发及发布,但声势日减(向曼波致敬)。
Joomla的成功,是开源社区的巨大成功(另一个当前正火的Drupal,设计理念上与JOOMLA略有不同,一样优秀,这个往后再总结),是核心开发人员(有待进一步查证)组织能力的成功(印象深刻的是世界各地的joomla日,但cmsnav人在大陆,无缘数月前在台湾的盛会)。
而核心团队+外围志愿者团队(测试、文档、推广等)+基于兴趣的扩展制作主体+商业模板公司+用户,形成了完整的生态圈(当然,JOOMLA团队专门注册了 非赢利社团,接受包括google等商业机构的资助,这包括定期的核心开发人员会议)。
从架构设计理念和开发流程设置看,如下细节值得重视并划出专门篇幅讨论:
1. 高度得视系统的可扩展性,系统中重要概念明确化(组件、菜单、插件/自动化程序、模块、模板),系统流程先处理页面逻辑(组件),加载页面上的模块,套用模板,在此过程中,插件(自动化程序)有几个触发点,用于完成文本替换等系统特定任务(如替换文章中字词,在主体内容后加载评论块,验证登录信息等);
2. 模板制作简单,与可随意置换和排布的模块,能最简单的实现几乎一切表现层功能,这使商业模板公司积极加入并贡献大量优秀模板(这可能是JOOMLA最具吸引力之处);
3. 对扩展的高度重视,每一类扩展都有固有的模式并且代码相当简单,方便移置其他系统进入(早期cmsnav.com曾在一些站点使用过joomla的WP插件,效果不错),当然,不少核心开发人员及时撰写示例并带领编写关键扩展——当然,现在joomla1.5的系统重新设计,本质上,它是一个侧重地门户的全功能框架库,因此,不少早期的组件编写者已经完全基于这个库重写了组件代码,组件作者和JOOMLA系统之间的共生关系由此更加紧密;
4. 开发流程上,与正常的开源项目流程类似,SVN使协作管理简化,专门的测试及bugs修订小组、有效的社区经动,让定期的修订版发布成为可能,而核心团队的主要注意力集中于下一个版本的需求分析和实现;
5. 组件和模块在概念上仍然有明确的分离,这到现在仍然是有别于主流CMS的一点,因:drupal和dnn均认为一个功能就是一个组件,而模块显然也被归入了这个概念(DNN中统一叫作“模块”、DRUPAL中统一名为“扩展”),因此,替代的,他们的名词是:模块/扩展、页面、皮肤——当然,大家都有语言文件。这个概念的分离,一定程度上来说,让为不同功能组件开发不同形式的模块变得更清晰更明确(见仁见智吧)。
当前JOOMLA已经从1.0跳空到1.5正式版,优势自不必言,kolidon的treeber.com采用了最初的1.5测试版本并一路升级,发现的主要变化有:
1. 对页面执行流程的定义更为明确(我相当怀疑这个是因为ASP.NET页面生命周期概念的逐渐流行,当然,这个概念的明确才产生了自动化程序的概念),因此,首页index.php的代码量相当少——10余行,对页而执行流程形成如此明确的理念,导致了joomla成了一套可供选择的php开发框架(其中众多功能直接采用开源实现,比如其模板功能性已经和smarty不相上下——采用的是www.php-tools.net提供的pattemplate enginen)
2. 执行效率大大降低:-(装载首页组件,取文章数相同,装载相似模块,确保每模块所消耗SQL查询内容相似前提下,比1.0x慢约1/2(在我的win2003服务器平台和ubuntu server平台上均如此),据信这主要是由地采用了完全面向对象的框架所致(这个框架代码量巨大,逻辑复杂,为了容错性而相当严谨),当然,全能的CMS系统比之WP、VBB一类的博客、论坛程序在逻辑上复杂得多;
3. 去除了一些次要概念(如:存档文章);
4. 缓存粒度更细,可根据模块、页面、组件等分别缓存,实现单对象销毁和重建;
5. 在UI层所选用的Mootools库非常优秀(虽然我更倾向Jquery),预期将能鼓励模板制作者和模块制作者据此实现更丰富的UI层动态效果。
(Mootools的core库若全选,在100K往上,这一点上DNN有专门有UI来管理JS和CSS文件——当然,DNN用的不是Mootools——据信他们已经开始采用微软的AJAX框架)。
使用Joomla1.5过程中遇到的若干问题:
1. 每个页面由不同的itemid(菜单编号)索引,可能的情况是,单元有自己的itemid,而单元下的分类也有自己的itemid,而文章又有不同的itemid,那么,灵活性可以相当大——joomla根据itemid来决定此页面上要展示的模块和配套模板等,这样,不同的主体内容通过配置不同的itemid,可以加载不同的外围模块,采用不同的模板,这相当自由——但在很多情况下,这会让人困扰(Drupal,DNN这样的系统并没有创建专用概念:“建立新页面”这个词这个词解决了itemid的问题——然后,让新页面容纳和展示不同的内容和模块组合,尽管原理完全一样,但相对joomla,这两个系统对终端用户更友好一些);
(备注:有专用的sef插件可以处理itemid不同但内容相同的情况——去除itemid,生成同一页面文件名)
2. 仍然没有提供多级分类、核心缓存功能照例不够强大——系统分步调试和sql查询记录表明,在缓存状态默认首页仍需12次查询,而其中半数查询是可通过将整表复制到本地实现(组件表、菜单表、模块表、插件表、模板表至少耗5次查询),采用joomla的站点,这几个表的数据量应该不会太大——超大型站点即使采用,亦应该是做地次开发;
(专文讨论Joomla效能提升页面缓存组件法+treeber.com独家法门)
3. 关于UI层,应该是对这一块的管理有所欠缺,竟然没有根据需求加载不同功能代码文件以减少单次传输数量的功能),也没有压缩存放——这是很多优秀的Joomla模板提供商的一个严重问题,因他们在较早版本的模板中即已开始包含prototype或者jquery或者mootools甚至highlight,严重时,在某些页面中,可以看到5-10个js文件及3-5个css文件,再加上模块制作者偶尔需要包含自有的脚本及css,这往往导致页面head部分过于雍肿,页面加载速度可能从原定的数百毫秒上升到数秒!!
(blog.treeber.com专文讨论JOOMLA效率提升之HTTP请求优化办法)
4. 这个新的框架可能未经严格的测试,亦未经足够的时间磨砺,尽管经过了严谨的设计,但在某些情况下,会产生令人困扰的回应——treeber.net最新的问题是:后台无法登录且无任何提示,以前的解决方案是重装,网上给出的解决方案显然低估了专业使用者的智力水准,但经两个小时eclipse+xdebug,问题解决;
(专文讨论)
值得注意的是,曼波在早期即借鉴cakePHP框架的思路,但为可用性计而有所扩展简化,在5.0开始直接采用cakePHP的框架。
而joomla从1.5起拥有了自己的框架——虽然原始思路亦脱胎于cakePHP,但整体趋势应该是逐渐背离,这个框架能否在将来取得巨大成功取决于开发组的管理是否规范有序。
未来展望
或许因为人事变迁,以前2.0版的roadmap已经消失。
1.6版的新功能可以在此处发现:http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemBrowse&tracker_id=6405
(joomla是由全球用户共同开发的,是一项社会合作的自然产物,并将继续不受单个个体的意志左右而演进,因此,此处标题用“嬗变”是合适的。)
(本页持续更新,请查看blog.treeber.com获得最新更新并发表评论)

关注WEB应用系统架构,侧重效能、可用性研究。欢迎访问treeber.com查看本站整理自网络的非原创精华(筹建中)。