Rails缓存计数器

时间:2017-05-25 18:34:30

标签: ruby-on-rails ruby caching

我有一个简单的Ruby方法可以限制执行。

MAX_REQUESTS = 60
# per
TIME_WINDOW = 1.minute

def throttle
  cache_key = "#{request.ip}_count"
  count = Rails.cache.fetch(cache_key, expires_in: TIME_WINDOW.to_i) { 0 }

  if count.to_i >= MAX_REQUESTS
    render json: { message: 'Too many requests.' }, status: 429
    return
  end
  Rails.cache.increment(cache_key)
  true
end

经过一些测试后,我发现cache_key永远不会失效。

我调查了binding.pry并发现了问题:

[35] pry(#<Refinery::ApiReferences::Admin::ApiHostsController>)> Rails.cache.write(cache_key, count += 1, expires_in: 60, raw: true)
=> true
[36] pry(#<Refinery::ApiReferences::Admin::ApiHostsController>)> Rails.cache.send(:read_entry, cache_key, {})
=> #<ActiveSupport::Cache::Entry:0x007fff1e34c978 @created_at=1495736935.0091069, @expires_in=60.0, @value=11>
[37] pry(#<Refinery::ApiReferences::Admin::ApiHostsController>)> Rails.cache.increment(cache_key)
=> 12
[38] pry(#<Refinery::ApiReferences::Admin::ApiHostsController>)> Rails.cache.send(:read_entry, cache_key, {})
=> #<ActiveSupport::Cache::Entry:0x007fff1ee105a8 @created_at=1495736965.540865, @expires_in=nil, @value=12>

因此,increment正在消除expires_in值并更改created_at,常规写入将执行相同的操作。

如何防止这种情况?我只想更新给定缓存密钥的

更新

根据我的建议我尝试过:

MAX_REQUESTS = 60
# per
TIME_WINDOW = 1.minute

def throttle
  cache_key = "#{request.ip}_count"
  count = Rails.cache.fetch(cache_key, expires_in: TIME_WINDOW.to_i, raw: true) { 0 }

  if count.to_i >= MAX_REQUESTS
    render json: { message: 'Too many requests.' }, status: 429
    return
  end
  Rails.cache.increment(cache_key)
  true
end

没效果。缓存不会过期。

2 个答案:

答案 0 :(得分:1)

这是一个“解决方案”,我不会将其标记为正确,因为这肯定不是必要的吗?

MAX_REQUESTS = 60
# per
TIME_WINDOW = 1.minute

def throttle
  count_cache_key = "#{request.ip}_count"
  window_cache_key = "#{request.ip}_window"

  window = Rails.cache.fetch(window_cache_key) { (Time.zone.now + TIME_WINDOW).to_i }

  if Time.zone.now.to_i >= window
    Rails.cache.write(window_cache_key, (Time.zone.now + TIME_WINDOW).to_i)
    Rails.cache.write(count_cache_key, 1)
  end

  count = Rails.cache.read(count_cache_key) || 0

  if count.to_i >= MAX_REQUESTS
    render json: { message: 'Too many requests.' }, status: 429
    return
  end

  Rails.cache.write(count_cache_key, count + 1)
  true
end

答案 1 :(得分:1)

在Rails缓存中增加 原始(使用raw: true选项)可以按照您希望的方式工作,即仅更新值< / strong>,而不是到期时间。但是,在调试时,您不能非常依赖read_entry的输出,因为这与存储在缓存中的原始值不完全对应,因为缓存存储不会在仅存储原始时返回到期时间值。

这就是为什么通常(没有raw)选项,Rails不仅存储原始值,而是创建一个cache Entry object除了值之外还保存其他数据,例如到期时间。然后它序列化此对象并将其保存到缓存存储。在读回值后,它会反序列化对象并仍然可以访问所有信息,包括到期时间。

但是,由于无法增加序列化对象,因此需要存储原始值,即使用raw: true选项。这使得Rails直接存储该值并将过期时间作为参数传递给缓存存储write方法(无法从存储中读回)。

因此,总而言之,在缓存增值值时必须使用raw: true,并且缓存存储中通常会保留到期时间。请参阅以下测试(在mem_cache_store商店中完成):

# cache_test.rb
cache_key = "key"

puts "setting..."
Rails.cache.fetch(cache_key, expires_in: 3.seconds, raw: true) { 1 }
puts "#{Time.now} cached value: #{Rails.cache.read(cache_key)}"

sleep(2)
puts "#{Time.now} still cached: #{Rails.cache.read(cache_key)}"

puts "#{Time.now} incrementing..."
Rails.cache.increment(cache_key)
puts "#{Time.now} incremented value: #{Rails.cache.read(cache_key)}"

sleep(1)
puts "#{Time.now} gone!: #{Rails.cache.read(cache_key).inspect}"

运行时,您将获得:

$ rails runner cache_test.rb 
Running via Spring preloader in process 31666
setting...
2017-05-25 22:15:26 +0200 cached value: 1
2017-05-25 22:15:28 +0200 still cached: 1
2017-05-25 22:15:28 +0200 incrementing...
2017-05-25 22:15:28 +0200 incremented value: 2
2017-05-25 22:15:29 +0200 gone!: nil

如您所见,该值已增加而未重置到期时间。

更新:我为您的代码设置了一个最小测试,但不是通过真实控制器运行,而是仅作为脚本运行。我只对OP中的throttle代码进行了4次小改动:

  1. 降低了时间窗口
  2. render更改为简单的puts
  3. 仅使用单个密钥,就像请求来自单个IP地址一样
  4. 打印增量值
  5. 剧本:

    # chache_test2.rb
    MAX_REQUESTS = 60
    # per
    #TIME_WINDOW = 1.minute
    TIME_WINDOW = 3.seconds
    
    def throttle
      #cache_key = "#{request.ip}_count"
      cache_key = "127.0.0.1_count"
      count = Rails.cache.fetch(cache_key, expires_in: TIME_WINDOW.to_i, raw: true) { 0 }
    
      if count.to_i >= MAX_REQUESTS
        #render json: { message: 'Too many requests.' }, status: 429
        puts "too many requests"
        return
      end
    
      puts Rails.cache.increment(cache_key)
      true
    end
    
    62.times do |i|
      throttle
    end
    
    sleep(3)
    throttle
    

    运行打印以下内容:

    $ rails runner cache_test2.rb 
    Running via Spring preloader in process 32589
    2017-05-26 06:11:26 +0200 1
    2017-05-26 06:11:26 +0200 2
    2017-05-26 06:11:26 +0200 3
    2017-05-26 06:11:26 +0200 4
    ...
    2017-05-26 06:11:26 +0200 58
    2017-05-26 06:11:26 +0200 59
    2017-05-26 06:11:26 +0200 60
    2017-05-26 06:11:26 +0200 too many requests
    2017-05-26 06:11:26 +0200 too many requests
    2017-05-26 06:11:29 +0200 1
    

    也许您根本没有在开发中配置缓存?我建议在memcached存储中测试它,这是生产环境中最受欢迎的缓存存储。在开发中,您需要明确地将其打开:

    # config/environemnts/development.rb
    config.cache_store = :mem_cache_store
    

    此外,如果您运行的是最新的Rails 5.x版本,则可能需要运行rails dev:cache命令which creates实际上在开发配置中使用的tmp/caching-dev.txt文件在开发环境中启用缓存