成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

謹防 ActiveSupport::Cache::Store 緩存 nil 值

tunny / 1393人閱讀

摘要:這三行代碼,則是在緩存命中時,直接讀取緩存內(nèi)容并且返回。通過上面的源碼分析,我們可以知道,當(dāng)緩存失效時,方法會直接將其代碼塊中的代碼的返回值不加判斷地寫入緩存,并且返回該返回值。

Rails 中的 active_support 組件主要基于 Rails 需要提供了很多非常有用的基礎(chǔ)工具以及對 Ruby 內(nèi)置類進行擴展。其中的 cache 模塊主要提供了 Rails 中底層緩存的定義以及簡單實現(xiàn)。今天要跟大家探討的是之前在使用此模塊所遇到的一個坑,有興趣學(xué)習(xí)其基本用法的可以點擊以下兩個鏈接:

Rails Guides: ActiveSupport::Cache::Store

Rails API: ActiveSupport::Cache::Store

從 ActiveSupport::Cache::Store#fetch 聊起

之前在實現(xiàn)一個需要從外部服務(wù)請求數(shù)據(jù)的功能時,處于性能考慮,我在代碼中使用了緩存,并且設(shè)置緩存失效時間為 7 天,示例代碼如下:

def read_external_service(params)
  # 這段代碼稍微解釋下:
  #   當(dāng)緩存命中時,則直接讀取緩存,如果無期待緩存,則通過 HTTP 向外請求結(jié)果,并且將結(jié)果
  #   緩存下來,這樣子,當(dāng)下次繼續(xù)調(diào)用時,則可直接返回緩存內(nèi)容,而無需重復(fù)向外請求
  #
  Rails.cache.fetch "example_cache_key_here", expires_in: 7.days do
    response = HTTParty.get "https://example.com/example/request/path"
    JSON.parse(response.body)["data"]
  end
end

上面的代碼其實不復(fù)雜,核心代碼就是使用了 ActiveSupport::Cache::Store#fetch 方法。

一切都很正常地運行著,直到有一天,線上系統(tǒng)不斷報警,出錯原因就是這段代碼總是返回 nil ,而調(diào)用者又因為沒有判斷 nil 值,就會出現(xiàn) undefined method "xxx" for nil:NilClass 錯誤。在 debug 時,我嘗試了直接調(diào)用外部服務(wù)接口,發(fā)現(xiàn)請求都有正確返回數(shù)據(jù),不可能返回 nil 啊,難道是緩存了 nil 值?下面就直接通過代碼驗證一下!

[1] pry(main)> require "active_support"
=> true
[2] pry(main)> cache = ActiveSupport::Cache::MemoryStore.new
=> <#ActiveSupport::Cache::MemoryStore entries=0, size=0, options={}>
[3] pry(main)> cache.read :nil_value
=> nil
[4] pry(main)> cache.exist? :nil_value
=> false
[5] pry(main)> cache.fetch :nil_value do
[5] pry(main)*   nil   # this `nil` value will be cached
[5] pry(main)* end
=> nil
[6] pry(main)> cache.read :nil_value
=> nil
[7] pry(main)> cache.exist? :nil_value
=> true

看吧, fetch 方法確實會緩存 nil 值(通過 exist? 方法可以判斷是否緩存了指定的 key ),所以系統(tǒng)出錯原因就清晰了:在某次代碼執(zhí)行中,我的緩存剛好失效了,所以系統(tǒng)向外部發(fā)送了請求,恰巧這時候外部系統(tǒng)因為故障或者其他可能原因,沒有返回期待數(shù)據(jù),導(dǎo)致代碼中最終緩存了 nil 值,在接下來的時間里,雖然外部系統(tǒng)可能恢復(fù)了正確服務(wù),可是這時候因為我們的系統(tǒng)已經(jīng)緩存了 nil值,所以在每次調(diào)用時都返回緩存的 nil,而不是重新請求正確結(jié)果,導(dǎo)致最后不停的報錯告警。

這里插播一句,通過后來仔細查閱文檔,才發(fā)現(xiàn)文檔里已經(jīng)注明:

Nil values can be cached.

╮(╯▽╰)╭ 怪我咯~

解決方案

意識到這個問題之后,解決思路簡單粗暴,就是在可能返回 nil 值的地方放棄寫入緩存:

def read_external_service(params)
  cache_key = "example_cache_key_here"
  result = Rails.cache.read(cache_key)
  # 緩存命中,且內(nèi)容不為 nil ,直接返回緩存內(nèi)容
  return result if result.present?

  # 緩存失效,只能重新請求了~
  response = HTTParty.get "https://example.com/example/request/path"
  result = JSON.parse(response.body)["data"]

  # 請求結(jié)果正確,寫入緩存;否則,放棄之~~~
  Rails.cache.write(cache_key, result, expires_in: 7.days) if result.present?
  result
end

呃~~~雖然解決問題了,可是,就為了告訴系統(tǒng)不要相信 nil,就寫得這么繁瑣,好么?好么?好么?

踏上閱讀源碼之路

我嘗試搜索了 #fetch 方法是否有支持比如 reject_nil 這樣的 option,可惜的是,沒有!可是真的沒有嗎?我不信!看源碼去!

首先還是拜訪下 ActiveSupport::Cache::Store 這個類啦,它可是所有緩存實現(xiàn)類的抽象類,別問我抽象類是什么,就是它明明只說話不干活,但是其他干活的都得向它看齊!好啦,說人話,其實就是說,我們在調(diào)用 Rails.cache.read、Rails.cache.fetch 等讀寫方法時,這些方法都是在 ActiveSupport::Cache::Store 中定義的,但是它只定義邏輯,而實際底層的讀寫實現(xiàn),則都是交由其各種子類實現(xiàn)的,比如前面的 ActiveSupport::Cache::MemoryStore。

首先讓我們來看看 fetch方法的全部內(nèi)容:

def fetch(name, options = nil)
  if block_given?
    options = merged_options(options)
    key = namespaced_key(name, options)

    instrument(:read, name, options) do |payload|
      cached_entry = read_entry(key, options) unless options[:force]
      payload[:super_operation] = :fetch if payload
      entry = handle_expired_entry(cached_entry, key, options)

      if entry
        payload[:hit] = true if payload
        get_entry_value(entry, name, options)
      else
        payload[:hit] = false if payload
        save_block_result_to_cache(name, options) { |_name| yield _name }
      end
    end
  else
    read(name, options)
  end

從代碼中可以看到,當(dāng) #fetch 方法調(diào)用時沒有傳遞 block 的話,它本質(zhì)上就是 read 方法的別名而已。而當(dāng)調(diào)用時傳遞了 block 的話,即如我前面的示例代碼,讓我們把代碼分開看下:

cached_entry = read_entry(key, options) unless options[:force]
payload[:super_operation] = :fetch if payload
entry = handle_expired_entry(cached_entry, key, options)

它首先判斷是否設(shè)置了 force 選項,如果有,則不讀取緩存,由此模擬緩存強制失效;如果未設(shè)置 force 選項或者該選項不等于 true value,則嘗試讀取緩存,并且調(diào)用 handle_expired_entry判斷緩存是否仍舊有效。

if entry
  payload[:hit] = true if payload
  get_entry_value(entry, name, options)

這三行代碼,則是在緩存命中時,直接讀取緩存內(nèi)容并且返回。

else
  payload[:hit] = false if payload
  save_block_result_to_cache(name, options) { |_name| yield _name }
end

else 的代碼則表示,在緩存無命中時, #fetch 代碼直接調(diào)用 #save_block_result_to_cache 方法,并且向其傳遞了一個 block,這個 block 沒有干別的事情,它只會執(zhí)行我們傳遞給 #fetch 方法的 block,讓我們接著往下看看相關(guān)的實現(xiàn):

def save_block_result_to_cache(name, options)
  result = instrument(:generate, name, options) do |payload|
    yield(name)
  end

  write(name, result, options)
  result
end

可以看到,#save_block_result_to_cache 方法首先執(zhí)行傳遞進來的代碼塊,實際上也就是我們期待在緩存失效時執(zhí)行的代碼,而在獲得執(zhí)行結(jié)果 result 后,方法通過調(diào)用 #write 方法將結(jié)果寫入緩存,最后將 result 返回。

通過上面的源碼分析,我們可以知道,當(dāng)緩存失效時,#fetch 方法會直接將其代碼塊中的代碼的返回值不加判斷地寫入緩存,并且返回該返回值。這里,或許我們可以做點什么,來實現(xiàn)我們想要支持 :reject_nil 的需求?

支持 :reject_nil option

為了支持 :reject_nil,我們只需要在寫入緩存前判斷是否真的需要 nil 值即可,于是我們只需要在 #save_block_result_to_cache 中加入 #write 的前置條件:

def save_block_result_to_cache(name, options)
  result = instrument(:generate, name, options) do |payload|
    yield(name)
  end

  # options[:reject_nil] && result.nil? 作為前置條件
  write(name, result, options) unless result.nil? && options[:reject_nil]

  result
end

話不多說,讓我們來重新試驗一番:

[1] pry(main)> require "active_support"
=> true
[2] pry(main)> cache = ActiveSupport::Cache::MemoryStore.new
=> <#ActiveSupport::Cache::MemoryStore entries=0, size=0, options={}>
[3] pry(main)> cache.fetch :nil_key1 do
[3] pry(main)*   nil
[3] pry(main)* end
=> nil
[4] pry(main)> cache.exist? :nil_key1
=> true
[5] pry(main)> cache.fetch :nil_key2, reject_nil: true do
[5] pry(main)*   nil
[5] pry(main)* end
=> nil
[6] pry(main)> cache.exist? :nil_key2
=> false

可以看到,當(dāng)我們調(diào)用 #fetch 方法時,如果沒有傳遞 reject_nil: true,則 #fetch 方法會默認緩存 nil 值;而如果我們設(shè)置 reject_nil: true 的話,則 #fetch 就會放棄寫入 nil 值到緩存中。試驗成功?。?!

基于這樣的實現(xiàn),我的代碼就又可以改為如下了:

def read_external_service(params)
  # 所有改動只是加了一個 `reject_nil: true`,多方便,媽媽再也不用擔(dān)心我掉到坑里去了
  Rails.cache.fetch "example_cache_key_here", expires_in: 7.days, reject_nil: true do
    response = HTTParty.get "https://example.com/example/request/path"
    JSON.parse(response.body)["data"]
  end
end

待會去給 Rails 提交 Pull Request 去 O(∩_∩)O~~

總結(jié)

緩存是好個東西,用得好能夠讓應(yīng)用性能表現(xiàn)突飛猛進

要注意緩存寫入的邊界條件,要注意避免緩存了空值,但也并非所有空值都不能緩存(比如有些接口確實就是有可能返回空值嘛),具體看業(yè)務(wù),沒有絕對的要與不要,反正 :reject_nil 給你了,看你要不要

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/61765.html

相關(guān)文章

  • 謹防云中斷,數(shù)據(jù)中心冗余如何設(shè)計?

    摘要:云計算提供商提供的云服務(wù)在出現(xiàn)中斷時,行業(yè)廠商和用戶似乎都在學(xué)習(xí)如何設(shè)計本地云冗余,管理人員希望將這些教訓(xùn)應(yīng)用于傳統(tǒng)的虛擬化數(shù)據(jù)中心冗余。云計算冗余并不完美,停機中斷提供了經(jīng)驗教訓(xùn)高度冗余的系統(tǒng)不會免受性能下降的影響。很多公共云提供商在日常運營中通常不可避免地遭遇災(zāi)難性中斷,而IT管理人員需要從云平臺的故障和教訓(xùn)中學(xué)習(xí),并將其應(yīng)用到內(nèi)部基礎(chǔ)設(shè)施當(dāng)中。云平臺(尤其是大型公共云平臺)具有多種冗余...

    jayzou 評論0 收藏0
  • 謹防云計算監(jiān)控的方法有哪些?

    摘要:尤其是云計算監(jiān)控,已經(jīng)引起了人們的廣泛關(guān)注。云計算監(jiān)控具有很多應(yīng)用,但作為一種單獨的解決方案,它充滿了危險。企業(yè)必須注意這些危險,而不是認為可以單獨依靠云計算監(jiān)控。數(shù)據(jù)中心和IT運營經(jīng)理長期以來一直認為,僅從防火墻后面進行監(jiān)控并不能了解最終用戶是否享受快速可靠的數(shù)字體驗。這是因為防火墻之外還有大量的外部元素——第三方服務(wù)、ISP、CDN等等,這可能會影響最終用戶在最后一英里的最終體驗。最終用...

    dance 評論0 收藏0
  • iOS 客戶端基于 WebP 圖片格式的流量優(yōu)化(下)

    摘要:在客戶端基于圖片格式的流量優(yōu)化上這篇文章中,已經(jīng)介紹了格式圖片的下載使用,僅僅只有這樣還遠遠不夠,還需要對已經(jīng)下載的圖片數(shù)據(jù)進行緩存。二圖片緩存關(guān)于的緩存,系統(tǒng)提供了一個類,。而且,既然是全局影響,肯定要用包起來,防止誤傷其他緩存。 在iOS 客戶端基于 WebP 圖片格式的流量優(yōu)化(上)這篇文章中,已經(jīng)介紹了WebP格式圖片的下載使用,僅僅只有這樣還遠遠不夠,還需要對已經(jīng)下載的圖片數(shù)...

    JiaXinYi 評論0 收藏0
  • Derek解讀Bytom源碼-持久化存儲LevelDB

    摘要:函數(shù)總共操作有兩步從緩存中查詢值,如果查到則返回如果為從緩存中查詢到則回調(diào)回調(diào)函數(shù)?;卣{(diào)函數(shù)會將從磁盤上獲得到塊信息存儲到緩存中并返回該塊的信息。回調(diào)函數(shù)實際上調(diào)取的是下的,它會從磁盤中獲取信息并返回。 作者:Derek 簡介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc......

    Eminjannn 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<