新手问题 关于将 ActiveRecord 结果集缓存到 Redis 的 HASH 中

wfwdex · 2017年10月25日 · 最后由 hooopo 回复于 2017年10月27日 · 2765 次阅读

在优化项目响应时,想将 ActiveRecord 的结果集存在 Redis 的 HASH 中,主要是为了好管理。

比如用关联的 user_id 当做 HASH 的 key:

books = Book.where(user_id: user_id)
$redis.hset("books", user_id, books.to_json)

取出:

books = JSON.parse($redis.hset("books", user_id))

这样做的问题是取出时并不会还原成 ActiveRecord 结果集对象,而且数据量大的时候不知道会不会有性能问题。

但是使用Rails.cache.write又不支持存进 Redis 的 HASH 中,请教一下有没有更好的方法解决这个问题?

有干这个的 gem https://github.com/hooopo/second_level_cache

不过带 where 条件的关联,用 slc 可能会导致关联没法正确过期,要留意一下或者你参照 slc 的思路自己弄一个

redis 也是远程调用,细粒度的缓存是否有用还要实测。

mizuhashi 回复

谢谢,slc 似乎不能只指定某一个关联启用缓存,而是给 Model 的所有二级关联启用缓存了。

Rei 回复

谢谢,目前 mysql 和 redis 是在同一台单独的服务器上,我测一下看看效果。

查阅 Rails.cache

wfwdex 回复

所以说参考 slc 做一个嘛,反正注入关联要干的活 slc 里都写了

books = Rails.cache.fetch('cache_key', expires_in: 1.days) do
  Book.where(user_id: user_id).to_a
end

你想要的是这个吗?

但是以上做法有弊端,当 book 更新时,缓存中的 books 的无法得到更新

本质就是要把 ActiveRecord 的结果集 marshalize 一下,如果要还原出来还是 ActiveRecord 的类的话。可以把 attributes 自己提出来,塞到 ActiveRecord 里,出来的时候还原一下。理论上,也可以调标准库里那个 marshalize,这个有可能杀鸡用牛刀了,看具体需要了。

slc 里核心需要用的就是 record_marshal 的代码,照着实现一个类似的就行。 https://github.com/hooopo/second_level_cache/blob/master/lib/second_level_cache/record_marshal.rb

breeze 回复

谢谢回复,这个的只是在 redis 里存一个 key/value 键值,而不是在 Redis 的 HASH 里存键值。 我的问题是怎样方便地在 redis 的 hash 里存键值,并且取出来的时候能正常还原成 activerecord 对象。

dsh0416 回复

多谢,这个似乎就是解决这个问题的切入点,我研究下。

建议用 PG 的 trigger 和扩展比如 https://github.com/pg-redis-fdw/redis_fdw 来管理缓存

nouse 回复

这个看起来真不错,可惜我现在用的是 mysql,下次用试试,多谢。

wfwdex 回复

为什么一定要用 hash? 如果只是想缓存的话,Rails.cache.fetch 应该没有问题 (现在的项目用的这个来缓存一些 master table)

https://redis.io/topics/memory-optimization hash 好像更节省内存,可以先比较下 hash 和 Rails.cache.fetch 的内存使用量在决定使用哪个

dsh0416 回复

second_level_cache 那个 record_marshal.rb 也是每次 Rails 大版本升级最难搞的部分

需要深入分析新版本里面 Active Record 是如何实现的,还得回想之前版本的是如何实现的,所以这个文件写了很多注释

还有直接用 redis 做存储的, https://github.com/soveran/ohm

huacnlee 回复

之前 Rails 有个 issue 说是给缓存插件提供一些 hook 接口,后来也没有下文了

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