AIOps 一场颠覆传统运维的盛筵
667
2022-09-13
如何提升 RailS 应用的性能?(如何提升男性功能)
「铁路很慢」,你也许听过这个笑话,那么我们的 Rails 框架呢?如果说 Rails 慢,那么如何提升 Rails APP 的性能就成了开发者们最关注的问题。
也许你听说过很多提升 RoR APP 性能的方法,它们有难有易,我们需要在选择其中最能帮助开发者脱离性能困境的。
这里列举了几种不同的提升 Rails 应用性能的方法。
1. 数据库索引
你的 APP 被 DB 性能限制,优秀的数据库索引可以在大型数据库表中带给你100倍的性能提升。然而并非所有 Rails 开发者都明白这一点有多重要。
添加 indexes 很容易:
class AddIndexToClientIndustry < ActiveRecord::Migration def change add_index :client_industries, :client_id endend
接下来就有无 Index 的情况做个对比。
有 Index 的情况:
CREATE INDEX addresses_addressable_id_addressable_type_idx ON addresses USING btree (addressable_id,addressable_type); t1 = Time.now c = Company.find(178389) a = c.addresses.first t2 = Time.now puts "---Operation took #{t2-t1} seconds---” Result with index: ---Operation took 0.012412 seconds---
没有 Index 的情况:
DROP INDEX addresses_addressable_id_addressable_type_idx;t1 = Time.now c = Company.find(178389) a = c.addresses.firstt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”Result without index:---Operation took 0.378073 seconds---
0.378073 / 0.012412 = 30.46 没有索引比有索引慢了30.46秒。
因此工程师可以在所有引用参数,或者其他经常查询的参数中加入 Indexes。但是不能加太多, 因为每一个都会增加 DB Size 从而影响性能。
2. 数据库查询数量
RoR让编程更快捷,但反过来也让每条请求的数据库查询次数难以控制。举个例子,如果每一个 Client 有一或多个 Industries。 我们想要显示 Client List 和它们的 Primary Industries:
<% @clients.each do |client| %>
如果有50个 Clients, 则会有51条数据库查询:
Processing by ClientsController#index as HTMLSELECT "clients".* FROM "clients" SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 1 LIMIT 1SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 2 LIMIT 1SELECT "industries".* FROM "industries" INNER JOIN "client_industries" ON "industries"."id" = "client_industries"."industry_id" WHERE "client_industries"."client_id" = 3 LIMIT 1…
解决方案: Eager Loading
# app/controllers/clients_controller.rbdef index @clients = Client.includes(:industries).allend
现在只有2至3条数据库查询而非51条:
Processing by ClientsController#index as HTMLSELECT "clients".* FROM "clients" SELECT "client_industries".* FROM"client_industries" WHERE"client_industries"."client_id" IN (1, 2, 3)SELECT "industries".* FROM "industries" WHERE "industries"."id" IN (1, 5, 7, 8, 4)
3. 减少内存占用
只用真正需要的gem使用时再加载对象分批处理海量数据。
一个使用真实数据的例子——find_each:
Using find:
t1 = Time.now Company.where(:country_id=>1).find do |c|puts "do something!" if ['Mattski Test'].include?(c.common_name)endt2 = Time.nowputs "---Operation took #{t2-t1} seconds---”
Result:
1 query, taking 46.65 seconds
Now using find_each:
t1 = Time.now Company.where(:country_id=>1).find_each do |c| puts "do something!" if ['Mattski Test'].include?(c.common_name)end t2 = Time.nowputs "---Operation took #{t2-t1} seconds---"
Result:
100 queries, taking 15.53 seconds in total (3x faster)
也有查询多了反而快的情况。
4. 使用缓存
缓存的使用对性能有巨大影响,首先确保数据模型正确,缓存可以帮你隐藏结构问题。
对象缓存 在使用对象缓存的情况下,应该把查询方法的 include 去掉,避免关联查询无法利用缓存的现象。 查询缓存 在不要求实时的情况下,对于统计类耗时查询,那么可以使用 memcache-client 将查询结果缓存到 memcached 里。 页面局部缓存 对象缓存和查询缓存都会降低数据库访问负载,但如果 RoR 的负载很高,就只能依靠页面局部缓存了。
「web2.0网站比较常用使用页面局部缓存,Rails 的页面局部缓存有一个缺点,就是和页面查询结果对应的 Action 当中的查询语句要放在 View 里面,否则每次 Action 里面的查询还是会被执行,但是这样做会破坏程序代码良好的 MVC 结构。这种情况下,也可以采用另外一个 Cache 插件: better rails caching,在缓存页面的同时可以缓存 Action 当中的查询语句。」
5. 让 web 请求更快
只有少量可用进程用于服务 web 请求,因此需要使 web 请求更快。理想情况下, web 进程一般在毫秒内完成,1至2秒算是慢的,10秒以上是非常慢的。如果你的 web 请求很慢,你的Rails APP 将无法支撑同一时间的大量用户。
解决方案:使用后台运行对长时间运行的项目使用后台运行诸如 delayed jobs, 从而释放你的 web 进程来解决更多请求。
6. 性能监控
图为使用 进行监控的总览页面,在这里可以对请求在服务器端耗时有个初步印象。可以直观的看到不同时间 web 事物、后台任务、数据库和外部服务的平响应时间、吞吐量、执行次数等指标,图中 web 事物在15:41的时候响应时间出现峰值,响应速度较慢。
为了进一步确定问题所在,点进 web 事物界面可以进一步了解各慢事物响应时间占比,快速定位到 articles/index 的响应时间较长。
7. 使用内存数据库
当查询和排序都在内存中完成,数据库将会运行的更快,而它们需要在磁盘上运行的时候就变得很慢。
解决方案:
限制 DB 的大小,保证它完全适合内存。将不紧急的信息移出主要数据库,移入次要数据库或其他地方。如果有大量存储需求,考虑使用非关系型数据库。
8. 更多性能建议:
对静态文件使用内容分发网络,例如使用 AWS CloudFront。对需要1-2秒的加载项使用延迟加载。使用服务导向架构,使一些进程在托管栈同步进行。
相信选择一种或几种适合的性能提升方法,可以使 RoR APP 更令用户满意。
备注:本文参考并翻译了 Matt Kuklinski 在 slideshare 上关于 提升 Rails 性能所分享的部分内容。
发表评论
暂时没有评论,来抢沙发吧~