Rails 做 MySQL 的查询缓存,或者 Model 方法的缓存,有什么好方法?

ihlayy · 2014年03月31日 · 最后由 zhangjinzhu 回复于 2014年04月02日 · 4248 次阅读

请问大家做数据库层的缓存,都用什么 Gem? 有什么好建议吗,谢谢。

要给出场景才知道要用什么缓存机制啊。

如果是缓存一个查询

def something_need_to_cache
  Rails.cache.fetch('some_key') do
    where(CONDITIONS).to_a
  end
end

#2 楼 @Rei 实际使用过程中,大多数应该只是根据 ID 缓存了对象吧,比如 User id=1,2,3 .

像分页的情况,也是查询出 ID,然后在缓存中取的吗?有比较特殊的使用情况吗

#3 楼 @ihlayy 没场景讨论不了。应该先把这里看一次 http://guides.rubyonrails.org/caching_with_rails.html

#4 楼 @Rei SQL Caching 这种吧。 dalli 这可以肯定是可以实现的,但就是每次要自己生成 KEY,比较麻烦。 second_level_cache 这个方便一些,但是复杂的 query 的缓存好像是没法做吧。

可以尝试下 https://github.com/qor/qor_cache 不用自己生成 cache_key,直接 cache 一个方法

#4 楼 @Rei #6 楼 @zhangjinzhu 想问一下,如果我是两种语言混用的项目,前台用 Rails 后台用 Java, 如何共享缓存,或者怎么使用 memcached.

假设有 Customer 对象在 Rails,Java 中都被缓存了,如果是从 Rails 程序更新了 Customer 对象和缓存,怎么使 Java 缓存失效?

没看到有现成的库两边的 Key 和保存的 Json 都能通用的呢?

一般几个思路?

  1. Rails,Java 通用的类库
  2. 各自独立,更新的时候通过队列,或者 Rest api 通知另一端更新
  3. 数据统一从一个地方取,一个数据层?(这种效率就不清楚了)

@ihlayy

用 identity_cache + Rails.cache 应该够了,对于基本的查询(如使用主键,或者使用固定字段等)直接使用 identity_cache,如

User.fetch(1)
Chart.fetch_file_name_by_uuid(uuid)

对于 identity_cache 不能处理的,或者想单独缓存的,可以使用 Rails.cache 包下,如 @Rei 上面说得。

@ihlayy 不要尝试写回调让缓存失效,只要改变缓存的 key 即可。逻辑挺简单的,也就是几十行代码的事情。 https://github.com/qor/qor_cache/blob/master/lib/qor_cache/active_record.rb

#9 楼 @zhangjinzhu 我有很多是类方法,比如 Customer.find_by_email , 这个时候缓存的 Key 是 customer_{#email} => 对应 customer id

比如登录这个场景,这个时候不是一个对象的实例方法,没有 model 的 updatedAt 这个时间戳的

#8 楼 @martin 谢谢,我看看吧,因为 Java 那边,我本来是打算用 simple spring memcached , rails 这边用 second_level_cache + dalli , 结果我发现有几个地方对不起来。

  1. 生成的 cache 的 key
  2. key 对应的 content(json 格式)

如果直接用 dalli 应该是可以实现,但是代码会比较乱,可能得去改 second level cache 的 Gem 包 所以看看大家有没有别的什么好的思路

参考这 18 行代码: https://github.com/qor/qor_cache/blob/master/lib/qor_cache/active_record.rb#L15-L33

以及这两行 https://github.com/qor/qor_cache/blob/master/lib/qor_cache/active_record.rb#L7-L8

你的类会维护一个类级别的 cache key,如果这个类有更新的话,就会改变这个类的 cache key,你所要做的就是在你的类方法上用这些类级别的 cache key 就好了...

#12 楼 @zhangjinzhu 你这个是类名 + 方法名 + 随机数,如果我只有 rails 的 app 还行,但是我要在 Java 那边和这个 Key 对起来更麻烦了。我想的是 Java 和 Rails 生成的缓存的 Key 的规则是一致的,就是我 Java 的 lib 可以使用 Rails 程序保存的 memcached 的对象,这样的好处是,内存节约了,更新在一个端更新另外一端使用就行了。

不然的话我就需要处理如果 Rails 这边更新了数据库,Java 那边的缓存怎么办的问题。

@ihlayy 你就压根不需要管 cache key 是什么组成的,你只要知道他是一个随机数就好了!java 那边难道不能做成一个随机数存到 memcache 里?。。。

#14 楼 @zhangjinzhu 如果同一个数据库里的同一个 customer(Id;1),你有 rails 程序和 Java 程序,你在 memcached 里面保存几份 customer 对象?

我怀疑多个 Rails 程序集群的时候也可能会有问题。

如果 customer 对象在数据库里被更新了,你怎么保证 Rails 程序和 Java 程序读到的是最新的 Customer 数据?

因为可能是通过 Rails 程序更新的数据库而 Java 程序根本就不知道这个 Customer 被更新了

@ihlayy 给你举个例子,对于 customer 这个类来说 rails, java 要有一个同享的 cache key(例如名字叫:customer_cache_key, 这个 key 是用来存到 memcache 中的),这个 cache key 的值是一个随机值。

如果 rails 中 customer 变了的话,就更新 customer_cache_key 的值变成另一个随机值,如果 java 中 customer 变了的话,就更新 customer_cache_key 的值为另一个随机值。

这样子你取的时候,只需要根据 customer_cache_key 的值来读缓存,如果不存在缓存就写数据库就可以了

#16 楼 @zhangjinzhu

问题是你怎么处理这个同享的 cache key?

假设 login_by_email 方法,你两边怎么产生这个一致的 Key?

rails cache_key(123456) -> customer1 java cache_key(123456) -> customer1

rails 中 customer 变了 (你的思路是变 key 这个没错,不清除缓存) rails cache_key(56789) -> customer1_change java cache_key(123456) -> customer1

这个时候 java 得使用 cache_key(56789)?我怎么知道这个新的 key(56789)?

@ihlayy 我的描述能力有这么差么?。。。。

再以你的例子举个例子:

getCacheKeyFromMemcache() -> 123456
rails cache_key(getCacheKeyFromMemcache()) -> customer1
java cache_key(getCacheKeyFromMemcache()) -> customer1

如果 rails 中 customer 变了:

getCacheKeyFromMemcache() -> 56789
rails cache_key(getCacheKeyFromMemcache()) -> customer1_change
java cache_key(getCacheKeyFromMemcache()) -> customer1_change

浅显易懂了么?

#18 楼 @zhangjinzhu 我知道你这个意思。我怀疑是我表述的太不清楚了?

我刚问的意思就是 getCacheKeyFromMemcache() 你这个方法你准备怎么写。

你想想具体实现会很容易么?

#18 楼 @zhangjinzhu

你是想告诉我 cacheKey 对应类名,方法名存在 memcache 里面?这得查询多少次,感觉不太划算吧。。。?

@ihlayy 其实挺简单的,只是读两次 memcache 而已,第一次读 cache key,第二次读真正的缓存值。

这个带来的好处就是,你不需要维护缓存的数据 (不用清缓存) (这个的复杂度通常来说是很高的。。。特别在还有关联表的情况下)。。。。你看看和你上面的解决方案来比较,这个解决方案轻量级多了。。。

一般几个思路?
1. Rails,Java通用的类库
2. 各自独立,更新的时候通过队列,或者Rest api通知另一端更新
3. 数据统一从一个地方取,一个数据层?(这种效率就不清楚了)

#21 楼 @zhangjinzhu 我刚装了下 qor_cache,看了下源码,确实是读了两次 memcache 换来了不用清除缓存。

我想下吧。没在真实的产品环境实验过,读两次代价也挺高的,毕竟不是单机进程内读数据。 调用 memcache 是有网络开销的。你要一个方法内有 4 次调用数据库,无形中就可能多了 4 次的 memcache 调用。

谢谢啊。这么有耐心的回答。。

我想下吧。没在真实的产品环境实验过,读两次代价也挺高的,毕竟不是单机进程内读数据。 调用 memcache 是有网络开销的。你要一个方法内有 4 次调用数据库,无形中就可能多了 4 次的 memcache 调用

https://github.com/mperham/dalli 这种库一般都是维持一个长连接的,所以可以忽略网络开销。。。

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