五个月前,Arvato Systems公司找到我说:
背景如果你对非盈利组织有所了解,那么就能大致猜到ChangingThePresent的运作模式。就使用的技术而言,它和其他需要通过互联网聚集用户的行业别无二致。但在 ChangingThePresent,人们彼此不再是买卖关系,而代之以捐赠和无利可图。在兰斯·阿姆斯特朗基金会、儿童救助协会、第一本书(First Book,美国贫困儿童读物捐赠机构)和Sierra俱乐部(Sierra Club,美国自然保护组织)等将近200个顾问机构和380家非盈利组织的支持下,ChangingThePresent的市场运作工作正在走上正轨。但在这里,我更想告诉你的是这个项目技术方面的故事。
在本文中,我将带你重温我们使用Ruby on Rails开发此站点的整个过程。你将了解我们用到的一些关键特性和常用插件。我们使用的技术并非惊世骇俗,但对我们每天所做工作有所了解,可能对你有所帮助。我的目的是在团队协作、生产环境下的可信任技术、我们使用的工具,以及我们认为非常重要的Rails框架等方面,让你有个大致认识。我还会提供一些链接资源,但不会就具体问题作细节探讨,如果你希望深入了解,可在文后留言。如果你们真有兴趣,我将乐于撰写系列文章,就你们的问题共同深入讨论。
概述最早的时候,我以个人身份加入了这个团队,担任主程序员。因为可扩展性和稳定性优异,团队领导人首选Java,但以此评估整个解决方案后,发现投入巨大且系统复杂。我们后来盯上了Ruby on Rails,它很好兼顾了高效生产力、编程模型的清晰度和运行性能。当然,它也并非万无一失。仔细分析现有的站点和我们的商业计划后,我们觉得自己有能力设计出每天处理数百万请求量的系统。尽管当然还没有这样的成功案例,但我们认为通过聚合缓存(Aggregation Cache)和无共享群集(Shared-Nothing Cluster)技术,可以开发出如此级别的系统。
9月份来了又去了。我们的工作非常高产,每周都能推出Demo,进展神速,市场开始关注此项目。到11月份,我干脆加入了WellGood LLC公司,并担任CTO。12月间,我们发布了站点的第一个Beta版。Rails为我们生产力的提高和最后的成功起到了至关重要的作用。
我们离整个站点的完工还差得很远——到目前为止,已经完成的功能大概只占原计划的5%——但我们已经有了足够积累,相信后来的工作会越来越顺利。尽管我们已经初步做过基准和压力测试,但仍然无法保证现在的版本能够长期应对访问量的增长,因为几乎没有哪个Rails站点提供了先例。但我们在以下几个方面大有信心:
拓扑结构首先,让我们对系统全貌有个大致了解。Rails应用大多采用 LAMP(Linux+Apache+MySQL+PHP)架构。就此站点而言,我们使用了Sun的硬件,以F5 BigIP作负载均衡。和其他很多新兴Rails站点一样,我们的应用服务器是 Mongrel(主要处理动态内容。和 lighttpd 属同类产品),数据库自然是 MySQL。我们的数据库按 master/master/slave模式部署,以满足故障切换(Fail-Over)、性能和扩展性要求。
我们选择了对大流量Rails站点运营有着丰富经验的Text Drive公司托管我们的内容。和其他互联网站点一样,我们主要是只读流量,写流量主要集中在几个社交网络个性化模板,以及用户购物历史上。我们还通过分析发现ActiveRecord模型需要优化,关键是要减少数据库请求次数。此外,我们还利用MemCache插件实现了缓存策略。除缓存层外,我们采用的都是Rails传统配置策略。
每个捐赠者满怀热情来捐赠,他们常常会为赠品准备很多图片。接受捐赠的机构和组织也会从中挑选能最恰当展示赠品的图片,以激发访问者的兴趣。图片的价值无容质疑,通过图片的推介,可让赠品尽快到达最需要它们的人手中,从而增强捐赠者的信心和自豪感。但大量图片下载,无疑加大了服务器的负担。Mongrel的特长不在于静态内容,所以我们采用了URL重写(URL Rewriting)技术管理图片。目前,我们还正准备利用 Panther Express实现图片缓存,加快访问速度。
一些Java和.NET的开发者及相关厂商警告我们说Ruby on Rails可能无法满足我们在扩展性上的要求,但我们对此并不十分担心。我相信软件技术发展这么多年,已经积累了大量对Rails同样适用的经验教训;Rails的基石LAMP也是目前各类大型Web站点的主流架构准则。对我来说,更重要的问题是如何以最快的速度实现投资人、用户和老板的需求。现在,我更多的时间花在团队、工具和实现过程等方面。
过程与团队Rails宣称:“我们敏捷,我们快乐。”对此我完全赞同,我热爱敏捷开发。我们热烈拥抱以下敏捷准则:
我们的团队成员分布在三个时区,不久可能更为分散。成员间可以随时开放交流,这样可以尽快解决问题。虽然要依靠长距离通讯,我还是希望我的团队里有更多的优秀Rails开发人员,而不是工作地虽近,但新手过多。到目前为止,这个决定的好处是显而易见的。我们还尽可能增加会议次数,缩短会议时间。我们每天通过Skype举行例会,每周召集市场人员参加的技术会议。我们还坚持每周公布阶段性计划。
有人说Rails对复杂解决方案的扩展支持能力不够,我不赞成这种说法。和同类技术相比,Rails的生产力是最高的。Rails对我们的开发过程、团队组建和管理都产生了戏剧性的影响。所用技术的生产力越低,迭代周期就越长;团队越大,花在检查和协调上的时间就越多——在Ruby和Java项目管理我都经历过后,更为支持这个观点。使用别的技术,就需要组建更大的团队(比如10到15人),而我们则可以保持团队最小化。我们目前有5个Ruby开发人员,最多只需要其中两个人专门负责HTML设计。以后可能增加到7人更为合适,那时,我们将拥有第二个开发小组,专门负责复杂和重点问题的解决。
工具我们使用的工具超级简单,比如代码覆盖测试使用RCov,集成测试使用Selenium,驱动所有的测试和覆盖工具的则是Rake。我们的测试用例覆盖率一般在85%左右,特别是严格跟踪覆盖率后,基本保持在79%到90%的范围。因为我们经常修改用户接口,所以 Selenium的使用情况不太理想。但我想用户接口逐渐固定下来后,成功率会慢慢提高的。目前,我们主要进行的是单元和功能测试,但在其他测试方法探索方面也取得了较大进步。
我们的开发人员可以自由选择编码工具,实际上到目前为止,我们没有任何人从头至尾使用过集成开发环境。我们主要使用的源代码编辑工具是TextMate。包含了日志和测试功能的Rails基准和调试工具已经有足够能力帮助我们完成系统调试。重构有点伤脑筋,特别是必须修改数据库模型的时候,因此一旦发现有足够好的IDE,我们还是会考虑使用的。不过,就我的经验而言,我觉得我们现在的维护速度可以达到Java应用的三倍,甚至更快。
核心架构尽管在项目方案的选择上,我们最初显得有点儿激进,但现在最重要的是一如既往、继续坚持。我们的核心架构仍将以Rails为中心。在数据库端,我们是 DHH( 译者注:Rails作者David Heinemeier Hansson的简称)的忠实信徒;不强行要求引用完整性等数据库约束,也没有使用数据库视图。在测试方面,我们主要依靠Rails框架;当然,随着测试用例规模的增大,我们也在积极尝试其他工具。不少方面都还不到最后定论的时候,仍在探索和变化之中。但总体而言,我们使用的是 REST WebService支持下的经典Rails MVC模型。下文对此还将详述。
模型和很多网络社区站点一样,首先对读操作做了优化,因为我们的大部分内容都是静态的,平均而言每天修改不到一次。通过分析竞争对手的统计数据,我们发现,要想提高系统容量,对稳定内容的缓存非常重要,像慈善组织(如UNICEF。 译者注:United Nations Children's Fund,联合国儿童基金会)、赠品(如癌症研究者的一小时时间)及机构使命(如世界和平、医学研究等)。这些内容不会频繁变化,可以稳定缓存。我们使用Rails插件ActsAsCacheable实现了静态内容的缓存。其后端部署了分布式的二级缓存服务(MemCache),但在特殊情况下,我们绝大多数时候会直接使用ActiveRecord API。
此站点也包含工作流机制。比如,慈善机构提交了某项内容,ChangingThePresent的管理员将负责内容的审查和批准。我们必须提供可在任何时候将任何赠品激活和冻结的功能;而且我们更喜欢将记录标记为删除状态,而不是直接从数据库删除。为了支持这种自定义的工作流,我们引入了一个叫做 ActsAsStateMachine的插件。此插件支持使用DSL(Domain Specific Language)表述状态机,有关DSL的详细资料请参看: 跨越边界:Rails插件。
知道DSL被用于和我们的商业用户交流,我才认识到DSL的强大。Ruth Ann Hacking负责站点内容搜集与管理。虽然她没有编程经验,却是一个参加过世界杯的击剑手。因为她随时可以用军刀(甚至一支圆珠笔)杀死我,所以我整天想法设法给她找乐子。一次兴起,我将包含状态机的代码给了她。结果出人意料,她居然将代码从头到尾看完了,还找出了几个Bug!Ruth Ann高兴了,我也活到了现在。这就是和Rails在一起的生活,它的框架里总隐藏着一些小东西,时不时跳出来逗我开心开心。
用户接口我们的用户接口构建于经典的Rails控制器-视图-布局(Controller-View-Layout)模式基础上。现在还不支持新的RESTful控制器,但会在以后考虑增加。我们只在少数地方使用了AJAX——如贺卡生成向导和现场编辑。总而言之,我们的目标是让用户接口尽可能简洁明了。为了提高站点的可扩展性,我们采取了如下措施:
- 有一个重要项目要我领导。
- 可以使用我喜欢的技术。
- 团队成员(至少部分)可以由我挑选。
背景如果你对非盈利组织有所了解,那么就能大致猜到ChangingThePresent的运作模式。就使用的技术而言,它和其他需要通过互联网聚集用户的行业别无二致。但在 ChangingThePresent,人们彼此不再是买卖关系,而代之以捐赠和无利可图。在兰斯·阿姆斯特朗基金会、儿童救助协会、第一本书(First Book,美国贫困儿童读物捐赠机构)和Sierra俱乐部(Sierra Club,美国自然保护组织)等将近200个顾问机构和380家非盈利组织的支持下,ChangingThePresent的市场运作工作正在走上正轨。但在这里,我更想告诉你的是这个项目技术方面的故事。
在本文中,我将带你重温我们使用Ruby on Rails开发此站点的整个过程。你将了解我们用到的一些关键特性和常用插件。我们使用的技术并非惊世骇俗,但对我们每天所做工作有所了解,可能对你有所帮助。我的目的是在团队协作、生产环境下的可信任技术、我们使用的工具,以及我们认为非常重要的Rails框架等方面,让你有个大致认识。我还会提供一些链接资源,但不会就具体问题作细节探讨,如果你希望深入了解,可在文后留言。如果你们真有兴趣,我将乐于撰写系列文章,就你们的问题共同深入讨论。
概述最早的时候,我以个人身份加入了这个团队,担任主程序员。因为可扩展性和稳定性优异,团队领导人首选Java,但以此评估整个解决方案后,发现投入巨大且系统复杂。我们后来盯上了Ruby on Rails,它很好兼顾了高效生产力、编程模型的清晰度和运行性能。当然,它也并非万无一失。仔细分析现有的站点和我们的商业计划后,我们觉得自己有能力设计出每天处理数百万请求量的系统。尽管当然还没有这样的成功案例,但我们认为通过聚合缓存(Aggregation Cache)和无共享群集(Shared-Nothing Cluster)技术,可以开发出如此级别的系统。
9月份来了又去了。我们的工作非常高产,每周都能推出Demo,进展神速,市场开始关注此项目。到11月份,我干脆加入了WellGood LLC公司,并担任CTO。12月间,我们发布了站点的第一个Beta版。Rails为我们生产力的提高和最后的成功起到了至关重要的作用。
我们离整个站点的完工还差得很远——到目前为止,已经完成的功能大概只占原计划的5%——但我们已经有了足够积累,相信后来的工作会越来越顺利。尽管我们已经初步做过基准和压力测试,但仍然无法保证现在的版本能够长期应对访问量的增长,因为几乎没有哪个Rails站点提供了先例。但我们在以下几个方面大有信心:
- 我们可以实现快速开发、测试和发布。
- 在少量硬件的支持下,我们就可以将系统扩展到更高访问量级。
- 我们可以根据实际需要,在不修改软件系统的前提下增加硬件。
- 我们的五人开发团队在站点早期成长过程中可以同时完成开发和管理工作。
- 我们可以解决未来可能出现的扩展性和性能问题。
拓扑结构首先,让我们对系统全貌有个大致了解。Rails应用大多采用 LAMP(Linux+Apache+MySQL+PHP)架构。就此站点而言,我们使用了Sun的硬件,以F5 BigIP作负载均衡。和其他很多新兴Rails站点一样,我们的应用服务器是 Mongrel(主要处理动态内容。和 lighttpd 属同类产品),数据库自然是 MySQL。我们的数据库按 master/master/slave模式部署,以满足故障切换(Fail-Over)、性能和扩展性要求。
我们选择了对大流量Rails站点运营有着丰富经验的Text Drive公司托管我们的内容。和其他互联网站点一样,我们主要是只读流量,写流量主要集中在几个社交网络个性化模板,以及用户购物历史上。我们还通过分析发现ActiveRecord模型需要优化,关键是要减少数据库请求次数。此外,我们还利用MemCache插件实现了缓存策略。除缓存层外,我们采用的都是Rails传统配置策略。
每个捐赠者满怀热情来捐赠,他们常常会为赠品准备很多图片。接受捐赠的机构和组织也会从中挑选能最恰当展示赠品的图片,以激发访问者的兴趣。图片的价值无容质疑,通过图片的推介,可让赠品尽快到达最需要它们的人手中,从而增强捐赠者的信心和自豪感。但大量图片下载,无疑加大了服务器的负担。Mongrel的特长不在于静态内容,所以我们采用了URL重写(URL Rewriting)技术管理图片。目前,我们还正准备利用 Panther Express实现图片缓存,加快访问速度。
一些Java和.NET的开发者及相关厂商警告我们说Ruby on Rails可能无法满足我们在扩展性上的要求,但我们对此并不十分担心。我相信软件技术发展这么多年,已经积累了大量对Rails同样适用的经验教训;Rails的基石LAMP也是目前各类大型Web站点的主流架构准则。对我来说,更重要的问题是如何以最快的速度实现投资人、用户和老板的需求。现在,我更多的时间花在团队、工具和实现过程等方面。
过程与团队Rails宣称:“我们敏捷,我们快乐。”对此我完全赞同,我热爱敏捷开发。我们热烈拥抱以下敏捷准则:
- 我们专注于缩短开发周期,坚持周周迭代。我们至少每周发布一个新版本。
- 我们依靠开发人员边开发边编写测试用例,而不是漏斗式阶段测试保证质量。
- 我们不害怕以优化业务模型的系统重构。通过重构,我们提高了代码质量、需求理解准确度和系统灵活性。
- 对次级需求,我们实行周内管理;对重要需求,我们实行月度管理。
- 我们利用每周发布的Demo,与用户保持紧密联系。
我们的团队成员分布在三个时区,不久可能更为分散。成员间可以随时开放交流,这样可以尽快解决问题。虽然要依靠长距离通讯,我还是希望我的团队里有更多的优秀Rails开发人员,而不是工作地虽近,但新手过多。到目前为止,这个决定的好处是显而易见的。我们还尽可能增加会议次数,缩短会议时间。我们每天通过Skype举行例会,每周召集市场人员参加的技术会议。我们还坚持每周公布阶段性计划。
有人说Rails对复杂解决方案的扩展支持能力不够,我不赞成这种说法。和同类技术相比,Rails的生产力是最高的。Rails对我们的开发过程、团队组建和管理都产生了戏剧性的影响。所用技术的生产力越低,迭代周期就越长;团队越大,花在检查和协调上的时间就越多——在Ruby和Java项目管理我都经历过后,更为支持这个观点。使用别的技术,就需要组建更大的团队(比如10到15人),而我们则可以保持团队最小化。我们目前有5个Ruby开发人员,最多只需要其中两个人专门负责HTML设计。以后可能增加到7人更为合适,那时,我们将拥有第二个开发小组,专门负责复杂和重点问题的解决。
工具我们使用的工具超级简单,比如代码覆盖测试使用RCov,集成测试使用Selenium,驱动所有的测试和覆盖工具的则是Rake。我们的测试用例覆盖率一般在85%左右,特别是严格跟踪覆盖率后,基本保持在79%到90%的范围。因为我们经常修改用户接口,所以 Selenium的使用情况不太理想。但我想用户接口逐渐固定下来后,成功率会慢慢提高的。目前,我们主要进行的是单元和功能测试,但在其他测试方法探索方面也取得了较大进步。
我们的开发人员可以自由选择编码工具,实际上到目前为止,我们没有任何人从头至尾使用过集成开发环境。我们主要使用的源代码编辑工具是TextMate。包含了日志和测试功能的Rails基准和调试工具已经有足够能力帮助我们完成系统调试。重构有点伤脑筋,特别是必须修改数据库模型的时候,因此一旦发现有足够好的IDE,我们还是会考虑使用的。不过,就我的经验而言,我觉得我们现在的维护速度可以达到Java应用的三倍,甚至更快。
核心架构尽管在项目方案的选择上,我们最初显得有点儿激进,但现在最重要的是一如既往、继续坚持。我们的核心架构仍将以Rails为中心。在数据库端,我们是 DHH( 译者注:Rails作者David Heinemeier Hansson的简称)的忠实信徒;不强行要求引用完整性等数据库约束,也没有使用数据库视图。在测试方面,我们主要依靠Rails框架;当然,随着测试用例规模的增大,我们也在积极尝试其他工具。不少方面都还不到最后定论的时候,仍在探索和变化之中。但总体而言,我们使用的是 REST WebService支持下的经典Rails MVC模型。下文对此还将详述。
模型和很多网络社区站点一样,首先对读操作做了优化,因为我们的大部分内容都是静态的,平均而言每天修改不到一次。通过分析竞争对手的统计数据,我们发现,要想提高系统容量,对稳定内容的缓存非常重要,像慈善组织(如UNICEF。 译者注:United Nations Children's Fund,联合国儿童基金会)、赠品(如癌症研究者的一小时时间)及机构使命(如世界和平、医学研究等)。这些内容不会频繁变化,可以稳定缓存。我们使用Rails插件ActsAsCacheable实现了静态内容的缓存。其后端部署了分布式的二级缓存服务(MemCache),但在特殊情况下,我们绝大多数时候会直接使用ActiveRecord API。
此站点也包含工作流机制。比如,慈善机构提交了某项内容,ChangingThePresent的管理员将负责内容的审查和批准。我们必须提供可在任何时候将任何赠品激活和冻结的功能;而且我们更喜欢将记录标记为删除状态,而不是直接从数据库删除。为了支持这种自定义的工作流,我们引入了一个叫做 ActsAsStateMachine的插件。此插件支持使用DSL(Domain Specific Language)表述状态机,有关DSL的详细资料请参看: 跨越边界:Rails插件。
知道DSL被用于和我们的商业用户交流,我才认识到DSL的强大。Ruth Ann Hacking负责站点内容搜集与管理。虽然她没有编程经验,却是一个参加过世界杯的击剑手。因为她随时可以用军刀(甚至一支圆珠笔)杀死我,所以我整天想法设法给她找乐子。一次兴起,我将包含状态机的代码给了她。结果出人意料,她居然将代码从头到尾看完了,还找出了几个Bug!Ruth Ann高兴了,我也活到了现在。这就是和Rails在一起的生活,它的框架里总隐藏着一些小东西,时不时跳出来逗我开心开心。
用户接口我们的用户接口构建于经典的Rails控制器-视图-布局(Controller-View-Layout)模式基础上。现在还不支持新的RESTful控制器,但会在以后考虑增加。我们只在少数地方使用了AJAX——如贺卡生成向导和现场编辑。总而言之,我们的目标是让用户接口尽可能简洁明了。为了提高站点的可扩展性,我们采取了如下措施:
- 尽量少用图片。比如站点中有很多圆角,我们都是用JavaScript库实现渲染。当然,不久实现对PantherExpress的支持后,这一策略将有所改变。
- 减少页面缓存。在我们的站点中,用户自定义的东西很多,所以静态页面很少,页面缓存的价值不大。目前缓存主要实现在ActiveRecord层面,另外使用PantherExperss实现图片缓存。
- 我们最开始以翻页方式,按名称首字母排序向用户展示赠品。但不久发现所有用户都只能从A开头命名的赠品开始浏览体验,于是修改为随机排序方式,并每天缓存结果。我们没有通过视图实现这个功能,而是利用MySQL随机函数设计了一个用户自定义的、以当前时间为随机种子的搜索器。