Rails 集合缓存优化实践

flydragon · January 27, 2019 · Last by flydragon replied at January 31, 2019 · 2110 hits

场景

对获取列表的 api 接口进行集合缓存,假设获取前 10 条文章列表。

优化前

关键代码

# model
class Article
  include Mongoid::Document
  include Mongoid::Timestamps

  field :title, type: String
  field :content, type: String
end

# controller
class ArticlesController < Api::BaseController
   def index
      @articles = Article.order_by(id: :asc).limit(10)
   end
end

# view
json.cache! [@articles] do
  json.articles @articles do |article|
    json.call(article, :title, :content)
  end
end

调起 api 的数据库 log

# 第一次调用api
MONGODB | localhost:27017 | _development.find | STARTED | {"find"=>"articles", "filter"=>{}, "limit"=>10, "sort"=>{"id"=>1}}
# 第二次调用api
MONGODB | localhost:27017 | _development.find | STARTED | {"find"=>"articles", "filter"=>{}, "limit"=>10, "sort"=>{"id"=>1}}

优化后

关键代码

# 处理类数据的缓存键(新增)
module ClassDataCacheKeyAble
  extend ActiveSupport::Concern

  included do
    after_create do
      self.class.refresh_class_cache_version
    end

    after_update do
      self.class.refresh_class_cache_version
    end

    after_destroy do
      self.class.refresh_class_cache_version
    end
  end

  class_methods do
    def cache_key_with_version
      "#{cache_key}-#{cache_version}"
    end

    def cache_version
      Redis::Counter.new("class_cache_version:#{cache_key}").value
    end

    def refresh_class_cache_version
      Redis::Counter.new("class_cache_version:#{cache_key}").increment
    end

    def cache_key
      name.tableize
    end
  end
end

# model调整
class Article
  include Mongoid::Document
  include Mongoid::Timestamps
  include ClassDataCacheKeyAble # 引入模块

  field :title, type: String
  field :content, type: String
end

# view调整
json.cache! [Article] do
  json.articles @articles do |article|
    json.call(article, :title, :content)
  end
end

调起 api 的数据库 log

# 第一次调用api
MONGODB | localhost:27017 | _development.find | STARTED | {"find"=>"articles", "filter"=>{}, "limit"=>10, "sort"=>{"id"=>1}}
# 第二次调用api,没有查询数据库

总结

相对优化前,减少了一次数据的列表查询,但多了一次获取全局更新键的 redis 查询,总体下来性能还是很好的,本地简单测试了一下近两倍左右,随着数据库数量的增加,优化空间会更大。

适用场景:模型总体数据变化不大,例子仅说明问题使用。

大家有没什么更好的方案,欢迎留言指正。

查询量大的话,稍微增删改 article 就会可能雪崩?

应该不会,即使缓存失效,也只会查询一次数据库。 相交优化前,好很多,优化前不论数据是否修改每次都会查询数据库。

为什么不直接用 Rails.cache 的 cache_key_with_version

Reply to w7938940

怎么用?展示你的 code

Rails 5 已经有 collection cache, jbuilder 也有 collection cache

不使用 jbuilder_cache_multi,适用于增删改不频繁,缓存整个集合

json.cache! @user1 do
  json.partial!  'article', collection: @user2, as: :article
end

使用 jbuilder_cache_multi,缓存每个对象

json.cache_collection! @user3 do |article|
  json.partial! 'article', article: article
end

如果对象支持 cache_key 方法就用 cache_key 做缓存的 key,获取的时候使用 Rails.cache.fetch_multi

我代码里面都用的 @articles,显示出来出错了

Reply to w7938940

这个优化主要解决的就是,当集合数据没有变化的时候,不需要查询数据库。 你可以试一下你的 demo,即时集合数据中的数据没有任何变化,但是为了生成集合缓存键,他还会进行查询数据库。

Reply to w7938940

还有为了复用cache_key,为以后缓存优化做准备,引入了cache_key_with_versioncache_version,代码已修改

You need to Sign in before reply, if you don't have an account, please Sign up first.