人们总说Rails慢,这几乎成了Ruby和Rails社区的一个普遍问题。然而事实上,这种说法是不正确的。如果正确使用Rails,让应用程序运行速度提高10倍并不难。那么如何优化你的应用呢,下面的内容我们就来了解一下。1.1优化Rails应用程序的步骤Rails应用程序变慢的原因只有两个:在不应该是首选的地方使用Ruby和Rails。(用Ruby和Rails做一个不擅长的工作)内存消耗过大导致垃圾回收的时间很多。Rails是一个令人愉快的框架,而Ruby是一种简单而优雅的语言。但如果它被滥用,它会大大影响性能。有很多工作不适合Ruby和Rails。你最好使用其他工具。比如数据库在大数据处理方面优势明显,R语言特别适合统计相关的工作。内存问题是许多Ruby应用程序运行缓慢的首要原因。Rails性能优化的80-20法则是这样的:80%的加速来自优化内存,剩下的20%属于其他因素。为什么内存消耗如此重要?因为分配的内存越多,RubyGC(Ruby的垃圾回收机制)要做的工作就越多。Rails已经占用了大量的内存,平均每个应用程序刚启动就占用近100M的内存。如果不注意内存控制,很有可能你的程序内存增长超过1G。有这么多内存需要回收,难怪程序的大部分执行时间都花在了GC上。2我们如何使Rails应用程序运行得更快?可以通过三种方法使您的应用程序更快:缩放、缓存和代码优化。今天扩展很容易。Heroku基本上会为您完成这些工作,而Hirefire甚至可以使该过程更加自动化。您可以在此处了解有关自动缩放的更多信息。其他托管环境提供类似的解决方案。无论如何,如果可以的话,请使用它。但请记住,缩放并不是提高性能的灵丹妙药。如果您的应用程序只需要在5分钟内响应请求,则缩放是无用的。此外,使用Heroku+Hirefire几乎可以很容易地透支你的银行账户。我已经看到Hirefire将我的一个应用程序扩展到36个实体,并为此花费了3100。我立即手动将实例大小减小到2并优化了代码。Rails缓存也很容易实现。Rails4中的块缓存非常好。Rails文档是获取缓存知识的绝佳资源。另外值得一读的是CheyneWallace关于Rails性能的文章。现在设置Memcached也很简单。但是,与扩容相比,缓存并不能成为解决性能问题的最终方案。如果您的代码没有以最佳方式运行,您会发现自己在缓存上花费了越来越多的资源,直到缓存不再提供速度提升。使Rails应用程序更快的唯一可靠方法是代码优化。在Rails上下文中,这是内存优化。当然,如果您接受我的建议并避免使用Rails超出其设计目的,那么您需要优化的代码就会更少。2.1避免内存密集型Rails功能一些Rails功能占用大量内存并导致额外的垃圾收集。名单如下。2.1.1序列化程序序列化程序是将从数据库读取的字符串表示为Ruby数据类型的实用方法。classSmth:tags)>0.058secThiscodehasTheproblem,它为每个选项卡创建对象,这会占用大量内存。另一种解决方案是在数据库中预加载标签。tasks=Task.select<<-END*,array(selecttags.namefromtagsinnerjointasks_tagson(tags.id=tasks_tags.tag_id)wheretasks_tags.task_id=tasks.id)astag_namesEND>0.018秒这只需要内存存储一个额外的列,带有数组标签。难怪它快了3倍。2.2.2数据收集数据收集是指任何总结或分析数据的代码。这些操作可以是简单的汇总,也可以是更复杂的东西。以团体排名为例。假设我们有一个员工、部门和薪水的数据集,我们想计算员工薪水在一个部门中的排名。SELECT*FROMempsalary;部门名称|empno|薪水------------+--------+--------发展|6|6000发展|7|4500发展|5|4200人|2|3900人|4|3500销量|1|5000销量|3|4800你可以用Ruby计算排名:salaries=Empsalary.allsalaries.sort_by!{|s|[s.depname,s.salary]}key,counter=nil,nilsalaries.each做|s|ifs.depname!=keykey,counter=s.depname,0endcounter+=1s.rank=counterendEmpsalarytable100K数据程序在4.02秒内完成。替代Postgres查询,使用窗口函数在1.1秒内完成4次以上相同的工作。SELECTdepname,empno,salary,rank()OVER(PARTITIONBYdepnameORDERBYsalaryDESC)FROMempsalary;部门名称|empno|工资|排名------------+--------+--------+------发展|6|6000|1发展|7|4500|2发展|5|4200|3名人员|2|3900|1名人员|4|3500|2销售|1|5000|1销售|3|4800|24倍的加速已经令人印象深刻,有时你会得到更多,高达20倍。我自己经历的一个例子。我有一个包含60万行数据的3DOLAP多维数据集。我的程序进行切片和聚合。在Ruby中,在1G内存上完成大约需要90秒。等效的SQL查询在5中完成。2.3优化Unicorn如果您使用的是Unicorn,则将应用以下优化技术。Unicorn是Rails框架中最快的Web服务器。但是你仍然可以让它运行得更快一点。2.3.1预加载AppUnicorn可以在创建新的worker进程之前预加载Rails应用程序。这有两个优点。首先,主线程可以通过写时复制友好的GC机制(Ruby2.0及以上)共享内存数据。操作系统将透明地复制这些数据,以防止它被工人修改。其次,预加载减少了工作进程的启动时间。Railsworker进程重启很常见(稍后会详细介绍),因此worker重启得越快,我们可以预期的性能就越好。如果需要开启应用的预加载,只需要在unicorn的配置文件中添加一行:preload_apptrue2.3.2Request请求之间的GC请注意GC的处理时间最多会占到50申请时间的百分比。这不是唯一的问题。GC通常是不可预测的,并且会在您不希望它运行时触发它运行。那么,你用它做什么?首先我们会想,如果我们完全禁用GC呢?这似乎是一个非常糟糕的主意。你的应用程序很可能会很快填满1G的内存,而你却没有及时注意到。如果您的服务器还同时运行多个worker,您的应用程序将很快耗尽内存,即使您的应用程序位于自托管服务器上也是如此。更不用说只有512M内存限制的Heroku了。其实我们有更好的办法。所以如果我们无法避免GC,我们可以尝试让GC运行的时机尽可能的明确,在空闲时间运行。例如,在两个请求之间,运行GC。这很容易通过配置Unicorn来实现。对于Ruby2.1之前的版本,有一个名为OobGC的独角兽模块:require'unicorn/oob_gc'use(Unicorn::OobGC,1)#“1”表示“强制GC在1个请求后运行”对于Ruby2.1及更高版本,最好使用gctools(https://github.com/tmm1/gctools):require'gctools/oobgc'use(GC::OOB::UnicornMiddleware)但是在请求之间运行GC时有一些注意事项。最重要的是,这种优化技术是可感知的。也就是说,用户会明显感受到性能的提升。但是服务器需要做更多的工作。与在需要时运行GC不同,此技术需要服务器频繁运行GC。因此,您需要确保您的服务器有足够的资源来运行GC,并且在其他worker运行GC时有足够的worker来处理用户请求。2.4增长有限我已经向您展示了一些占用1G内存的应用程序示例。如果你有足够的内存,那么占用这么大的内存问题不大。但Ruby可能不会将此内存返回给操作系统。让我解释一下为什么。Ruby通过两个堆分配内存。所有的Ruby对象都存储在Ruby自己的堆中。每个对象占用40个字节(在64位操作系统上)。当对象需要更多内存时,它会在操作系统的堆上分配内存。当对象被垃圾回收和释放时,操作系统中占用的堆内存会归还给操作系统,而Ruby自身堆中占用的内存会简单地标记为空闲,不会归还给操作系统。这意味着Ruby的堆只会增加不会减少。想象一下,如果您从数据库中读取100万行,每行有10列。那么你至少需要分配1000万个对象来存储这个数据。通常一个Rubyworker启动后会占用100M内存。为了容纳这么多数据,worker需要额外的400M内存(1000万个对象,每个对象占用40字节)。即使这些对象最终都被回收了,worker仍然使用了500M的内存。这里需要说明一下,RubyGC可以减小这个堆的大小。但是我在实战中并没有发现这个功能。因为在生产环境中,触发堆减少的情况很少发生。如果您的worker只能增长,最明显的解决方案是每次占用太多内存时重新启动worker。一些托管服务会这样做,例如Heroku。让我们看看实现此功能的其他方法。2.4.1内部记忆控制TrustinGod,butlockyourcarTrustinGod,butlockyourcar相信上帝,但不要忘记锁车。(凡人:外国人大多有宗教信仰,相信上帝是无所不能的,但在日常生活中,谁能指望上帝帮助自己。信仰是信仰,但当你有困难时,还是要靠自己。)。您的应用程序有两种方法来实现自内存约束。我控制它们,Kind(友好)和hard(强制)。友好的内存限制是在每次请求后强制内存大小。如果一个worker占用太多内存,则该worker被终止,unicorn会创建一个新的worker。这就是为什么我称它为“善良”。它不会导致您的应用程序中断。获取进程的内存大小,使用Linux和MacOS上的RSS指标或Windows上的操作系统gem。让我展示如何在Unicorn配置文件中实现此限制:pid}`.chomp.to_i/1024exitifrss>KIND_MEMORY_LIMIT_RSSendend磁盘内存限制是通过要求操作系统在工作进程增长很多时终止它来完成的。在Unix上,您可以调用setrlimit来设置RSSx限制。据我所知,这只适用于Linux。MacOS实现已损坏。如果有任何新信息,我将不胜感激。此片段来自Unicorn硬盘限制的配置文件:after_forkdo|server,worker|worker.set_memory_limitsendclassUnicorn::WorkerHARD_MEMORY_LIMIT_RSS=600#MBdefset_memory_limitsProcess.setrlimit(Process::RLIMIT_AS,HARD_MEMORY_LIMIT2024*1end24)extern内存控制自动控制不会将您从偶尔的OMM(内存不足)中拯救出来。通常你应该设置一些外部工具。在Heroku上,没有必要,因为他们有自己的监控。但是,如果您是自托管的,那么使用monit、god或其他一些监控解决方案是个好主意。2.5调整RubyGC在某些情况下,您可以调整RubyGC以提高其性能。我想说这些GC调优变得越来越不重要,Ruby2.1中的默认设置,后来对大多数人有利。为了良好的GC调整,您需要知道它是如何工作的。这是一个独立的话题,不是本文的一部分。要了解更多信息,请通读SamSaffron撰写的DemystifyingRubyGC。在我即将出版的关于Ruby性能的书中,我深入探讨了RubyGC的细节。订阅此书,当我完成本书的测试版时,我会向您发送电子邮件。我的建议是最好不要更改GC设置,除非您确切地知道自己想要做什么并且有足够的理论知识知道如何提高性能。这对于使用Ruby2.1或更高版本的用户尤其重要。我知道只有一次GC优化可以真正提高性能。也就是说,当您要一次超载大量数据时。您可以通过更改以下环境变量来降低GC运行的频率:RUBY_GC_HEAP_GROWTH_FACTOR、RUBY_GC_MALLOC_LIMIT、RUBY_GC_MALLOC_LIMIT_MAX、RUBY_GC_OLDMALLOC_LIMIT和RUBY_GC_OLDMALLOC_LIMIT。请注意,这些变量仅适用于Ruby2.1及更高版本。对于2.1之前的版本,变量可能会丢失,或者变量不使用此名称。RUBY_GC_HEAP_GROWTH_FACTOR默认值为1.8,当Ruby的堆没有足够空间分配内存时使用,每次应该增加多少。当你需要使用大量的对象时,你希望堆的内存空间增长得更快。在这种情况下,您需要增加因子的大小。内存限制用于定义当您需要在操作系统的堆上申请空间时触发GC的频率。Ruby2.1及之后的版本,默认的限额为:NewgenerationmalloclimitRUBY_GC_MALLOC_LIMIT16MMaximumnewgenerationmalloclimitRUBY_GC_MALLOC_LIMIT_MAX32MOldgenerationmalloclimitRUBY_GC_OLDMALLOC_LIMIT16MMaximumoldgenerationmalloclimitRUBY_GC_OLDMALLOC_LIMIT_MAX128M让我简要的说明一下这些值的意义。Bysettingtheabovevalues,每次新对象分配16M~32M之间,老对象每次占用16M~128M之间(“老对象”是指该对象至少被垃圾回收调用过一次),Ruby会运行GC.Ruby会根据你的内存模式动态调整限流值。所以,当你只有几个对象,但占用大量内存时(比如将一个大文件读入字符串对象),你可以增加限制来减少GC被触发的频率。请记住同时将限制增加4,最好是此默认值的倍数。我的建议可能与其他人的建议不一样。它可能对我有用,但对你不起作用。这些文章将描述什么适用于Twitter以及什么适用于Discourse。2.6配置文件有时,这些建议可能并不通用。你需要弄清楚你的问题。这时候就需要用到profiler了。Ruby-Prof是每个Ruby用户都会使用的工具。要了解有关分析的更多信息,请阅读ChrisHeald和我关于在Rails中使用ruby??-prof的文章。还有一些可能有点过时的内存分析建议。2.7编写性能测试用例最后,虽然不是提高Rails性能的最重要的技术,但确保您的应用程序的性能不会因为您的代码更改而再次降低。Rails3.x有一个特性,它附带一个性能测试和分析框架。对于Rails4,您可以通过rails-perftestgem使用相同的框架。3结论关于如何提高Ruby和Rails的性能,真的不可能在一篇文章中面面俱到。所以,在此之后,我将通过写一本书来总结我的经验。如果您觉得我的建议有用,请注册邮件列表,我会在准备好本书的预览后立即通知您。现在,让我们一起努力,让Rails应用程序运行得更快!