论坛首页 编程语言技术论坛

Sinatra 中的缓存是如此的简单

浏览 6247 次
该帖已经被评为精华帖
作者 正文
   发表时间:2010-02-10   最后修改:2010-03-01

 

Ryan Tomayko

 →



 

 

 

页面缓存

 

sinatra 和 rails 的一个区别是 sinatra 默认是多线程模式。(不过当用到非线程安全的库时,可能还需要打开 mutex 选项,用单线程方式执行)

 

多线程模式搞缓存(cache)要容易一些。单线程的程序往往需要多进程避免并发时的 IO 堵塞,如果每个进程内都缓存一通,消耗的内存就会很大,如果用外部缓存如 memchached,就弄得稍繁琐,而且要考虑接口代码的额外消耗。(不过外部缓存也有优势,可以独立于服务器运行)



sinatra 1.0 pre release 包含了 tilt (适配多种模板的视图模块),里面有个很简单的 Tilt::Cache,不过它有点过于简单(譬如不能控制失效时间),自己写一个 cache 类也很容易:

# coding: utf-8
# Ruby 1.9
# super simple cache control
class Cachee < Hash
  # 30 min default
  def fetch path, t=nil
    # page data, expire time
    data, et = self[path]
    now = Time.now
    if !et or et < now
      data = yield
      self[path] = [data, now + (t || 1800)]
    end
    data  
  end

  alias expire delete

  # clear expired caches
  def sweep
    now = Time.now
    delete_if do |_, (_, t)|
      t < now
    end
  end
end



其使用和 Tilt::Cache 很相似,如:

$cache = Cache.new
get '/' do
  # expire after 1 hour
  $cache.fetch '/', 3600 do
    haml :index
  end
end

 

这个机制非常简单:超出时间后,在 fetch 时自动生成新的内容覆盖它。也可主动调用 expire 使其失效。另外 sweep 可清扫 cache,释放一些内存空间。



另一种缓存方式是用 sinatra-cache ,它通过生成静态文件来达成缓存。

 

 

浏览器端缓存


sinatra 支持 http etag 属性 (浏览器端缓存),etag 的作用是:当同一个会话重复请求一个资源的时候,服务器返回 http 代码 304- not changed,省去了文件的传输,页面响应速度要快很多。(sinatra 不对静态文件作 etag 处理,但是 nginx , apache 等 web 服务器都默认开启静态文件的 etag。)

 

在上面例子中添加 etag 非常的简单:

$cache = Cache.new
get '/' do
  etag 'mimi'
  $cache.fetch '/', 3600 do
    haml :index
  end
end

 

sinatra API 中 etag 的参考

http://www.sinatrarb.com/api/classes/Sinatra/Helpers.html#M000021

 

查看源码可以看到,etag 如果发现请求头带有浏览器缓存,就会调用了 halt 304,而 halt 方法包含一个 throw,halt 之后 etag 下面的代码都不会执行。

 

邮件列表上一个 讨论 中还提到控制浏览器的缓存时间:

response['Cache-Control'] = 'public, max-age=3600' 
 

既然 sinatra 管好动态内容的 etag 了,那么静态内容的 etag 也交给相应的专家吧。譬如 nginx 的配置就像这样:

 

worker_processes  3;
events {
    worker_connections  1024;
}

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    gzip on;

    # 配置 cluster
    # 应保证这些端口上运行 thin 或者 mongrel 服务器
    upstream myclusters {
        server 127.0.0.1:4001;
        server 127.0.0.1:4002;
        server 127.0.0.1:4003;
    }

    server {
        listen 80;
        server_name  localhost;
        charset utf-8;

        # 主要内容交给 sinatra
        location / {
            proxy_pass http://myclusters;
        }

        # 静态内容交给 nginx
        location ~ /css {
            root	myapp/public;
        }
        location ~ /img {
            root	myapp/public;
        }
        location ~ /js {
            root	myapp/public;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   myapp/public;
        }
    }
}

 

在 windows 下也能用,开发服务器也不慢呢。

开两台机器,一台开发,一台看效果,后台跑个脚本 每 5 秒点一下浏览器的刷新钮 ……

 

 

缓存策略

 

再回来说说页面缓存。

 

根据不同的资源特点,选用不同的缓存策略,可以精细的调整程序,获得最佳的性能表现。

譬如一个队列式的,限制大小的缓存:

 

# coding:utf-8
class QueuedCachee < Hash
  # sz limits the max size of cache
  def initialize max
    @max = max
    super
  end

  alias expire delete
  alias expire_if delete_if

  # fetch cache or (yield and add to cache)
  def fetch path
    # ruby 1.9 的 Hash 同时也是一个链表 (万能数据结构啊!)
    # Hash 里面的元素是按照插入顺序排列的
    # 如果已存在,删除,再将其插入最尾端
    # 如果不存在,且缓存数量超标,删除最前端(老)的元素,插入 yield 结果
    data = expire path
    if !data and size() >= @max
      oldest, _ = first()
      expire oldest
    end
    self[path] = data || yield
  end

  # remove oldest n cached items
  def sweep n
    expire_if do
      return if n <= 0
      n -= 1
    end
  end
end

 

还有其它的策略,如按访问量控制(实现或许需要一个优先队列,用 algorithms 能省一点点功夫)。如果需要非常复杂的缓存功能,最好还是用现成的东西 …… 如果应用程序架构设计得好,切分为很多独立的小 sinatra app 和消息服务器,这些小缓存类虽然简单,但也非常够用了。

 

ps: 这些缓存类还可以兼任对象缓存、数据库缓存和 fragment 缓存,在静态语言中这几乎是不可能的。

 

 

题外:多线程和单线程的组合,scaling

 

处理请求的时候,mongrel 是多线程的,thin 是单线程的。

thin 处理单任务的时候性能优于 mongrel,但是进程开多了内存消耗相对高一些。

可以把线程安全的组件放到 mongrel,再把 mongrel 在负载平衡中的权重设高一点,用细致的调整满足大量并发访问的需求。

 

sinatra REST 的优点不仅仅是 "url 比较短",它还体现了横向切分资源的通用设计方法。

每个 sinatra 程序根本复杂不到哪里去。在设计庞大的应用程序的时候,基本套路就是按资源切分成很多简单的独立 sinatra 程序。在扩展升级的时候,把 sinatra 程序放到不同的服务器中,轻松完成 scaling。从出生起就没有 A 依赖 B,B 依赖 C …… 的连锁问题(我在整 lift 的时候被弄怕了 …… play! 那 63 M 的下载包也让人很郁闷)。

 

  • 大小: 11.3 KB
   发表时间:2010-02-10  
有一个问题请教,Ryan Yomayko写的Rack::Cache插件是做什么的?简单看了一下,也是用于ETag,Cache-Control的,不知道和Sinatra的配合怎么样。
0 请登录后投票
   发表时间:2010-02-11  
呃,应该和 etag 方法作用相似,不过可以作为中间件使用,不用修改代码。
0 请登录后投票
   发表时间:2010-02-11  
最近正好要做一个定制服务器,lz介绍的tilt来得正好!

另: Sinatra确实非常好用,上能通天(高性能服务器),下能入地(嵌入式系统)!
0 请登录后投票
   发表时间:2010-03-28   最后修改:2010-03-28
tilt确实非常好,最新的版本则利用一种巧妙的方式提升了render速度。(确切的说,是取值的速度。)

以下来自官方README
require 'tilt'

template = Tilt::ERBTemplate.new('foo.erb')

# Slow. Uses Object#instance_eval to process template
class Scope
end
scope = Scope.new
template.render(scope)

# Fast. Uses compiled template and Object#send to process template
class Scope
  include Tilt::CompileSite
end
scope = Scope.new
template.render(scope)



新版的Sinatra::Base直接帮你include了Tilt::CompileSite,现在可以认为Sinatra的响应速度是所有Ruby框架中最快的。
0 请登录后投票
   发表时间:2010-04-04  
我把Sinatra纯粹当成Rails的补充来用,专门用来写一些小而简单的程序.
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics