2017-05-25 73 views
2

我有一個簡單的Ruby方法,旨在遏制一些執行。Rails緩存計數器

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,經常寫會做同樣的事情。

我該如何預防?我只想爲給定的緩存鍵更新的值

UPDATE

每建議我嘗試:

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 

無影響。緩存不會過期。

回答

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

遞增Rails中緩存原料(與raw: true選項)的作品正是你想要的方式,即它僅更新值,沒有過期時間。但是,在調試時,不能依賴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只取得了4個小改動throttle代碼:

  1. 下調的時間窗口
  2. 改變render一個簡單puts
  3. 只使用一個密鑰,如果請求從一個單一的IP地址來
  4. 打印增加的值

的腳本:

# 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文件config在開發環境中實際啓用緩存。

+0

感謝您的迴應!嘗試它(見上文)沒有奏效。我錯過了什麼? – lostphilosopher

+0

它適用於我,看到更新的答案。你有沒有嘗試上面的簡單測試?你在使用什麼緩存存儲?它可能無法在所有緩存存儲中以相同的方式工作。我使用Dalli gem在memcached上測試了所有這些。 – BoraMa

+0

呵呵,試過你提供的代碼,在我的項目的rails runner下運行它,同樣的問題,在60後永遠存在「太多的請求」。我確定我的'cache_store'是':memory_store'。接下來,我將嘗試在我的項目中設置'dalli'和':mem_cache_store'。 (Rails 4.2.7.1供參考。) – lostphilosopher