Rails Ruby on Rails 网站大型化之静态资源 CDN 架构

embbnux · 2016年01月08日 · 最后由 ecloud 回复于 2017年06月09日 · 6621 次阅读
本帖已被管理员设置为精华贴

第一次发表话题,希望我新写的博客对社区有用

本文发表于 Embbnux 博客,欢迎转载,转载请注明原文出处,并保留原文链接:

https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/

rails 是个很成熟的网站开发架构,设计者也与时俱进把很多先进的技术与架构集成到 rails 中,造就了其他框架无法比拟的开发效率。网站发展到一定程度,网站流量越来越大就不能把静态文件请求和动态网页请求放到同一台服务器。因为静态资源的流量会远远大于动态资源的请求,流量一大,静态资源会占满服务器带宽,导致网站加载缓慢,所以 cdn 是必不可少的。

rails 的开发者考虑得很全,要实现网站的 cdn 化,只需要修改一个配置文件即可,不过为了不显得我这篇博文太少了,我还是慢慢的讲来。

一、讲讲原理之一个网页的请求

一个网页从在浏览器输入网址,到展示在你面前,之间经历了好多个请求,主要流程如下:

  • 发送请一个请求到所访问地址,网站服务器返回一个 html 文本
  • 浏览器解析 html 文本,并加载里面引入的 js、css 以及图片等文件,这些叫做静态资源,同样浏览器还要往服务器发送这些静态资源的请求
  • 等待外部静态资源请求完成后,浏览器在进行 css,js 文件的解析执行,把图片等填充到网页上
  • 然后就是你所看到的一个网页了,有颜色有图片也有交互

二、rails 的静态资源

rails 对于的静态资源主要有两种,一种是写在代码里的,比如网站的 js、css 以及图标等文件,还有一种为用户上传的,比如用户上传的头像图片等。

对于静态文件等 rails 引入了Asset Pipeline用来管理编译静态资源,对于第一种写在代码里的静态文件,在 rails 工程里面是存在 app/assets 文件夹下,分别有 images 和 stylesheets 以及 javascripts 等分类文件夹,js 和 css 代码放在这里可以用 coffeescript 以及 sass 等各种语言来写,最终线上环境 (生产环境) 得对这些代码进行编译生成 js 以及 css 文件,这样浏览器才会识别,编译不仅进行格式的翻译还会进行 minify 等,最终会在编译文件后面加一段当前文件的 hash,所以代码变动,编译出的文件就不一样:

RAILS_ENV=production rake assets:precompile
#assets/javascripts/application.js => pubilic/application-3214abdc8899.js

由于编译完的文件被加了一段 hash 所以不能直接在 html 里面用路径访问,所以得在 view 层渲染时得出文件路径:

app/views/layout/application.html.erb:

<%= stylesheet_link_tag 'application' %>
<%= javascript_include_tag 'application' %>
<%= image_url 'favicon.png' %>

对应渲染出来的 html 是:

<link rel="stylesheet" media="screen" href="/assets/application-122fe15eeed76211bd37e2f1234454.css" />
<script src="/assets/application-583bffd2a21c2a6b8d1ab72bad4ba8af.js">
assets/favicon-1aa0e2adc41f64de39.png

加 hash 是为了在代码得到更新后浏览器能够及时更新使用新的静态文件,早期的 rails 版本使用的版本控制手段如下:

/stylesheets/application.css?1309495796

但是这种手段在 cdn 的使用场景上不能及时更新存在 cdn 上的文件,所以该用加了 hash 的文件名来做版本控制,保证 cdn 部署时代码和静态资源得到同时跟新,变成这样

application-1309495796.css

第二种静态资源是用户上传的,这些的不是放在代码里的,在 rails 工程中这些静态文件放在 public 文件夹下,因为 web 服务器的根目录指向的就是 public 文件夹,其他文件夹浏览器没有权限访问得到,之前的 js 和 css 也得被编译完后放到 public 文件夹才可以访问。这些静态文件不会被编译,所以文件名后面不会被加入 hash, 但也可以用 image_url‘upload/avatar.png’来访问,image_url 会自动区分要不要加 hash.

upload/avatar.png

三、开启 cdn

说了这么多,是时候开启 cdn 了。如果一直是按 rails 规范来写的话在这里开启 cdn 配置,只需要在 config/environments/production.rb 加一句话:

config.action_controller.asset_host = 'static-cdn.embbnux.com'

这样之前渲染出来的 html 就变成这样:

<link rel="stylesheet" media="screen" href="http://static-cdn.embbnux.com/assets/application-122fe15eeed7688837e2f1234454.css" />
<script src="http://static-cdn.embbnux.com/assets/application-583bffd2a21c2a6b8d1a888ad4ba8af.js">
http://static-cdn.embbnux.comassets/favicon-1aa0e2adc41888de39.png
http://static-cdn.embbnux.com/upload/avatar.png

出来的路径就是指向 cdn 服务器上的静态资源了,这样一个网页的访问就只会有第一个 html 的请求发到我们服务器上,其他的静态资源请求是发到 cdn 服务器上的,一个 html 文本一半也就几 k,大小很小的,加载时间也很快,不很会占用服务器带宽。

有时候为了区分和管理第一种网站静态资源和第二种用户静态资源可以配置分别指向两个 cdn 域名,不同的域名用不同的 cdn 空间,可以这样配置

config.action_controller.asset_host = Proc.new { |source|
  if source =~ /assets/
    'static-assets-cdn.embbnux.com'
  else
    'static-images-cdn.embbnux.com'
  end
}

这样渲染出来就变成这样:

<link rel="stylesheet" media="screen" href="http://static-assets-cdn.embbnux.com/assets/application-122fe15eeed76211bd37e2f1234454.css" />
<script src="http://static-assets-cdn.embbnux.com/assets/application-583bffd2a21c2a6b8d1882bad488af.js">
http://static-assets-cdn.embbnux.comassets/favicon-1aa0e2adc8884de39.png
http://static-images-cdn.embbnux.com/upload/avatar.png

为了保证 assets 文件及时更新到 cdn 存储,可以写个 rake 脚本利用 cdn 商提供的接口,在部署的时候及时上传 assets 文件到 cdn 空间比如:

rake cdn:assets_upload

四、web 服务器配置

为了保证 cdn 的文件能够及时与我们自己服务器上静态文件保持一致,需要开启 cdn 的镜像功能,但为了不使 cdn 缓存我们的动态 html 内容,造成镜像网站而降低网站权重,需要配置一下 nginx 服务器

server {
 server_name www.embbnux.com;
 root /rails_app/public;
 location / {
   index index.html index.htm;
   proxy_pass http://rails_app_upstream;
   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 }
}

server {
 server_name static.embbnux.com;
 root /rails_app/public;
 location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
   expires max;
   log_not_found off;
 }
 location ~* ^/assets/ {
   root /rails_app/public;
   expires 1y;
   add_header Cache-Control public;
   add_header Last-Modified "";
   add_header ETag "";
   break;
  }
}

这样服务器上的静态资源就可以通过 static.embbnux.com 访问,动态资源只能通过 www.embbnux.com 访问,cdn 镜像通过 static 域名,也就不用担心会镜像动态页面

四、更进一步

开启 cdn 后也就不用担心静态资源来占我们服务器的带宽了,不过这也只是大型化网站的第一步,还可以进行很多优化,欢迎拍砖。流量在大一点,只接受动态资源请求的我们网站服务器也会承受不了的,这时候就是开启负载均衡的时候了,可以一个 nginx 代理请求,然后中转到几台服务器上的 rails_app_upstream,这都是后话了。

更多内容欢迎查看原文博客: https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/

使用 CDN 云服务吧 比如七牛 方案更简单 效果更好~

一般 cdn 都会有到源站抓文件的功能,所以不必自己 upload 上去。

#2 楼 @huobazi 就是镜像功能啊

写得很清楚,不错

#1 楼 @chucai 七牛使用没几个月已经 挂了三次,一点都不稳定。我这个不管使用哪家的 cdn 都得这么配置

config.action_controller.asset_host = Proc.new { |source|
  if source =~ /assets/
    'static-assets-cdn.embbnux.com'
  else
    'static-images-cdn.embbnux.com'
  end
}

这一段学到了。原来还可以这样。谢谢楼主

写得很清楚,学习了

可以七牛和又拍同时使用,搭一个自动切换

和我之前的一个项目采用的是一样方案,不过我们这边更加 SANGXINBINGKUANG 我们直接用 Ali 家的 OSS,包括 css 里面引用的资源图片都加到了 assets 里面

#9 楼 @xworm aliyun 也有 cdn 啊

#10 楼 @alucardpj 运维哥哥偷懒,哈哈~ 其实用 OSS 和 CDN 是一样的,不外乎就是没有回源功能,自己写一个 rake 在部署时上传更新过的文件就好了

#11 楼 @xworm OSS 是固定一个机房的,而 CDN 是全国多个节点就近下载的,下载速度上还是有点不同。 CDN 回源地址设成 rails web server 的域名,就不需要 rake upload assets 了

#12 楼 @qhwa 我这边是 CDN 回源地址设成指向 rails public 的文件夹的静态 web server 的域名,保证 cdn 不会储存任何动态内容

#12 楼 @qhwa OSS 自带 CDN 服务的,使用 OSS 主要是运维懒得帮我绑域名,😄,后来发现使用 OSS 也挺不错的 绝对的避免了动态内容被 CDN 缓存

#11 楼 @xworm 区别还是有的,比如价格...

好强大 学习了!

huacnlee 将本帖设为了精华贴。 07月08日 11:16

学习了

请问楼主,我使用 image_url 并不能自动识别是否加 hash,出现错误

ActionView::Template::Error (The asset "upload/CGImage封装.png" is not present in the asset pipeline.):

我在 public/upload 目录下有 CGImage 封装.png 文件,我使用的时 production 模式运行的。

ecloud 回复

具体代码?

@embbnux view 层的代码

<img src="<%= image_url 'CGImage封装.png' %>">

生成的 HTML 如下

<img src="http://static-assets-cdn.embbnux.com/assets/CGImage封装-HASH.png">

因为目前没有真实 CDN 服务器,我就用的假的,production 模式

config.action_controller.asset_host = Proc.new { |source|
  if source =~ /assets/
    'static-assets-cdn.embbnux.com'
  else
    'static-images-cdn.embbnux.com'
  end
}

成功生成链接,但是后面链接后面还是加上了 HASH。昨天不配置 asset_host 的时候直接报 ActionView::Template::Error,但是今天又没有问题,我还在探索问题。 ps:Ruby 社区真的很友善。

ecloud 回复

你确定文件是传到 public/upload/CGImage封装.png 吗,如果是 view 改成

<img src="<%= image_url 'upload/CGImage封装.png' %>">

文件结构如下

但是当我用

<img src="<%= image_url 'upload/CGImage封装.png' %>">

直接报错

ActionView::Template::Error (The asset "upload/CGImage封装.png" is not present in the asset pipeline.):

我使用的是 production 模式运行的。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号