寫在前面
讀寫分離、分庫分表、反範式化、採用 NoSQL……若是這些擴展手段全都上了,數據響應依舊愈來愈慢,還有什麼解決辦法嗎?mysql
有,加緩存。利用緩存層來吸取不均勻的負載和流量高峯:sql
Popular items can skew the distribution, causing bottlenecks. Putting a cache in front of a database can help absorb uneven loads and spikes in traffic.
一.在哪加?
理論上,在數據層以前的任意一層加緩存都可以阻擋流量,減小最終抵達數據庫的操做請求:數據庫
按緩存所處位置分爲 4 種:json
客戶端緩存:包括HTTP 緩存、瀏覽器緩存等瀏覽器
Web 緩存:例如CDN、反向代理服務等緩存
應用層緩存:例如Memcached、Redis等鍵值存儲app
爲了減輕數據庫的負載,咱們在應用程序和數據存儲之間加個鍵值存儲做爲緩衝層:異步
A cache is a simple key-value store and it should reside as a buffering layer between your application and your data storage.
經過內存中緩存的數據來響應一部分請求,而沒必要實際執行查庫操做,從而提高數據響應速度ide
二.存什麼?
常見的有兩種緩存模式:性能
Cached Database Queries:緩存原始查庫結果
緩存原始查庫結果
根據查詢語句生成key,將查庫結果緩存起來,例如:
key = "user.%s" % user_id user_blob = memcache.get(key) if user_blob is None: user = mysql.query("SELECT * FROM users WHERE user_id=\"%s\"", user_id) if user: memcache.set(key, json.dumps(user)) return user else: return json.loads(user_blob)
這種模式的主要缺陷在於難以處理緩存過時,由於數據與key(即查詢語句)之間並無明確的關聯,數據發生變化後,很難精確地刪掉緩存中的全部相關條目。試想,一個單元格發生變化,會影響哪些查詢語句?
儘管如此,這仍然是最經常使用的緩存模式,由於能夠作出妥協,好比:
只緩存與查詢語句有直接關聯的數據,排序、統計、篩選之類的計算結果通通都不存了
緩存數據對象
另外一種思路是將應用程序中的數據模型對象緩存起來,這樣原始數據與緩存之間就有了邏輯關聯,從而輕鬆解決緩存更新的難題
不管數據是如何查詢,如何加工轉換的,只把最終獲得的數據模型對象緩存起來,原始數據發生變化時,直接把相應的數據對象整個移除
對應用程序而言,數據對象比原始數據更容易管理和維護,所以,建議緩存數據對象,而不是原始數據
三.怎麼查?
常見的緩存數據訪問策略有 6 種:
Cache-aside/Lazy loading:預留緩存
Write-through:直寫式
Write-behind/Write-back:回寫式
Write-around:繞寫式
預留緩存模式下,緩存與數據庫之間沒有直接關係(緩存位於一旁,因此叫 Cache-aside),由應用程序將須要的數據從數據庫中讀出並填充到緩存中
數據請求優先走緩存,未命中緩存時才查庫,並把結果緩存起來,因此緩存是按需的(Lazy loading),只有實際訪問過的數據纔會被緩存起來
主要問題在於:
未命中緩存時須要 3 步,延遲不容忽視(對於冷啓動能夠手動預熱)
Read-through
直讀模式下,緩存擋在數據庫以前,應用程序不與數據庫直接交互,而是直接從緩存中讀取數據
未命中緩存時,由緩存負責查庫,並本身緩存起來。與預留緩存惟一的區別在於查庫的工做由緩存來完成,而不是應用程序
Write-through
相似於直讀模式,緩存也擋在數據庫以前,數據先寫到緩存,再寫入數據庫。也就是說,全部寫操做必須先通過緩存
通常與直讀式緩存相結合,雖然寫操做多過一層緩存(存在額外的延遲),但保證了緩存數據的一致性(避免緩存變舊)。此時,緩存就像數據庫的代理,讀寫都走緩存,緩存再查庫或將寫操做同步到數據庫
Write-behind/Write-back
回寫式緩存與直寫式很像,寫操做一樣要先通過緩存,惟一的區別在於異步寫入數據庫,進而容許批處理以及寫操做合併
一樣可以與直讀式緩存結合使用,並且不存在直寫式中寫操做的性能問題,但僅保證最終一致性
Write-around
所謂繞寫式緩存就是寫操做不通過(繞過)緩存,由應用程序直接寫入數據庫,僅緩存讀操做。可與預留緩存或直讀緩存結合使用:
Refresh-ahead
提早刷新,在緩存過時以前,自動刷新(從新加載)最近訪問過的條目。甚至能夠經過預加載來減小延遲,但若是預測不許反而會致使性能降低
四.塞滿了怎麼辦?
固然,緩存空間是極其有限的,因此還要有逐出策略(Eviction Policy),從緩存中剔除一些不太可能用到的條目,經常使用策略以下:
LRU(Least Recently Used):最經常使用的一種策略,根據程序運行時的局部性原理,在一段時間內,大機率訪問相同的數據,因此將最近沒有用到的數據剔除出去,好比訂機票,一段時間內大機率查詢同一路線
LFU(Least Frequently Used):根據使用頻率,將最不經常使用的數據剔除出去,好比輸入法大可能是根據詞頻聯想的
MRU(Most Recently Used):在有些場景下,須要刪掉最近用過的條目,好比已讀、再也不提醒、不感興趣等
這些策略還能夠結合使用,好比 LRU + LFU 綜合考慮,取決於具體場景
參考資料
Caching Overview
Scalability for Dummies – Part 3: Cache
Things You Should Know About Database Caching
All things caching- use cases, benefits, strategies, choosing a caching technology, exploring some popular products