太強了,全面解析快取應用經典問題
前言
隨著網際網路從簡單的單向瀏覽請求,發展為基於使用者個性資訊的定製化以及社交化的請求,這要求產品需要做到以使用者和關係為基礎,對海量資料進行分析和計算。對於後端服務來說,意味著使用者的每次請求都需要查詢使用者的個人資訊和大量的關係資訊,此外大部分場景還需要對上述資訊進行聚合、過濾、排序,最終才能返回給使用者。
CPU是資訊處理、程式執行的最終執行單元,如果它的世界也有“秒”的概念,假設它的時鐘跳一下為一秒,那麼在CPU(CPU的一個核心)眼中的時間概念是什麼樣的呢?
可見I/O的速度與CPU和記憶體相比是要差幾個數量級的,如果資料全部從資料庫獲取,一次請求涉及多次資料庫操作會大大增加響應時間,無法提供好的使用者體驗。
對於大型高併發場景下的Web應用,快取更為重要,更高的快取命中率就意味著更好的效能。快取系統的引入,是提升系統響應時延、提升使用者體驗的唯一途徑,良好的快取架構設計也是高併發系統的基石。
快取的思想基於以下幾點:
時間侷限性原理 程式有在一段時間內多次訪問同一個資料塊的傾向。例如一個熱門的商品或者一個熱門的新聞會被數以百萬甚至千萬的更多使用者檢視。透過快取,可以高效地重用之前檢索或計算的資料。
以空間換取時間 對於大部分系統,全量資料通常儲存在MySQL 或者Hbase,但是它們的訪問效率太低。所以會開闢一個高速的訪問空間來加速訪問過程,例如Redis讀的速度是110000次/s,寫的速度是81000次/s 。
效能和成本的Tradeoff 高速的訪問空間帶來的是成本的提升,在系統設計時要兼顧效能和成本。例如,在相同成本的情況下,SSD 硬碟容量會比記憶體大 10~30 倍以上,但讀寫延遲卻高 50~100 倍。
引入快取會給系統帶來以下優勢:
提升請求效能
降低網路擁塞
減輕服務負載
增強可擴充套件性
同樣的,引入快取也會帶來以下劣勢:
毫無疑問會增加系統的複雜性,開發複雜性和運維複雜性成倍提升。
高速的訪問空間會比資料庫儲存的成本高。
由於一份資料同時存在快取和資料庫中,甚至快取內部也會有多個資料副本,多份資料就會存在資料雙寫的不一致問題,同時快取體系本身也會存在可用性問題和分割槽的問題。
在快取系統的設計架構中,還有很多坑,很多的明槍暗箭,如果設計不當會導致很多嚴重的後果。設計不當,輕則請求變慢、效能降低,重則會資料不一致、系統可用性降低,甚至會導致快取雪崩,整個系統無法對外提供服務。
2
快取的主要儲存模式
三種模式各有優劣,適用於不同的業務場景,不存在最佳模式。
● Cache Aside(旁路快取)
寫: 更新db時,刪除快取,當下次讀取資料庫時,驅動快取的更新。
讀: 讀的時候先讀快取,快取未命中,那麼就讀資料庫,並且將資料回種到快取,同時返回相應結果
特點:懶載入思想,以資料庫中的資料為準。在稍微複雜點的快取場景,快取都不簡單是資料庫中直接取出來的,可能還需要從其他表查詢一些資料,然後進行一些複雜的運算,才能最終計算出值。這種儲存模式適合於對資料一致性要求比較高的業務,或者是快取資料更新比較複雜、代價比較高的業務。例如:一個快取涉及多個表的多個欄位,在1分鐘內被修改了100次,但是這個快取在1分鐘內就被讀取了1次。如果使用這種儲存模式只刪除快取的話,那麼1分鐘內,這個快取不過就重新計算一次而已,開銷大幅度降低。
● Read/Write Through(讀寫穿透)
寫: 快取存在,更新資料庫,快取不存在,同時更新快取和資料庫
讀: 快取未命中,由快取服務載入資料並且寫入快取
特點:
讀寫穿透對熱資料友好,特別適合有冷熱資料區分的場合。
1)簡化應用程式程式碼
在快取方法中,應用程式程式碼仍然很複雜,並且直接依賴於資料庫,如果多個應用程式處理相同的資料,甚至會出現程式碼重複。讀寫穿透模式將一些資料訪問程式碼從應用程式轉移到快取層,這極大地簡化了應用程式並更清晰地抽象了資料庫操作。
2)具有更好的讀取可伸縮性
在多數情況下,快取資料過期以後,多個並行使用者執行緒最終會打到資料庫,再加上數以百萬計的快取項和數千個並行使用者請求,資料庫上的負載會顯著增加。讀寫穿透可以保證應用程式永遠不會為這些快取項訪問資料庫,這也可以讓資料庫負載保持在最小值。
3)具有更好的寫效能
讀寫穿透模式可以讓應用程式快速更新快取並返回,之後它讓快取服務在後臺更新資料庫。當資料庫寫操作的執行速度不如快取更新的速度快時,還可以指定限流機制,將資料庫寫操作安排在非高峰時間進行,減輕資料庫的壓力。
4)過期時自動重新整理快取
讀寫穿透模式允許快取在過期時自動從資料庫重新載入物件。這意味著應用程式不必在高峰時間訪問資料庫,因為最新資料總是在快取中。
● Write Behind Caching(非同步快取寫入)
寫:只更新快取,快取服務非同步更新資料庫。
讀:快取未命中由封裝好的快取服務載入資料並且寫入快取。
特點:寫效能最高,定期非同步重新整理資料庫資料,資料丟失的機率大,適合寫頻率高,並且寫操作需要合併的場景。使用非同步快取寫入模式,資料的讀取和更新透過快取進行,與讀寫穿透模式不同,更新的資料並不會立即傳到資料庫。相反,在快取服務中一旦進行更新操作,快取服務就會跟蹤髒記錄列表,並定期將當前的髒記錄集重新整理到資料庫中。作為額外的效能改善,快取服務會合並這些髒記錄,合併意味著如果相同的記錄被更新,或者在緩衝區內被多次標記為髒資料,則只保證最後一次更新。對於那些值更新非常頻繁,例如金融市場中的股票價格等場景,這種方式能夠很大程度上改善效能。如果股票價格每秒鐘變化 100 次,則意味著在 30 秒內會發生 30 x 100 次更新,合併將其減少至只有一次。
3
快取7大經典問題
的常用解決方案
1快取集中失效
快取集中失效大多數情況出現在高併發的時候,如果大量的快取資料集中在一個時間段失效,查詢請求會打到資料庫,資料庫壓力凸顯。比如同一批火車票、飛機票,當可以售賣時,系統會一次性載入到快取,並且過期時間設定為預先配置的固定時間,那過期時間到期後,系統就會因為熱點資料的集中沒有命中而出現效能變慢的情況。
解決方案:
使用基準時間+隨機時間,降低過期時間的重複率,避免集體失效。即相同業務資料設定快取失效時間時,在原來設定的失效時間基礎上,再加上一個隨機值,讓資料分散過期,同時對資料庫的請求也會分散開,避免瞬時全部過期對資料庫造成過大壓力。
2快取穿透
快取穿透是指一些異常訪問,每次都去查詢壓根兒就不存在的key,導致每次請求都會打到資料庫上去。例如查詢不存在的使用者,查詢不存在的商品id。如果是使用者偶爾錯誤輸入,問題不大。但如果是一些特殊使用者,控制一批肉雞,持續的訪問快取不存在的key,會嚴重影響系統的效能,影響正常使用者的訪問,甚至可能會讓資料庫直接當機。我們在設計系統時,通常只考慮正常的訪問請求,所以這種情況往往容易被忽略。
解決方案:
第一種方案就是,查詢到不存在的資料時,首次查詢資料庫,即便資料庫沒有資料,仍然回種這個 key 到快取,並使用一個特殊約定的value表示這個key的值為空。後面再次出現對這個key的請求時,直接返回null。為了健壯性,設定空快取key時,一定要設定過期時間,以防止之後該key被寫入了資料。
第二種方案是,構建一個 BloomFilter 快取過濾器,記錄全量資料,這樣訪問資料時,可以直接透過 BloomFilter 判斷這個 key 是否存在,如果不存在直接返回即可,壓根兒不需要查詢快取或資料庫。比如,可以使用基於資料庫增量日誌解析框架(阿里的canal),透過消費增量資料寫入到BloomFilter 過濾器。BloomFilter的所有操作也是在記憶體裡實現,效能很高,要達到 1% 的誤判率,平均單條記錄佔用 1.2 位元組即可。同時需要注意的是BloomFilter 只有新增沒有刪除操作,對於已經刪除的key可以配合上述快取空值解決方案一起使用。Redis提供了自定義引數的布隆顧慮器,可以使用bf.reserve進行建立,需要設定引數error_rate(錯誤率)和 innitial_size。error_rate越低需要的空間越大,innitial_size表示預計放入的元素數量,當實際數量超過這個值以後,誤判率會上升。
3
快取雪崩
解決方案:
事前:快取採用高可用架構設計,redis使用叢集部署方式。對重要業務資料的資料庫訪問新增開關,當發現資料庫出現阻塞、響應慢超過閾值的時候,關閉開關,將一部分或者全都的資料庫請求執行failfast操作。
事中:引入多級快取架構,增加快取副本,比如新增本地 ehcache 快取。引入限流降級元件,對快取進行實時監控和實時報警。透過機器替換、服務替換進行及時恢復;也可以透過各種自動故障轉移策略,自動關閉異常介面、停止邊緣服務、停止部分非核心功能措施,確保在極端場景下,核心功能的正常執行。
事後:redis持久化,支援同時開啟兩種持久化方式,我們可以綜合使用 AOF 和 RDB 兩種持久化機制,用 AOF 來保證資料不丟失,作為資料恢復的第一選擇; 用 RDB 來做不同程度的冷備,在 AOF 檔案都丟失或損壞不可用的時候,還可以使用 RDB 來進行快速的資料恢復。同時把RDB 資料備份到遠端的雲服務,如果伺服器記憶體和磁碟的資料同時丟失,依然可以從遠端拉取資料做災備恢復操作。
4快取資料不一致
解決方案:
設定key的過期時間儘量短,讓快取更早的過期,從db載入新資料,這樣無法保證資料的強一致性,但是可以保證最終一致性。
cache更新失敗以後引入重試機制,比如連續重試失敗以後,可以將操作寫入重試佇列,當快取服務可用時,將這些key從快取中刪除,當這些key被重新查詢時,重新從資料庫回種。
延時雙刪除策略,首先刪除快取中的資料,在寫資料庫,休眠一秒以後(具體時間需要根據具體業務邏輯的耗時進行調整)再次刪除快取。這樣可以將一秒內造成的所有髒資料再次刪除。
快取最終一致性,使客戶端資料與快取解耦,應用直接寫資料到資料庫中。資料庫更新binlog日誌,利用Canal中介軟體讀取binlog日誌。Canal藉助於限流元件按頻率將資料發到MQ中,應用監控MQ通道,將MQ的資料更新到Redis快取中。
更新資料的時候,根據資料的唯一標識,將操作路由之後,傳送到一個 jvm 內部佇列中。讀取資料的時候,如果發現資料不在快取中,那麼將重新執行“讀取資料+更新快取”的操作,根據唯一標識路由之後,也傳送到同一個 jvm 內部佇列中。該方案對於讀請求進行了非常輕度的非同步化,使用一定要注意讀超時的問題,每個讀請求必須在超時時間範圍內返回。因此需要根據自己的業務情況進行測試,可能需要部署多個服務,每個服務分攤一些資料的更新操作。如果一個記憶體佇列里居然會擠壓 100 個業務資料的修改操作,每個操作操作要耗費 10ms 去完成,那麼最後一個讀請求,可能等待 10 * 100 = 1000ms = 1s 後,才能得到資料,這個時候就導致讀請求的長時阻塞。
5競爭併發
解決方案:
分散式鎖+時間戳
可以基於redis或者zookeeper實現一個分散式鎖,當一個key被高併發訪問時,讓請求去搶鎖。也可以引入訊息中介軟體,把Redis.set操作放在訊息佇列中。總之,將並行讀寫改成序列讀寫的方式,從而來避免資源競爭。對於key的操作的順序性問題,可以透過設定一個時間戳來解決。大部分場景下,要寫入快取的資料都是從資料庫中查詢出來的。在資料寫入資料庫時,可以維護一個時間戳欄位,這樣資料被查詢出來時都會帶一個時間戳。寫快取的時候,可以判斷一下當前資料的時間戳是否比快取裡的資料的時間戳要新,這樣就避免了舊資料對新資料的覆蓋。
6熱點Key問題
解決方案:
要解決這種極熱 key 的問題,首先要找出這些 熱key 。對於重要節假日、線上促銷活動、憑藉經驗可以提前評估出可能的熱 key 來。而對於突發事件,無法提前評估,可以透過 Spark或者Flink,進行流式計算,及時發現新發布的熱點 key。而對於之前已發出的事情,逐步發酵成為熱 key 的,則可以透過 Hadoop 進行離線跑批計算,找出最近歷史資料中的高頻熱 key。還可以透過客戶端進行統計或者上報。找到熱 key 後,就有很多解決辦法了。首先可以將這些熱 key 進行分散處理。redis cluster有固定的16384個hash slot,對每個key計算CRC16值,然後對16384取模,可以獲取key對應的hash slot。比如一個熱 key 名字叫 hotkey,可以被分散為 hotkey#1、hotkey#2、hotkey#3,……hotkey#n,這 n 個 key 就會分散存在多個快取節點,然後 client 端請求時,隨機訪問其中某個字尾的熱key,這樣就可以把熱 key 的請求打散。
7大Key問題
解決方案:
如果資料存在 Redis 中,比如業務資料存 set 格式,大 key 對應的 set 結構有幾千幾萬個元素,這種寫入 Redis 時會消耗很長的時間,導致 Redis 卡頓。此時,可以擴充套件新的資料結構,同時讓 client 在這些大 key 寫快取之前,進行序列化構建,然後透過 restore 一次性寫入。
將大 key 分拆為多個 key,儘量減少大 key 的存在。同時由於大 key 一旦穿透到 DB,載入耗時很大,所以可以對這些大 key 進行特殊照顧,比如設定較長的過期時間,比如快取內部在淘汰 key 時,同等條件下,儘量不淘汰這些大 key。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2927190/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 全面解析快取應用經典問題快取
- 七大快取經典問題快取
- Redis應用場景及快取問題Redis快取
- Spring 快取註解這樣用,太香了!Spring快取
- nginx DNS 解析快取的更新問題NginxDNS快取
- 系統快取全解析4:應用程式資料快取快取
- Google經典面試題解析Go面試題
- WEB 應用快取解析以及使用 Redis 實現分散式快取Web快取Redis分散式
- 部分JS經典題目解析JS
- Spring Cache 快取註解這樣用,實在是太香了!Spring快取
- 8道經典JavaScript面試題解析,你真的掌握JavaScript了嗎?JavaScript面試題
- 快應用稽核常見問題
- 快取的問題快取
- beego快取問題Go快取
- SOAP快取問題快取
- flutter 獲取應用快取以及清除快取Flutter快取
- [面試題]事件迴圈經典面試題解析面試題事件
- 快取問題(一) 快取穿透、快取雪崩、快取併發 核心概念快取穿透
- 應用基礎框架全面解析框架
- 八數碼 經典問題
- 快取問題(四) 快取穿透、快取雪崩、快取併發 解決案例快取穿透
- 阿里排查神器,太強了!阿里
- 快取同步的問題快取
- SQL language裡面的經典問題SQL
- Angular 伺服器端渲染應用的開箱即用的快取功能問題Angular伺服器快取
- 經典Java面試題彙總及答案解析Java面試題
- 揹包問題的一道經典問題
- CSS 穿牆術!太強了CSS
- 快取,你真的用對了麼?快取
- Redis 面試常見問題———快取雪崩、快取擊穿以及快取穿透Redis面試快取穿透
- IE8快取問題快取
- java script css快取問題JavaCSS快取
- Redis快取穿透、快取雪崩、redis併發問題分析Redis快取穿透
- 程式碼解決快取穿透和快取雪崩問題快取穿透
- 聽說"快應用"了沒?
- 快應用richtext元件背景色填充問題元件
- 淺解強快取和協商快取快取
- 經典演算法-最大流問題演算法