Redis 注意 Rails.cache.increment 设定有效期的问题

huacnlee · 2018年06月29日 · 最后由 Rei 回复于 2018年06月30日 · 9584 次阅读

Rails.cache.increment 时常会被我们用来做一些统计追加动作,也会时常用在一些需要做频率限制的场景。

例如:

  • API 频率限制;
  • 登录密码错误锁定限制;
  • 发送验证码频率...

再比如最近 Ruby China 添加的每小时发帖量限制 Topic::RateLimit

我们一般会有这样的用法:

# 期望这个锁定 1 小时有效
Rails.cache.increment(_rate_limit_hour_key, 1, expires_in: 1.hour)

if Rails.cache.read(_rate_limit_hour_key) > 10 
  ...
end

但你可能根本不会注意到这里 Rails.cache.incrementexpires_in 是不支持的(Rails 5.2 对 :mem_cache_store 支持了 rails/rails@b22ee64),也就是说你每次传递的 expires_in 参数基本被忽略了。

irb> Rails.cache.increment("foo", 1, expires_in: 100)
1
irb> Rails.cache.data.ttl("foo")
=> -2 # 根本没设置进去

上面的场景会一直处于锁定状态,不会过期。

:redis_store 的方式,我已经提交 PR 支持了:rails/rails#33254,但在使用的时候仍然需要注意这个陷阱。这个改动估计要到 Rails 6 才会带出来。

实际上这个也不算是个 Bug,目前 Redis 的 incrby, decrby 并没有 expire 参数的支持,所以一直没实现。但大家的使用的时候会条件反射的把参数当成 write 函数的参数来用。

val = c.incrby key, amount
if expires_in > 0 && c.ttl(key) == -2
  c.expire key, expires_in
 end

这里是不是应该判断 ttl 等于 -1

127.0.0.1:6379> incrby a 1
(integer) 1
127.0.0.1:6379> ttl a
(integer) -1
gzhi1992 回复

🤦🏻‍♂️ 已改正

> Rails.cache.write 'foo', 1
=> "OK"
> Rails.cache.redis.get 'cache:foo'
=> "\u0004\bo: ActiveSupport::Cache::Entry\n:\v@valuei\u0006:\u0015@marshaled_value\"\t\u0004\bi\u0006:\r@version0:\u0010@created_atf\u00171530335167.8461182:\u0010@expires_inf\v8.64e4"

Rails.cache 写入的是 ActiveSupport::Cache::Entry,用作缓存以外的用途时可能有不可预料的问题,依赖底层功能的时候我都直接用 Rails.cache.redis

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