本文主要討論這麼幾個問題:
數據庫
(1)「緩存與數據庫」需求緣起緩存
(2)「淘汰緩存」仍是「更新緩存」架構
(3)緩存和數據庫的操做時序異步
(4)緩存和數據庫架構簡析async
1、需求緣起工具
場景介紹性能
緩存是一種提升系統讀性能的常見技術,對於讀多寫少的應用場景,咱們常用緩存來進行優化。優化
例如對於用戶的餘額信息表account(uid, money),業務上的需求是:ui
(1)查詢用戶的餘額,SELECT money FROM account WHERE uid=XXX,佔99%的請求spa
(2)更改用戶餘額,UPDATE account SET money=XXX WHERE uid=XXX,佔1%的請求
因爲大部分的請求是查詢,咱們在緩存中創建uid到money的鍵值對,可以極大下降數據庫的壓力。
讀操做流程
有了數據庫和緩存兩個地方存放數據以後(uid->money),每當須要讀取相關數據時(money),操做流程通常是這樣的:
(1)讀取緩存中是否有相關數據,uid->money
(2)若是緩存中有相關數據money,則返回【這就是所謂的數據命中「hit」】
(3)若是緩存中沒有相關數據money,則從數據庫讀取相關數據money【這就是所謂的數據未命中「miss」】,放入緩存中uid->money,再返回
緩存的命中率 = 命中緩存請求個數/總緩存訪問請求個數 = hit/(hit+miss)
上面舉例的餘額場景,99%的讀,1%的寫,這個緩存的命中率是很是高的,會在95%以上。
那麼問題來了
當數據money發生變化的時候:
(1)是更新緩存中的數據,仍是淘汰緩存中的數據呢?
(2)是先操縱數據庫中的數據再操縱緩存中的數據,仍是先操縱緩存中的數據再操縱數據庫中的數據呢?
(3)緩存與數據庫的操做,在架構上是否有優化的空間呢?
這是本文關注的三個核心問題。
2、更新緩存 VS 淘汰緩存
什麼是更新緩存:數據不但寫入數據庫,還會寫入緩存
什麼是淘汰緩存:數據只會寫入數據庫,不會寫入緩存,只會把數據淘汰掉
更新緩存的優勢:緩存不會增長一次miss,命中率高
淘汰緩存的優勢:簡單(我去,更新緩存我也以爲很簡單呀,樓主你太敷衍了吧)
那究竟是選擇更新緩存仍是淘汰緩存呢,主要取決於「更新緩存的複雜度」。
例如,上述場景,只是簡單的把餘額money設置成一個值,那麼:
(1)淘汰緩存的操做爲deleteCache(uid)
(2)更新緩存的操做爲setCache(uid, money)
更新緩存的代價很小,此時咱們應該更傾向於更新緩存,以保證更高的緩存命中率
若是餘額是經過很複雜的數據計算得出來的,例如業務上除了帳戶表account,還有商品表product,折扣表discount
account(uid, money)
product(pid, type, price, pinfo)
discount(type, zhekou)
業務場景是用戶買了一個商品product,這個商品的價格是price,這個商品從屬於type類商品,type類商品在作促銷活動要打折扣zhekou,購買了商品事後,這個餘額的計算就複雜了,須要:
(1)先把商品的品類,價格取出來:SELECT type, price FROM product WHERE pid=XXX
(2)再把這個品類的折扣取出來:SELECT zhekou FROM discount WHERE type=XXX
(3)再把原有餘額從緩存中查詢出來money = getCache(uid)
(4)再把新的餘額寫入到緩存中去setCache(uid, money-price*zhekou)
更新緩存的代價很大,此時咱們應該更傾向於淘汰緩存。
however,淘汰緩存操做簡單,而且帶來的反作用只是增長了一次cache miss,建議做爲通用的處理方式。
3、先操做數據庫 vs 先操做緩存
OK,當寫操做發生時,假設淘汰緩存做爲對緩存通用的處理方式,又面臨兩種抉擇:
(1)先寫數據庫,再淘汰緩存
(2)先淘汰緩存,再寫數據庫
究竟採用哪一種時序呢?
還記得在《冗餘表如何保證數據一致性》文章(點擊查看)裏「究竟先寫正表仍是先寫反表」的結論麼?
對於一個不能保證事務性的操做,必定涉及「哪一個任務先作,哪一個任務後作」的問題,解決這個問題的方向是:
若是出現不一致,誰先作對業務的影響較小,就誰先執行。
因爲寫數據庫與淘汰緩存不能保證原子性,誰先誰後一樣要遵循上述原則。
假設先寫數據庫,再淘汰緩存:第一步寫數據庫操做成功,第二步淘汰緩存失敗,則會出現DB中是新數據,Cache中是舊數據,數據不一致。
假設先淘汰緩存,再寫數據庫:第一步淘汰緩存成功,第二步寫數據庫失敗,則只會引起一次Cache miss。
結論:數據和緩存的操做時序,結論是清楚的:先淘汰緩存,再寫數據庫。
4、緩存架構優化
上述緩存架構有一個缺點:業務方須要同時關注緩存與DB,有沒有進一步的優化空間呢?有兩種常見的方案,一種主流方案,一種非主流方案(一家之言,勿拍)。
主流優化方案是服務化:加入一個服務層,向上遊提供帥氣的數據訪問接口,向上遊屏蔽底層數據存儲的細節,這樣業務線不須要關注數據是來自於cache仍是DB。
非主流方案是異步緩存更新:業務線全部的寫操做都走數據庫,全部的讀操做都總緩存,由一個異步的工具來作數據庫與緩存之間數據的同步,具體細節是:
(1)要有一個init cache的過程,將須要緩存的數據全量寫入cache
(2)若是DB有寫操做,異步更新程序讀取binlog,更新cache
在(1)和(2)的合做下,cache中有所有的數據,這樣:
(a)業務線讀cache,必定可以hit(很短的時間內,可能有髒數據),無需關注數據庫
(b)業務線寫DB,cache中能獲得異步更新,無需關注緩存
這樣將大大簡化業務線的調用邏輯,存在的缺點是,若是緩存的數據業務邏輯比較複雜,async-update異步更新的邏輯可能也會比較複雜。
5、其餘未盡事宜
本文只討論了緩存架構設計中須要注意的幾個細節點,若是數據庫架構採用了一主多從,讀寫分離的架構,在特殊時序下,還極可能引起數據庫與緩存的不一致,這個不一致如何優化,後續的文章再討論吧。
6、結論強調
(1)淘汰緩存是一種通用的緩存處理方式
(2)先淘汰緩存,再寫數據庫的時序是毋庸置疑的
(3)服務化是向業務方屏蔽底層數據庫與緩存複雜性的一種通用方式