这篇文章拖了有点久,今天终于想起来把它写完了。记录一次生产环境的事故,也算是对自己的警示吧!

起因

前段时间自己对一个Rails项目后台管理部分的前端代码进行了重构(这里补充下项目情况,我们的后台管理界面采用Rails的服务端渲染,用户端产品部分使用前后端分离方案,后端提供API模式)。在修改的过程中因为一些原因就考虑学习现代前端方案去按页面自动加载对应的js和css代码文件(Rails的实践是使用Asset Pipeline连接静态资源文件,形成一个对应的主文件,当然如果代码较多,合并的主文件就会很大,首次加载会比较耗时),所以自己写了一个helper方法,去判断当前页面是否存在对应的js以及css文件,如果存在就加载对应文件,判断逻辑大致如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  def asset_exists?(path, type)
    Rails.application.assets.resolve(path, accept: type).present?
  end

  def javascript_exists?(path=nil)
    extensions = %w(.js .es6 .coffee) + [""]
    extensions.inject(false) do |truth, extension|
      default_path = "#{controller_path}/application#{extension}"

      truth || asset_exists?(path || default_path, 'application/javascript')
    end
  end

过程

代码逻辑上没有什么问题,在测试环境中测试的时候也是一切顺利,于是就发布了。发布完立马就有同事反馈说后台打不开了(因为这个判断逻辑放在了后台页面的layout中),然后马上进行了发布回滚对问题进行排查。查了一下发现 生产环境下Rails.application.assets为nil ,判断出错导致页面无法打开。

找到了问题那就解决呗,搜了一下发现问题比较常见,方案也比较多,但是像这种config.assets.compile = true修改生产环境配置的操作就忽略了,然后看到了

1
Sprockets::Railtie.build_environment(Rails.application, true)

这个代替方案,因为着急解决问题,另外看到这条回答下很多人点赞也就信任采用了,代码修改如下:

1
2
3
def asset_exists?(path)
  Sprockets::Railtie.build_environment(Rails.application, true)[path].present?
end

组长对此也没有深究就合并上线了,发布后页面正常访问了,但是监控系统开始显示后台页面访问速度变慢(大概3倍),另外系统内存也开始泄漏,内存逐渐上涨,回溯下提交记录判定应该是这行代码所引起的,然后就开始查看具体这行代码做了什么操作。

仔细的看了下sprockets-rails对应函数的源代码,发现对应的也是设置了config.assets.compile=true,打开了实时编译的选项。在实时编译模式下,Asset Pipeline中的所有静态文件都由Sprockets直接处理,静态资源文件会在首次请求时被编译和缓存。因此可见问题的原因就在于此,导致了页面访问变慢,而线上编译导致服务器内存占用飙升。

最后的解决方案代码如下:

1
2
3
def asset_exists?(path)
  Rails.application.assets_manifest.assets[path].present?
end

通过预编译静态资源文件时生成的manifest文件进行判断,避免实时编译的性能问题。

总结

回顾下整个问题过程,开始是因为生产环境的配置不一样,导致测试没有发现问题然后发布时进行了代码回滚操作。第二次时在查找解决方案时,因为自己急着解决问题,未对相关代码进行深入了解(另外可能由于测试服务器性能原本就较差,未明显发现性能问题),导致出现了性能问题(唯一庆幸的是只是后台管理页面受到了影响),然后第三次才完全解决了相关问题。

这一次的事情提醒了自己,心急吃不了热豆腐,解决问题还是要稳着来,另外对于不了解的代码还是需要再三的确认,了解下大致原理,不能盲目的相信他人!