Hadoop背后的数学

自从Nathan Marz同学写了那篇著名的How to beat the CAP theorem的Blog,以及Storm发布之后,俨然成为了技术界新偶像。顺着他本人的blog,翻了一下他过去几年的写的技术文章,发现老美的牛人们都爱总结,能够把技术实践提升到理论高度,然后抽象出新的设计和产品,比起我等只能每天苦逼苦逼应对实际需求的人来说,还是强出很多。我自己觉得那么多年,虽然觉得做什么都能做了,但是现在还是只能谈到模仿,做不到提炼总结和超越。

和著名的CAP的那篇Blog,Nathan Marz还有很多其他非常有料值得一读的博客,与其在国内的各种Hadoop大会上听大家泛泛而谈架构,还不如多看看老外们总结下来的技术博呢,比如这篇The mathematics behind Hadoop-based systems,比起大家简单列列机器数字来说,有价值得多。

我曾经写过,数学对于称为一名优秀的程序员是很重要的,但这不一定意味着一定要是多高深的数学。Nathan的这篇Blog用到的数学只有初中难度,但是的确写明白了我们遇到的很多Estimation以及集群数量预估的实际问题,我想一定也有很多人用hadoop但是和我们一样,对于很多机器数量或者workflow的管理是很粗放的,相信这篇blog对很多人都会有用,所以把它翻译成中文,全文如下


基于Hadoop的系统背后的数学

我希望一年之前我就知道这些。现在,根据一些简单的数学,我就可以回答:

  • 为什么在我将处理能力翻倍之后,我的workflow的速度没有翻倍?
  • 为什么10%的任务失败率会导致我的运行时间上升到原来的300%?
  • 如果通过优化了我的workflow的运行时间的30%导致运行时间下降了80%?
  • 我的集群中到底需要多少机器可以满足性能和容错的需要?

所有这些问题都可以通过这样一个简单的等式干净地回答:

运行时间(Runtime) = 额外开销(Overhead) / (1-{处理一小时数据需要的时间})

我们很快就可以推导出这个等式。首先,让我们简单地讨论一下什么是我所说的“基于Hadoop的系统”1。Hadoop的一个常见的用例,就是通过运行一个workflow来处理来连续进入的流数据。这个工作流运行一个”while(true)”的循环,然后每个workflow的迭代,都处理自从上个迭代依赖累积的数据。

下面的这些分析的灵感可以在一个简单的示例中概括出来。假如说你有一个workflow需要运行12小时,然后它会在每个迭代中处理12个小时的数据。然后假如说你加强了这个workflow去做一些额外的分析,然后你估计你在当前的这个workflow中需要多花两个小时来处理。然后麻烦来了。你的workflow增加的运行时间可能远超过两小时。它可能增加了10个小时,100个小时,或者这个workflow可能呈螺旋式上升地在每个迭代中花费越来越多的时间知道无限。

为什么?

问题在于,你将处理12个小时数据的workflow的运行时间增加到了14个小时。这意味着当下一次workflow运行的时候,有14小时的数据需要处理。既然下一个迭代有更多的数据,他要花费更多时间运行。这意味着下一个迭代会有更多的数据,等等。

为了确定什么时候运行时间,让我们做一些简单的算术。首先,让我们写下一个只有一个迭代的workflow的运行时间的等式

运行时间 = 额外开销 + {处理一小时数据的时间} * {多少小时的数据}

额外开销(Overhead)指的是花费在workflow上的任何常数项时间。例如,开始一个job需要的时间会计入额外开销。使用distributed cache来分发文件的时间会计入额外开销。你会把任何独立于数据规模的时间花费放到“额外开销”的分类中去。

“处理一小时数据的时间”指的是workflow中的动态时间。这是你忽视那些额外开销,花费在处理实际数据上的时间。如何一小时的数据会增加你半小时的运行时间上,这个值应该是0.5。如果一小时的数据会增加你一小时的运行时间,这是值就是2。

为了简洁,让我们把上面那个等式通过变量来重新写一下:

T = O + P * H

为了确定workflow的稳定的运行时间,我们需要找到workflow的运行时间在哪个点上正好等于累积了的需要处理的数据量。为了做这个,我们可以简单地插入 T=H 来求解T:

T = O + P * T
T = O / (1 - P)

这就是我之前展示的等式。你可以看到,一个workflow的稳定的运行时间是和workflow中的额外开销呈线性比例的。所以如果你可以将额外开销减少25%,那么你的workflow运行时间就会减少25%。然而,一个workflow的稳定运行时间和动态处理的速率“P”不是呈线性比例的。这背后的隐含含义就是每为集群加入一台机器带来的性能提升是在减少的。

通过这个等式,我们可以回答我在文章开始时候写下的问题了。让我们一起来过一些这些问题:

为什么在我将处理能力翻倍之后,我的workflow的速度没有翻倍?

将你的机器数翻倍,会将你的“处理一小时数据需要的时间”减少50%2。这个的效果和你的运行时间的关系是完全依赖于之前的P值的。让我们用数学来展示一下。假如说你的workflow在集群的机器数量翻倍之前的运行时间是“T1”,翻倍之后是“T2”。这给了我们两个等式:

T1 = O / (1-P)
T2 = O / (1 - P/2)

那么性能加速就会是 T2/T2,新的运行时间和旧的运行时间的比值。这给到我们

T2 / T1 = (1 - P) / (1 - P/2)
T2 / T1 = 1 - P / (2 - P)

对这个式子作图,我们可以得到如下的图,其中原始的P在x轴上,性能加速在y轴3上:

这个图说明了一切。如果你的P非常高,比如说,需要54分钟来处理一小时的数据,那么将你的集群数量翻倍会使得你新的运行时间是原来的18%,足足有82%的性能加速!这是一个非常违背直觉的结论 —— 我强烈推荐读者们仔细思考这种情况出现的背后的机制。

然后,如果你之前的P没有那么高(例如,需要6分钟的动态运行时间来处理1小时的数据),那么将集群数量翻倍对于运行时间几乎没有效果 —— 可能只有类似6%。由于运行时间被额外开销所主导,这是很合乎情理的,动态运行时间在运行时间中占得很小。

为什么10%的任务失败率会导致我的运行时间上升到原来的300%?

这个问题解释了workflow稳定性的属性。在一个大集群中,你总是会有不同的机器故障,所以任务失败率的峰值不会干掉mission critical系统的性能是非常重要的。关于这个问题的分析会和上一个问题看起来非常相似,除了我们会将动态运行时间变差而不是提高它之外。10%的任务失败率意味着我们需要多运行11%的任务来处理完我们的数据4。由于任务依赖于我们所拥有的数据量,这意味着,我们的“处理一小时数据需要的时间”会上升11%。类似于上一个问题,让我们将T1作为没有任务失败所需要的运行时间,T2作为有任务失败的运行时间

T1 = O / (1 - P)
T2 = O / (1 - 1.11*P)
T2 / T1 = (1 - P) / (1 - 1.11*P)

对此作图,我们得到:


(译著: 上图中X轴为P值,Y轴为花费时间增加的比例)

你可以看到,任务失败对于运行时间的影响在你的集群有“额外容量”减少的情况下戏剧性地增长。所以让保持你的P低是非常重要的。我们可以看到你的P越高,由于任务失败率的增加,你就更有可能进入“毁灭循环”的风险5

如果通过优化掉我的workflow的运行时间的30%而导致运行时间下降了80%?

这个问题是使得我真正找出workflow的运行时间的模型。我曾在一个workflow中有一个荒谬的瓶颈,导致了大概10小时的额外开销6,然后整个workflow的运行时间大概是30小时。在我优化了这个瓶颈(大约占据了30%的运行时间)之后,运行时间像石头一样坠落,最终稳定在6小时(减少了80%)。使用我们的模型,我们可以确定为什么这发生了:

30 = O / (1 - P)
6 = (O - 10) / (1 - P)
O = 12.5, P = 0.58

所以那10小时,占据了workflow中的80%的额外开销,这解释了整个的性能提升。

我的集群中到底需要多少机器可以满足性能和容错的需要?

这基本上是一个费效比分析的练习。我们可以看到,通过增加集群中机器数据来提高性能到回报在减少,而一旦P(“处理一小时数据需要的时间”)下降到30分钟以下(0.5),通过增加机器获得的性能提升是次线性的7。我们也同样看到将P保持得低是很重要得,不然任务失败的增加或者其他的任务使用集群,会严重影响你的运行时间。所以,你需要运行一些数据来确定对你的应用最佳的机器数量8

你应该在Twitter上follow

更新:看看本文的后续内容



1 事实上,这个等式适用于任何批量处理连续数据流的系统。
2 这里假设你的处理是完全分布式的,并且没有持有任何中心点,这对于一个基于Hadoop的workflow来说通常是真的。一个可能的例外是,你所有的tasks都通过一个中心的数据库进行通信。事实上,增加更多的机器会在额外开销(更多的机器去处理)以及数据处理速率(mapper需要将数据分发到更多的reducer中去)有少量的不利影响。对于这个分析的目的来说,我们可以忽略这些。
3 该图通过FooPlot生成。
4 比如说我们通过100个task来处理数据,现在,当你运行这些task的时候,其中有10个会失败。当你重新运行这10个的时候,9个会成功,还有1一个会失败。所以你实际运行了111个task而不是100个,这意味着task的数量增加了11%。
5 或者其他的运行在集群上的东西,比如一次性的查询或者其他的workflow。
6 这是由于Berkeley DB在ext3的文件系统上会生成很多碎片。
7 事实上,我没有展示这个,但是你可以通过模型来自己推导。我把这个作为一个练习留给读者。
8 如果你需要的话,你可以让这个模型变得更加全面。比如,这个模型没有区分新进入的数据和已经存在需要被查询的数据(并且在每个迭代中都会增加)。这个变量必然会影响到你的长期扩展需求,记住它是非常重要的。对于那些知识对于这些workflow的性能有获得一个直觉的目的来说,这不是必须的。


运营一个系统比开发一个系统更重要

互联网公司和软件公司最大的不同在于,即使对于技术团队来说,开发工作也只是一小部分工作,如何使得整个系统运营起来,才是重头戏,这是过去一年感悟最深的一点;而对于像广告质量这样的技术团队来说,运营和开发可以说是完全不可分离的。过去一年,在这点上,走了很多弯路,犯了很多错误,有很多可以总结的地方。从Quora上看到这样一个问题,“What are the keys to operationalizing a machine learning ranking system from an organization / engineering management point of view?”

觉得唯一的一个回答就很好,翻译一下以记之,虽然做不到信达雅,但是基本意思应该能描述对。

这个问题的大概内容是这样的:


如果从组织管理/工程管理的角度来看,什么是将一个机器学习的Ranking系统运营起来的关键点?
对于那些有基于机器学习或者其他方式有Ranking系统的组织,可以保证他们运行平稳,并且随着时间的推移慢慢进步?哪些监控是重要的?如何定义目标,目标由谁来定义?如何分辨效果的提升来自于Feature工程团队还是核心算法上的提升?哪些严重依赖于Ranking的组织如何为这些来构造他们的流程?


目前唯一可见的回答,来自前Google的工程师Brandon Ballinger:


我之前在Google做语音识别和广告,这两者都以机器学习作为他们的核心。下面是我从“野外”学到的如何运营一个机器学习系统的一些重要的经验和教训

  • 将你的成功的指标定义为让用户高兴。传统的精确性的指标,比如precision, square error, ROC等等并不能捕捉到那些你真正关心的——用户对于你的模型是如何反应的。例如,如果你运营一个广告系统,你的指标应该是每个PV的收入以及点击率。完全有可能,由于辛普森悖论,你有一个error rate减少了的模型,但是他带来的收入却降低了。
  • 核心指标下降是很严重的问题。你需要把核心指标的大幅下降当成和一个服务器当机或者数据库中数据损坏一样严重的问题。Oncall的人需要尝试尽快诊断问题,并且必要时把别人拉进来一起解决问题
  • 对每次Model的Launch做A/B测试。你应当始终并行运行两个模型,给不同的用于用不同的模型,然后比较用户行为。这是唯一可以确保你知道你在做正确的事情的办法。
  • 小心丑小鸭效应。机器学习系统会在从自己的错误中学习的过程中变得更加准确。这意味着,新的模型有一个与生固来的缺陷:你的历史训练数据包含着过去模型中的错误,但不是你新训练的模型的错误。导致你的新模型一开始可能像一个丑小鸭,但是如果你用他来决策用户看到的内容,他最后会变成一个漂亮的天鹅。你可以通过将一小部分流量切给新模型,来部分抵消丑小鸭效应,然后慢慢随时间提高这个百分比。
  • 使得算法对于噪声Feature健壮。类似于L1 regularization这样的技术可以使得你的机器学习算法对Feature进行剪枝,修剪掉那些对于预测的准确性贡献很小的Feature。这可以帮助你很好地拆分你的团队:一些人专注于算法,另一些人写Feature喂给算法。那些寻找Feature的人,可以直接“把东西扔进锅里”,然后让算法找出他们究竟好还是不好。(类似的,你应该通过确保合理的收敛性来选择一个算法)
  • 你可以部分地将架构和算法团队解耦合。基本上,大部分机器学习算法都是在累计一个分布式的hash table的统计信息,然后将这些统计信息合并成一个分数。开发一个分布式的hash table是和开发一个用于累计的算法不同的任务,可以由不同的子Team完成。然后,让这些人一起工作仍然是很重要的。例如,一些算法在写入和写入可用之前延时的“最终一致”的系统中(例如Dynamo),会有所波动。所以这只是一个部分解耦合,你仍然需要团队中有连接架构和算法的“桥梁”人员。
  • 在在线系统和批处理系统之间选择的时候要小心。一个在线系统可以实时学习,对于新的用户行为,可以在发生之后的几分钟内有所反应。但是这回带来巨大的开销,一个在线系统需要2-3倍的时间区开发和维护,而且他对瞬间的变化更加敏感。举个例子,如果你突然接收到了很多垃圾信息,这些噪声会立刻被吸收进你的系统并且开始降低用户的体验。类似的,一台机器如果没有可运行的内容了,如果网络连接中断了,或者一个特定的机器变慢了(导致Feature“歪了”),模型的某个输入开始生产垃圾,等等。
  • 为所有东西打上版本。一个训练过的模型依赖于稳定的标识,如果你更改了某个标识,模型立刻就过时了。例如,如果你将用户的语言(“en-us”)作为一个feature。那么当有些人提交了一个一行的修改,用下划线替换了短横线(“en_us”)。这使得模型立刻“忘记”了所有从”en-us”中学到的,使得所有的语言看起来都是一样的。所以为所有会在你模型中生成标识的代码和数据的变更都打上版本。
  • 你需要咬碎100-1000倍于实时的数据。如果你的训练数据来自于整整一年的历史数据,然后你的学习过程是10倍于实时,那他需要一个月来测试一个新Feature。把所有的东西都写成Map-Reduce或者Storm Topology使得你可以在数据变得足够大的时候仍然可以扩展。

看了一下,里面有不少错误我们都犯过,包括过去一个Q我们觉得由于团队小应该快点冲而省略掉的东西,现在看来也还是省略不掉的,而之前做对的部分也都是来自老大们的经验,希望今年少走弯路效果好啊。


建议的程序员学习LDA算法的步骤

这一阵为了工作上的关系,花了点时间学习了一下LDA算法,说实话,对于我这个学CS而非学数学的人来说,除了集体智慧编程这本书之外基本没怎么看过机器学习的人来说,一开始还真是摸不太到门道,前前后后快要四个月了,算是基本了解了这个算法的实现,记录一下,也供后来人快速入门做个参考。

一开始直接就下了Blei的原始的那篇论文来看,但是看了个开头就被Dirichlet分布和几个数学公式打倒,然后因为专心在写项目中的具体的代码,也就先放下了。但是因为发现完全忘记了本科学的概率和统计的内容,只好回头去看大学时候概率论的教材,发现早不知道借给谁了,于是上网买了本,花了几天时间大致回顾了一遍概率论的知识,什么贝叶斯全概率公式,正态分布,二项分布之类的。
后来晚上没事儿的时候,去水木的AI版转了转,了解到了Machine Learning的圣经PRML,考虑到反正也是要长期学习了,搞了电子版,同时上淘宝买了个打印胶装的版本。春节里每天晚上看一点儿,扫了一下前两章,再次回顾了一下基本数学知识,然后了解了下贝叶斯学派那种采用共轭先验来建模的方式。于是再次尝试回头去看Blei的那篇论文,发现还是看不太懂,于是又放下了。然后某天Tony让我准备准备给复旦的同学们share一下我们项目中LDA的使用,为了不露怯,又去翻论文,正好看到Science上这篇Topic Models Vs. Unstructured Data的科普性质的文章,翻了一遍之后,再去PRML里看了一遍Graphic Models那一张,觉得对于LDA想解决的问题和方法了解了更清楚了。之后从search engine里搜到这篇文章,然后根据推荐读了一部分的Gibbs Sampling for the Uninitiated。之后忘了怎么又搜到了Mark Steyvers和Tom Griffiths合著的Probabilistic Topic Models,在某个周末往返北京的飞机上读完了,觉得基本上模型训练过程也明白了。再之后就是读了一下这个最简版的LDA Gibbs Sampling的实现,再回过头读了一下PLDA的源码,基本上算是对LDA有了个相对清楚的了解。

这样前前后后,也过去了三个月,其实不少时间都是浪费掉的,比如Blei的论文在没有任何相关知识的情况下一开始读了好几次,都没读完而且得到到信息也很有限,如果重新总结一下,我觉得对于我们这些门外汉程序员来说,想了解LDA大概需要这些知识:

  • 有基本的概率论的知识,这个拿个大学的课本大概翻一下就好了
  • PRML的前两章和Graphic Model那部分需要浏览一下,了解一下所谓的贝叶斯学派的方法论,然后理解共轭先验的概念,以及Graphic Model的图形表达的意思。
  • 了解一下Gibbs Sampling的概念
  • 直接读Steyvers的Probabilistic Topic Models,没必要一定去读Blei的论文,开创性的论文不一定好读,最典型的例子就是Paxos了,30多页里多少都是希腊人名,对于英文不够精通,学术训练比较少的工程师来说,其实很痛苦,意义也不大。
  • 对照着Probabilistic Topic Models直接看LdaGibbsSampling.java的源码

基本上这样一圈下来,基本概念和算法实现都应该搞定了,当然,数学证明其实没那么容易就搞定,但是对于工程师来说,先把这些搞定就能干活了,这个步骤并不适合各位读博士发论文的同学们,但是这样先看看也比较容易对于这些数学问题的兴趣,不然,成天对这符号和数学公式,没有整块业余时间的我是觉得还是容易退缩放弃的。

发现作为工程师来说,还是看代码比较有感觉,看实际应用的实例比较有感觉,看来不能把大部分时间花在PRML上,还是要多对照着代码看。


Rails Magazine可以下载了

这里可以下载,呃,不知道2009会不会是Rails再次爆发的一年?


你需要天使投资么?

广告一则:

Trilogy Ventures 创业沙龙(互联网 手机应用方向)
Trilogy Ventures
时间:3月2日18:00-21:00
地点:上海交通大学闵行校区光彪楼

欢迎去玩。

Update:补充一下,Trilogy去年和前年各投了一家,今年估计机会会多一些,因为专门成立了Trilogy Ventures China专门投早期,此外,当天Speech胜者貌似有3000块奖金。


重要的是持续改进

其实开发过程中重要的不是采用了什么样的工具,流程甚至理念,重要的是作为开发人员开发团队应该把精力花在是你的开发变得更好上。无论是你喜欢XP这样的理念,还是因为喜欢Joel而简单地使自己的流程符合Joel Test上,这些问题出现的本质是让你把精力放在把事情变好而不是只是在做做事情。每日构建、单步构建和自动测试是把你从每天“做做构建”、“做做测试”这样的事务中解脱出来;使用源代码管理是让你避免浪费时间去反反复复做做过的事情;不要加班是让你意识到你要做得是效率上得改进而不是重复劳动来做体力活。无论是不是已经有了这些流程,开发人员始终可以做的是每天把精力花在使今天比昨天好上面。只要你有意识去花时间进行改建,那么你之前没有用源代码管理你自然会发现花了很多时间做出来得东西被猫吃了;你没有为每个bug添加test case自然会发现同一个bug会反复出现;你没有用单步构建和每日构建自然会发现每次要发布得时候都会手忙脚乱要通宵加班。而有了这样的意识,即使你已经符合了Joel Test的12条你仍然可以发现你可以使用自动发布来让客户看到每天的开发结果;你可以使用创建VM Image来解决不同开发人员开发环境不同,可能有遗漏的依赖没有放入源代码管理中;你会发现Git比SVN更好地解决了开branch的开销和常常需要它记录一部分不完整代码的问题。

所以,重要的是始终在开发过程中保持持续改进的意识,工具和方法论反而是第二位的。


Tool到用时方恨多

越来越发现,这个时代实在是工具太多了,多到有点让人讨厌了。随便干点什么,都要会一堆东西;学点新东西,发现要能用要再学一堆新东西。最近用ROR做点东西,发现真要做点Production Quality的东西,至少要用一堆工具,比如New Relic,比如Capistrano,然后如果想要用得爽还有一堆RSpec等等的东西可以用,更不用说HTML/CSS/Javascript/MySQL/Git这些基本的工具。如果在Java世界的话,用的东西更是多到让人厌恶。比起要用的工具,人生时间实在是短到让人绝望,不过没办法,只能慢慢玩了……


配置Rails应用

译者注:本文原文来自Rails Guides,版本为January 3, 2009: First reasonably complete draft by Mike Gunderloy。本文根据原文,按照Creative Commons Attribution-Share Alike 3.0授权。

本指南涵盖了可用于Rails应用中的配置和初始化特性。通过本指南,你可以
1.调整Rails应用的行为
2.添加在应用启动阶段运行的代码

本指南的第一版是基于Rails 2.3的源代码撰写的。虽然不保证向前兼容,但是本指南涵盖的信息可广泛地用于Rails 2.2。

1. 初始化代码的位置

Rails(至少)提供了五个好位置来放置初始化代码:

  • 预初始化器(preinitializers)
  • environment.rb
  • 针对特定环境的配置文件
  • 初始化器(load_application_initializers)
  • 后初始化器(After-Initializers)

2. 使用预初始化器

Rails允许你使用一个预初始化器,在框架自身被装载前来运行代码。如果你把代码存放在RAILS_ROOT/config/preinitializer.rb中,这些代码会在任何框架组件(Active Record, Action Pack, and so on)之前第一个被装载。如果你想要改变任何初始化过程中某些类的行为,你可以在这里(preinitializers文件)做。

3. 配置Rails组件

基本上来说,配置Rails所做的工作就是配置Rails自身以及配置Rails的各个组件。environments.rb文件以及针对特定环境的配置文件(比如config/environments/production.rb)允许你设定各个组件的设置。例如,默认的Rails2.3中的environment.rb文件中包含这样一条配置:

config.time_zone='UTC'

这是一条Rails自身的设置。如果你想要设定Rails的各个组件的设置,你也可以通过同样的这个config对象来做:

config.active_record.colorize_logging = false

Rails会使用这条设置来配置Active Record

3.1 配置Active Record

ActiveRecord::Base包含许多种配置选项:

  • logger接收一个符合Log4r或者Ruby1.8.x Logger类的接口的logger,该logger会传递给所有的新创建的数据库连接。你可以在ActiveRecord的model类或者其实例上调用logger来获取该logger。把该值设为nil会经用日志功能。
  • primary_key_prefix_type用于设置主键列的命名。Rails默认主键列的名字是id(该配置无需设定),但是你可以有两个别的选择:
    • :table_name会另Customer类的主键为customerid
    • :table_name_with_underscore会另Customer类的主键为customer_id
  • table_name_prefix使你可以在表名的前面设置添加一个全局的字符串。如果你将该值设为northwest_,那么Customer类会映射到northwest_customers表。默认该值为一个空字符串
  • table_name_prefix使你可以在表明的后面设置添加一个全局的字符串。如果你将该值设为_northwest,那么Customer类会映射到customers_northwest表。默认该值为一个空字符串。
  • pluralize_table_names规定了Rails使用单词的单数还是复数来命名数据库中的表名。如果该值被设置为true(默认值),那么Customer类会使用customers表,如果该值被设置为false,那么Customer类会使用customer
  • colorize_logging(默认为true)规定了在ActiveRecord的日志信息中是否使用彩色的ANSI代码。
  • default_timezone决定了在从数据库中读取日期和时间时,是使用Time.local(如果设置为:local)还是Time.utc(如果设置为:utc)。默认值为:local
  • schema_format控制了从数据库中导出数据库模式到文件中所用的格式。选项:ruby(默认值)会使用一个数据库无关的migrations,选项:sql会使用(可能依赖于特定数据库的)SQL指令。
  • timestamped_migrations控制了migrations的数字是使用连续的整数还是时间戳。默认值为true,代表使用时间戳,这在优多人共同开发同一个应用的时候是一个更好的选择。
  • lock_optimistically控制了ActiveRecord是否使用乐观锁。默认该值为true
  • MySQL适配器增加了一个额外的配置选项:

  • ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans控制了ActiveRecord是否认为MySQL数据库中所有的tinyint(1)列为布尔值。默认该值为true
  • 数据库模式的导出器(schema dumper)增加了一个额外的配置选项:

  • ActiveRecord::SchemaDumper.ignore_table接受一个表名的数组,该数组中的表不应包含在任何生成的数据库模式文件中。该设置在ActiveRecord::Base.schema_format == :ruby时才会起作用。

3.2 配置Action Controller

ActionController::Base包含了一些配置设定:

  • asset_host在所有AssetHelper中的辅助方法所生成URL的前面添加一个字符串。这是设计用来将所有的javascript,CSS以及图片文件转移到一个不同的主机(asset host)上。
  • consider_all_requests_local通常在开发环境中被设为true而在生产环境中被设为false;如果它被设为true,则任何错误都会导致详细的调试信息会被导出到HTTP response中。如果你想做更细致的控制,将其设为false然后实现local_request?方法来决定哪些requests会在出错的情况下提供调试信息。
  • allow_concurrency为了允许同步(线程安全的)的action处理,该值应当被设为true。默认情况下该值为false。你通常不应当直接修改这个配置,因为你需要一系列的其他调整使得线程安全模式可以正确地工作做。你可以在production.rb文件中简单地调用config.threadsafe!,它会为你做所有需要的调整。

  • Threadsafe操作和开发模式下的一些功能不兼容。特别是自动的依赖装载和类重装载会在你调用config.threadsafe!之后被自动禁用


  • param_parsers让你可以设置一个handlers的数组,用于提取HTTP requests中的信息并将它放到params哈希中。默认情况下,multipart forms,URL-encoded forms,XML以及JSON的parsers是被激活的。
  • default_charset设定了所有render默认使用的字符集。默认值为"utf-8"
  • logger接收一个符合Log4r或者Ruby1.8.x Logger类的接口的logger,可以被用来在Action Controller中记录日志信息。把该值设为nil会禁用日志功能。
  • resource_action_separator用来设置RESTful url中resource和action之间的分隔符。默认为"/"。
  • resource_path_names用来设置一个RESTful action默认的名字的哈希。默认情况下,new action被命名为new而edit action被命名为edit
  • request_forgery_protection_token用来设置RequestForgery的参数名。调用protect_from_forgery会将它设为:authenticity_token
  • optimise_named_routes打开了一些生成路径表(routes table)的优化。它默认被设为true
  • use_accept_header用于设置确定response格式的规则。如果该值设为true(默认值),则respond_to方法和Request#format方法会根据Accept Header返回相应格式的response。弱该值设为false则request格式会单独由params[:format]来决定。如果没有format参数,则response的格式会根据request是否是一个Ajax request来决定返回HTML还是Javascript。
  • allow_forgery_protection用于设置启用还是禁用CSRF保护。默认在测试模式下该值为false而在其他模式下为true
  • relative_url_root可以被用来告诉Rails你将会将应用部署到一个子目录。默认值为ENV['RAILS_RELATIVE_URL_ROOT']
  • 缓存功能代码将会增加两个额外的设置:

  • ActionController::Caching::Pages.page_cache_directory用于设置Rails为你的Web服务器创建缓存页面的位置。默认值为Rails.public_path(通常该值被设为RAILS_ROOT"/public"+)。
  • ActionController::Caching::Pages.page_cache_extension用于设置生成的缓存页面文件的扩展名(如果进来的requests已经带了扩展名则这里的扩展名会被忽略)。默认值为.html
  • 分发器(dispatcher)包含了这样一条配置

  • ActionController::Dispatcher.error_file_path给定了Rails用来寻找出错文件如404.html的路径。默认值为Rails.public_path
  • 使用Active Record的session存储可以这样来配置

  • CGI::Session::ActiveRecordStore::Session.data_column_name用于设置用来存储session数据的列名。默认值为'data'。

3.3 配置Action View

Action View上只有不多的几个配置选项,从ActionView:Base的四个开始:

  • debug_rjs决定RJS response是否会被包装在try/catch块中,并alert()被捕捉的异常(并重新将其抛出)(译者注:即是否对rjs使用debug模式,使得rjs的异常在开发工程中会被alert显示出来)。默认值为false
  • warn_cache_misses是否在一个action的结果遇到view路径中缓存未命中时Rails会显示一个警告。默认值为false
  • field_error_proc提供了一个HTML生成器来显示来自于Active Record的错误。默认值为Proc.new {|html_tag, instance| "#{html_tag}"}
  • default_form_builder告诉Rails默认实用的表单构建器(form builder)。默认值为ActionView::Helpers::FormBuilder
  • ERB Template handler提供了一个额外的选项

  • ActionView::TemplateHandlers::ERB.erb_trim_mode提供了ERB使用的修剪模式(trim mode)。默认为'-'。查看ERB文档获得更多信息

3.4 配置Action Mailer

ActionMailer::Base上由一系列可用的配置:

  • template_root提供了Action Mailer模版的根目录
  • logger接收一个符合Log4r或者Ruby1.8.x Logger类的接口的logger,用来记录来自Action Mailer的日志信息。设为nil则禁用日志记录功能。
  • smtp_settings用来为smtp邮件发送方式提供详细的配置信息。它接受一个哈希组成的选项,包括如下任意的选项:
    • :address允许你使用一个远端的邮件服务器。请将它从默认的"localhost"改为你所用的邮件服务器。
    • :port如果你使用的邮件服务器不在25端口上运行,请修改这个配置。
    • :domain如果你需要指定一个HELO域名,请在这里指定。
    • :user_name如果你的邮件服务器需要认证,请在这里设置用户名
    • :password如果你的邮件服务器需要认证,请在这里设置密码
    • :authentication如果你的邮件服务器需要人证,你需要在此指定认证的类型。它应该是:plain:login,和:cram_md3三个符号其中之一。
  • sendmail_settings允许你对sendmail邮件发送方式进行详细的配置。它接受一个哈希组成的选项,包括如下任意的选项:
    • :location—sendmail可执行文件的位置。默认为/usr/sbin/sendmail
    • :arguments—命令行参数。默认值为-i -t
  • raise_delivery_errors设置如果邮件发送没能完成是否需要抛出一个错误。默认值为true
  • delivery_method设置邮件发送方式。可以使用的值有:smtp(默认值),:sendmail以及:test
  • perform_deliveries指定邮件是否要真得发送出去。默认值为:true,测试时可以方便地设为false
  • default_charset设定Action Mailer用于编码邮件主题和内容的字符集。默认为utf-8
  • default_content_type设置消息的主体所使用的默认的content type。默认值为text/plain
  • default_mime_version设置消息实用的默认的MIME版本。默认值为1.0
  • default_implicit_parts_order—当一个消息被隐式地构建(例如,邮件的多部分通过模版组装,而模版通过文件名设定了content type),这个变量控制多个部分的顺序。默认为["text/html", "text/enriched", "text/plain"]
  • 。数组中先出现的项在邮件客户端中有较高的优先级,并在mime编码消息的最后出现。

3.5 配置Active Resource

ActiveResource::Base中有一个配置可以设置:
logger接收一个符合Log4r或者Ruby1.8.x Logger类的接口的logger,用来从Active Resource中记录日志信息。设为nil时禁用日志功能。

3.6 配置Active Support

Active Support中有一些配置选项:

  • ActiveSupport::BufferedLogger.silencer设为false则禁用了对代码块的缄默日志(silence logging)功能(译者注:可以参看http://api.rubyonrails.com/classes/ActiveSupport/BufferedLogger.html#M001499,缄默日志使得silence方法可以对指定的代码块屏蔽日志功能或者改变日志的level)。默认值为true
  • ActiveSupport::Cache::Store.logger设定了缓存存储操作所使用的logger。
  • ActiveSupport::Logger.silencer设为false则禁用了对代码块的缄默日志(silence logging)功能。默认值为true

配置 Active Model

Active Model目前有一个单独的配置设置:

  • ActiveModel::Errors.default_error_messages是一个包含所有验证错误消息的数组。

使用初始化器

在装载了框架以及任何gems和插件之后,Rails会开始装载初始化器(initializers)。一个初始化器是在你的应用中任何存储在/config/initializers中的ruby代码。你可以使用初始化器来存放哪些应该整个框架和插件装载后进行的配置设置。

如果你喜欢的话,你可以使用子目录来组织你的初始化器,因为Rails会渗入到initializers目录的整个文件目录层级中去。


如果你的初始化器有任何的顺序依赖关系,你可以通过命名来控制它们装载的顺序。例如,01_critical.rb会在02_normal.rb之前装载。


使用一个后初始化器(After-Initializer)

后初始化器(如同你所猜想的)会在所有的初始化器装载之后运行。你可以通过在任何Rails配置文件中设立config.after_intialize来支持一个after_initialize代码块(或者一个这种代码块的数组)。

config.after_initialize do
    SomeClass.init
end
你应用的一部分,特别是观察者(observers)和路径(routing)在after_initialize代码块被调用时还没有建立好。


Rails环境设置

Rails的一部分配置可以通过外部环境变量来配置。以下的环境变量会被Rails的不同部分识别出来。

  • ENV['RAILS_ENV']定义了Rails所运行在的环境(production,development,test等等)
  • ENV['RAILS_RELATIVE_URL_ROOT']被路径代码用来在你部署你的应用到一个子目录时识别出URL。
  • ENV['RAILS_ASSET_ID']会覆写默认的Rails生成的用户可下载资产的缓存失效的时间戳。
  • ENV['RAILS_CACHE_ID']ENV['RAILS_APP_VERSION']用来生成Rails缓存代码中的扩展的缓存键。这可以允许你对同一个应用有多个分开的缓存。
  • ENV['RAILS_GEM_VERSION]RAILS_GEM_VERSION没有在environment.rb文件中定义时,定义了使用的Rails gems的版本。

functional tests中的form_authenticity_token

又是一个前一阵遇到的很tricky的问题。

Rails2.0中加入了form_authenticity_token来防止部分的cross-site的攻击,ActionView中默认的form_for标签会自动加入类似于

的代码,如果你自己使用form_tag来创建form的话,可以用类似于

<input type="hidden" name="authenticity_token" value="<%= "#{form_authenticity_token}" %>" />

的代码来加入这个隐藏的form中的authenticity_token。

但是手工加入这个token在functional tests中会带来问题,因为functional tests中是把allow_forgery_protection关掉的
可以在environments/test.rb中看到这样的配置

config.action_controller.allow_forgery_protection    = false

但是authenticity现在是hardcode在代码中,所以跑functional tests通常会遇到这样的错误

ActionView::TemplateError: No :secret given to the #protect_from_forgery call.  Set that or use a session store capable of generating its own keys (Cookie Session Store).

一种解决办法是在view中加入当前all_forgery_protection状态的判断代码,比如

<% if  protect_against_forgery? %>
<input type="hidden" name="authenticity_token" value="<%= "#{form_authenticity_token}" %>" />
<% end %>

但是有位同学用了个更巧妙的hook的办法,在test_helper.rb中hook掉form_authenticity_token,如下

module ActionController
  module RequestForgeryProtection
    def form_authenticity_token
      return "form_authenticity_token OVERRIDE!"
    end
  end
end

我比较喜欢后一种啦。


在新macbook上安装Rails+MySQL

嗯,嗯,嗯,这个问题前一阵来来回回折腾过不少时间,主要的问题在于

  • Macbook 466是64位的CPU
  • Leopard自带的Ruby是32位的版本
  • 如果安装的MySQL是64位版本的,就需要自己编译个Ruby
  • 要不你就像我这样还是装个32位的MySQL吧

MySQL安装的注意事项

  • 建议安装Startup Item,这样就开机自启动了
  • 如果装了Startup Item但是不想开机自启动,那就修改/etc/hostconfig,把MYSQLCOM=-YES-改成MYSQLCOM=-NO-
  • 默认的encoding是latin1,所以要改一下配置文件,按下面这么办就行了
   $ sudo cp /usr/local/mysql/support-files/my-small.cnf /etc/my.cnf
   $ sudo vim /etc/my.cnf
   # 在对应的节点添加如下信息
   [client]
   default-character-set = utf8
   [mysqld]
   character-set-server = utf8
   default-table-type = innodb 

记得数据库创建要在改完配置重启mysqld之后,不然创建的数据库默认还是latin1的

然后就是gem安装mysql的驱动了,抛弃掉Rails自带的过时的ruby版本的驱动了

sudo env ARCHFLAGS="-arch i386" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

如果您装的是64位的Ruby和MySQL那么改一个参数吧

sudo env ARCHFLAGS="-arch x86_64" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config