Web開發基本准則-55實錄-緩存策略


續上篇《Web開發基本准則-55實錄-Web訪問安全》。

Web開發基本准則-55實錄-緩存策略

鄭昀 創建於2013年2月
鄭昀 最后更新於2013年10月26日

提綱:
  1. Web訪問安全
  2. 緩存策略
  3. 存儲介質連接池
  4. 業務降級
  5. 並發請求的處理

關鍵詞:
會話串號,Cache-Control頭域,緩存穿透,緩存集體失效,緩存重建,緩存雪崩,緩存永不過期,緩存計數器,

 
二,緩存策略
  這里的“緩存”概念不只限於服務器端的“緩存”。
 
2.1.防會話串號
  如果你收到一個投訴,說訪問“我的個人中心”頁面時進入其他人的帳號,至少訂單列表上顯示的不是自己的。此時,技術支持人員可以提三個問題,第一,對頁面上顯示的信息是否有操作權限,如取消訂單;第二,瀏覽器地址欄上給URL增加訪問參數,如追加一個&111之類的字符串,看看頁面是否還是顯示別人的信息;第三,投訴者上網接入方式是什么,如鐵通光纖寬帶,如通過某款代理軟件上網。
  如果既無操作權限,追加URL參數后又能看到自己的帳號信息或頁面提示處於未登錄狀態,那么說明是 URL已被各級 HTTP Proxies 緩存
  即 在服務器端收到 Request 之前,網絡鏈路上的某一級代理已返回緩存數據
  
2.1.1.簡單辦法,如利用Expiration Model
  第一種:如果頁面 Response 里設置了正確的 Last-modified 和 Expires 頭域,那么  基本過期模型 已經能正常運轉了,因此,頭域里的 Cache-Control:private 聲明就已經夠了,HTTP Caches 和 User Agent 都會根據這兩個字段檢查緩存網頁是否陳舊。
  第二種:重要頁面的URL上加時間戳參數。
  第三種:像淘寶博文[ 注1]所描述的:“ cookie 里增加一個值,用來記錄通過關鍵 cookie 計算出來的簽名,這個簽名的算法非常簡單。 每次請求到服務端的時候 session 框架代碼里會對此簽名進行匹配,如果和服務端獲取的數據簽名出來的值是一致的,則認為合法, 否則清空 session 信息和 cookie 信息,讓用戶重新登錄。”
 
2.1.2.需要有更多背景知識的辦法:利用 Cache-Control 頭域控制
   Web開發工程師都需要了解 Cache-control 頭域背后的 HTTP 1.1緩存控制機制和緩存重驗證機制。
 
  先說處理辦法是:含個人敏感信息的網頁響應頭里,聲明  Cache-Control:must-revalidate,proxy-revalidate,no-store,private,no-cache 即可。
 
  下面簡單地介紹一下背景知識,詳細信息請閱讀   HTTP/1.1 RFC2616, section 13 HTTP里的緩存 和  HTTP/1.1 RFC2616, section 14 頭域定義,或找到 HTTP 1.1協議中文版閱讀。
  HTTP1.1協議定義,Response 是可以被各種 HTTP caches 緩存的。
   除非有 Cache-Control 控制指令的特殊約定,否則從瀏覽器端到源服務器(origin server)端之間鏈路上所存在的各種 Caching System 都完全 有可能緩存一個成功的 response:
  如果這個 cache entry 是 fresh 的, 可能不會(去源服務器端)校驗直接返回;也 可能會做一個校驗再返回。
  一個狀態碼是200, 203, 206, 300, 301, 410的 response,可能會被緩存。
 
2.2.緩存穿透
  • 目的
    • 防止訪問(短期內)必然不存在的數據導致請求穿透緩存直接打到 DB。
  • 原因
    • 可能是數據真的不存在,但也可能是第三方惡意構造大量不存在的 id 來沖擊 DB
  • 多種手段結合
    • 『存儲EMPTY』思路存儲一個 EMPTY 對象到緩存對應鍵值,設置一個較短的過期時間。這樣在緩存失效后,還能繼續查詢數據是否存在。
    • 必須認真對待(不同業務不同端口的)緩存命中率(get_hits/cmd_get * 100)定期監控的結果,認真審視那些命中率低的緩存端口,找到命中率低的原因,提出優化方案。
    • 『先行校驗』思想采用布隆過濾器算法,將所有可能存在的數據(如所有有效商品的id)哈希到一個足夠大的 bitmap 里,那么一個一定不存在的數據會被這個 bitmap 攔截掉,從而避免了對底層存儲系統的查詢壓力。(出處
 
2.3.”半緩存“策略
  緩存命中率低,其中一個原因是, 你緩存的數據被人訪問間隔長、幾率低,於是在下次訪問到來之前緩存早已失效。命中率低,為我們指出了優化方向。
  如,用戶在查詢一個列表頁時,我們可以把前6頁的數據緩存起來,再往后的頁碼,訪問頻次很低,也許就不需要緩存了。( 出處
 
2.4.緩存集體失效
  以下原因都會導致緩存集體失效,從而引發系統”抖動“甚至”雪崩“:
  • 系統預熱數據的緩存過期時間過於整齊划一;
  • 緩存系統宕機或重啟;
  • 訪問高峰期間種下了一大批緩存,過期時間非常接近。
  處理手段:
  • 緩存過期時間散列開:在過期時間基礎上增加一個隨機值,如1秒~120秒隨機,將大家的過期時間盡量打散。
  • 防范緩存節點暫不可用的緩存雙寫策略
    • memcache雙寫:向 memcahce 的 Master Ring 和 Backup Ring 雙寫,如下圖1所示:
    • http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clipboard%20-45.png 圖1 memcache 雙寫 原圖出自點評技術PPT
    • Redis備份寫:向 memcache 寫入的同時,寫一份到備份緩存 Redis 里,鍵值的緩存過期時間非常大,如原鍵值在 memcache 過期時間5分鍾,在 Redis 里則8小時過期。當 memcache 集群節點暫不可用時,Web工程就切換讀取備用緩存 Redis。這種思路是保證基本可用性,所以必要時刻可以給用戶返回臟數據。
  • 對於不同的業務場景,緩存的使用策略也不同
    • 當系統面臨緩存異常的危險時,有些系統可以采用備份方案繼續支撐服務。有些系統則會優雅降級,將某些依賴緩存的功能直接去除,保證主服務的正確性。所以這兩種策略的選擇需要根據實際的業務場景考慮並實施。(出處
 
2.5.分級緩存
  有些業務場景里,應該把 DB 當成僅是一個存儲而已,靠分級緩存策略來層層抵擋緩存失效,不讓請求打到 DB。
  • 手段:
    • 由遠及近分層建立緩存,越靠近前端,緩存片段越大(或存儲粒度越大)。
    • 上一層的緩存失效,可以靠下一級的緩存迅速重建。
  • 目的:
    • 避免系統產生抖動。
    • 減少緩存雪崩,防止 DB 連接數暴漲、響應變慢,連累前端應用連接數持續高漲、最后宕機。
http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clipboard%20-%2046.png
圖2 緩存控制體系(圖出自  http://www.alidata.org/archives/1789 )
 
2.6.緩存重建
  既然有緩存過期,自然有緩存重建。
  熱點數據的緩存重建,無論是本地緩存還是遠端緩存,都有必要加鎖來確保進程內同一時刻只有一個 Worker 負責重建,甚至利用 分布式鎖保證集群環境下只有一個重建者,避免緩存雪崩時的 Race Condition。TimYang 早在2010年在《 Memcache mutex設計模式》中描述過如下風險:” 在大並發的場合,當cache失效時,大量並發同時取不到cache,會同一瞬間去訪問db並回設cache,可能會給系統帶來潛在的超負荷風險。我們曾經在線上系統出現過類似故障“孫立將這種場景稱為 cache key mutex 問題[ 注7]。
http://pic002.cnblogs.com/img/sunli/201009/2010090709371444.jpg
圖3 cache key mutex 問題的解決(圖出自 http://www.cnblogs.com/sunli/archive/2010/07/27/cache_key_mutex.html)
  簡而言之,緩存重建時,當多個 Client 對同一個緩存數據發起請求時,會在客戶端采用加鎖等待的方式,對同一個 CacheKey 的重建需要獲取到相應的排他鎖才行,只有拿到鎖的 Client 才能訪問數據庫重建緩存,其他的 Client 都需要等待這個拿到鎖的 Client 重建好緩存后直接讀緩存。這樣,對同一個緩存數據,只有一次數據庫重建訪問。但是如果訪問分散比較嚴重,還是會瞬間對數據庫造成非常大的壓力。
 
  當然也可以不加(悲觀)鎖,那么多線程並發讀寫同一個 cache key 可能會帶來“ABA問題”。
  解決方法很簡單:memcached 1.2.5以及更高版本提供了 gets 和 cas 命令。如果使用 gets 命令查詢某個鍵值,memcached 會返回該鍵值的唯一標識 casUnique。如果覆寫了這個鍵值並想把它寫回到 memcached 中,可以通過 cas 命令把那個 casUnique 一起發送給 memcached。如果該鍵值存放在 memcached 中的 casUnique 與提供的一致,寫操作將會成功。如果另一個進程在這期間也修改了這個鍵值,那么該鍵值存放在 memcached 中的 casUnique 將會改變,寫操作就會失敗。
 
2.7.緩存永不過期
  因為擔心緩存失效帶來的系統抖動,所以有些業務場景會讓緩存永不過期,數據變化時,由后端負責維護緩存數據一致性。
 
2.8.電商場景里的緩存計數器:秒殺和超賣
  我們在秒殺和防超賣場景里的實現邏輯類似於淘寶這篇博客[ 注3]所提及的”分布式緩存計數器“,所以我就直接照搬過來了:

    分布式緩存的另一個應用場景是緩存計數器。

    對於多服務器的系統,分布式緩存提供了統一的存儲和原子操作,便於集群環境下的使用。庫存計數器是分布式緩存的一個典型應用場景, 對於集群中的每一台機器,庫存都應該是一個統一的值,因此使用本地緩存記錄庫存,數據肯定是不准確的(下面會陳述例外情況)。因此,統一的存儲空間是必要 的條件。

    由於庫存數據被多台機器共享,因此,必須使用鎖機制控制多個請求的並行並發問題。基於這樣的機制就可以實行庫存技術器的作用,防止貨物超賣。最近的積分商城超值兌換就是使用的這種機制。

    這種機制下,需要注意操作的邏輯順序,錯誤的順序會導致意想不到的結果。積分兌換的業務流程為,用戶看到要搶兌的商品,如果庫存大於0,則用戶可以點擊搶兌操作,這時用戶會獲得兌換該商品的權限,從而優惠購買,這時庫存商品應該減一。

    如果完全按照這個業務流程,我們會完成下面這三步操作:

  • 驗證庫存是否大於0;
  • 給用戶打標,使其獲得優惠購買資格;
  • 獲得資格后,原子減庫存,記錄用戶購買記錄。

    乍一看這樣的邏輯是很正常的,但是考慮一下異常情況,就會發現它防不住超賣。如果庫存只有一件,那么多個用戶並發驗證庫存時,都大於0。這樣並發的多個用戶都會獲得優惠資格,產生了超賣

    正確的邏輯為:

  • 驗證庫存是否大於0,小於0直接返回;
  • 原子減庫存,返回的結果如果小於0說明已經沒有庫存,直接返回
  • 如果返回的當前庫存大於等於0,為用戶打標,如果打標成功,記錄用戶購買記錄;如果打標失敗,回補原子庫存

    這樣的方法,無法保證緩存中的值一定大於等於0,因為並發的發生會把緩存減為負數,但是,真正能夠優惠購買的用戶一定是小於等於庫存數的。因為,每次原子減操作后,只有返回的庫存值大於等於零的用戶才能夠獲得購買資格無論並發量有多大,原子操作都會成功的防止超賣的發生

    對於上述的邏輯,可以應對絕大多數的情況。
    但是隨着量的增加,這種方式也有風險。當用戶量極大、貨物的庫存極少時,就變成了秒殺。這個時候,大量的用戶涌入分布式緩存減庫存,對分布式緩存有極大沖擊,一旦分布式緩存掛掉,秒殺活動也就宣告失敗。使用分布式緩存,目的是為了讓用戶准確的看到剩余庫存數 目,秒殺活動非常快,用戶還沒有看清楚庫存,活動就結束了。其實用戶只關心有沒有秒到商品,並不關心庫存的剩余數量,因此,庫存減得准不准確並不是主要矛盾,這時就可以放棄分布式緩存的設計,轉而使用本地緩存存儲庫存數,這也就是本地緩存使用的第二個場景
    比如,一共有10件商品,2台機器,可以設置每台機器的本地內存中庫存等於10,那么對於外網的千萬個用戶,就可以有20個人搶到商品,剩下的人都 被擋在庫存之外。當這20個人搶到后,就可以實現另一個處理邏輯,從20個人中選出10個真正中標的人,獲得10個商品的購買權限。這個選擇的邏輯非常靈活,可隨意定制。但是從20選10的操作,無論如何也比從千千萬萬個人中選10要好的多,這樣可以確保秒殺的安全完成
    如果秒殺的人繼續增多,那么也可以通過客戶端(即javascript)設置格擋率的方法,使少量的用戶可以發出請求到服務器,絕大多數的用戶都被擋在瀏覽器上。(注:一些技術人士在2013年吐槽小米網站搶購小米手機時,瀏覽器模擬排隊等待其實沒有發出任何網絡請求,這就是客戶端格擋率生效的結果。)
 
-未完待續-
 

備注參考資源:
1,2013,淘寶中間件團隊, 說說會話串號
3,2013,淘寶搜索技術, 關於緩存(上)
4,2013,范凱, Web應用的緩存設計模式
5,2012,kenny, Cache Reload機制設計和實現(防止cache失效引發雪崩) (注:只看他的故障現象即可);
6,2010,timyang, Memcache mutex設計模式

贈圖幾枚:
Lindsey Stirling的小提琴專輯 http://music.douban.com/programme/276131
http://ww1.sinaimg.cn/mw1024/62a92ba0gw1e9t7gat0grj20om0hedlh.jpg
 
 
 
http://ww3.sinaimg.cn/mw1024/62a92ba0gw1e9t7o32inmj20ri0i1gt3.jpg
 
 
http://ww1.sinaimg.cn/mw1024/62a92ba0gw1e9pqry8t92g20dw07tk7k.gif
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM