我有一个简单的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
没效果。缓存不会过期。
答案 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次小改动:
render
更改为简单的puts
剧本:
# 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
文件在开发环境中启用缓存