1 Introduction简介
大家总是说 Rails 好慢啊,这差不多已经成为 Ruby and Rails 社区里的一个老生常谈的问题了。然而实际上这个说法并不正确。只要正确使用 Rails,把你的应用运行速度提升 10 倍并不困难。那么如何优化你的应用呢,我们来了解下面的内容。
1.1 优化一个 Rails app 的步骤导致你的 Rails 应用变慢无非以下两个原因:
在不应该将 Ruby and Rails 作为首选的地方使用 Ruby and Rails。(用 Ruby and Rails 做了不擅长做的工作)
过度的消耗内存导致需要利用大量的时间进行垃圾回收。
Rails 是个令人愉快的框架,而且 Ruby 也是一个简洁而优雅的语言。但是如果它被滥用,那会相当的影响性能。有很多工作并不适合用 Ruby and Rails,你最好使用其它的工具,比如,数据库在大数据处理上优势明显,R 语言特别适合做统计学相关的工作。
内存问题是导致诸多 Ruby 应用变慢的首要原因。Rails 性能优化的 80-20 法则是这样的:80% 的提速是源自于对内存的优化,剩下的 20% 属于其它因素。为什么内存消耗如此重要呢?因为你分配的内存越多,Ruby GC(Ruby 的垃圾回收机制)需要做的工作也就越多。Rails 就已经占用了很大的内存了,而且平均每个应用刚刚启动后都要占用将近 100M 的内存。如果你不注意内存的控制,你的程序内存增长超过 1G 是很有可能的。需要回收这么多的内存,难怪程序执行的大部分时间都被 GC 占用了。
2 我们如何使一个 Rails 应用运行更快?有三种方法可以让你的应用更快:扩容、缓存和代码优化。
扩容在如今很容易实现。Heroku 基本上就是为你做这个的,而 Hirefire 则让这一过程更加的自动化。你可以在这个了解到更多有关自动扩容的内容。其它的托管环境提供了类似的解决方案。总之,可以的话你用它就是了。但是请牢记扩容并不是一颗改善性能的银弹。如果你的应用只需在 5 分钟内响应一个请求,扩容就没有什么用。还有就是用 Heroku + Hirefire 几乎很容易导致你的银行账户透支。我已经见识过 Hirefire 把我一个应用的扩容至 36 个实体,让我为此支付了 $3100。我立马就手动吧实例减容到了 2 个, 并且对代码进行了优化.
Rails 缓存也很容易实施。Rails 4 中的块缓存非常不错。Rails 文档 是有关缓存知识的优秀资料。另外还有一篇 Cheyne Wallace 有关 Rails 性能的文章 也值得一读。如今设置 Memcached 也简单。不过同扩容相比,缓存并不能成为性能问题的终极解决方案。如果你的代码无法理想的运行,那么你将发现自己会把越来越多的资源耗费在缓存上,直到缓存再也不能带来速度的提升。
让你的 Rails 应用更快的唯一可靠的方式就是代码优化。在 Rails 的场景中这就是内存优化。而理所当然的是,如果你接受了我的建议,并且避免把 Rails 用于它的设计能力范围之外,你就会有更少的代码要优化。
2.1 避免内存密集型Rails特性
Rails 一些特性花费很多内存导致额外的垃圾收集。列表如下。
2.1.1 序列化程序
序列化程序是从数据库读取的字符串表现为 Ruby 数据类型的实用方法。
class Smth < ActiveRecord::Base
serialize :data, JSON
end
Smth.find(...).data
Smth.find(...).data = { ... }
But convenience comes with 3x memory overhead. If you store 100M in data column, expect to allocate 300M just to read it from the database.
它要消耗更多的内存去有效的序列化,你自己看:
class Smth < ActiveRecord::Base
def data
JSON.parse(read_attribute(:data))
end
def data=(value)
write_attribute(:data, value.to_json)
end
end
这将只要 2 倍的内存开销。有些人,包括我自己,看到 Rails 的 JSON 序列化程序内存泄漏,大约每个请求 10% 的数据量。我不明白这背后的原因。我也不知道是否有一个可复制的情况。如果你有经验,或者知道怎么减少内存,请告诉我。
2.1.2 活动记录
很容易与 ActiveRecord 操纵数据。但是 ActiveRecord 本质是包装了你的数据。如果你有 1g 的表数据,ActiveRecord 表示将要花费 2g,在某些情况下更多。是的,90% 的情况,你获得了额外的便利。但是有的时候你并不需要,比如,批量更新可以减少 ActiveRecord 开销。下面的代码,即不会实例化任何模型,也不会运行验证和回调。
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
后面的场景它只是执行 SQL 更新语句。