緩存專題--服務(wù)端緩存

疑問:為何要使用服務(wù)端緩存?

1.對熱點(diǎn)數(shù)據(jù)進(jìn)行緩存,可以加快響應(yīng)速度

2.高并發(fā),大流量這種問題怎么解決?加機(jī)器抗,用緩存抗,優(yōu)化算法。。。

3.降低對數(shù)據(jù)服務(wù)器的壓力

(老鷹抓小雞),一句話----不惜一切代價將流量攔截,攔截,再攔截。

服務(wù)端緩存的分類:

數(shù)據(jù)庫緩存

SQL查詢流程:

命中條件

緩存存在一個hash表中,通過查詢SQL,查詢數(shù)據(jù)庫,客戶端協(xié)議等作為key,在判斷命中前,mysql不會解析SQL,而是使用SQL去查詢緩存,SQL上的任何字符的不同,如空格,注釋,都會導(dǎo)致緩存不命中。如果查詢有不確定的數(shù)據(jù)like now(),current_date(),那么查詢完成后結(jié)果者不會被緩存,包含不確定的數(shù)的是不會放置到緩存中。

數(shù)據(jù)庫緩存各種相關(guān)參數(shù):https://www.cnblogs.com/yesuuu/p/6114600.html

mysql緩存原理以及碎片問題:https://blog.csdn.net/qzqanzc/article/details/80418125

mysql一級緩存,二級緩存:https://www.cnblogs.com/maoyizhimi/p/7778504.html

一個實(shí)際的例子:(如何開啟mysql緩存可以自行查資料,僅僅是實(shí)驗(yàn)為說明原理,知道了這個后就當(dāng)沒有mysql緩存這個事兒。)

例子:show variables like '%query_cache%';

可以通過設(shè)置query_cache_type->OFF來關(guān)閉緩存,但這就將查詢緩沖永久地關(guān)閉了。在MySQL 5.0中提供了一種可以臨時關(guān)閉查詢緩沖的方法:

(1) SELECT SQL_NO_CACHE field1, field2 FROM TABLE1

以上的SQL語句由于使用了SQL_NO_CACHE,因此,不管這條SQL語句是否被執(zhí)行過,服務(wù)器都不會在緩沖區(qū)中查找,每次都會執(zhí)行它。

我們還可以將my.ini中的query_cache_type設(shè)成2,這樣只有在使用了SQL_CACHE后,才使用查詢緩沖。

(2) SELECT SQL_CALHE * FROM TABLE1

數(shù)據(jù)庫緩存失效機(jī)制:

自動失效

在表的結(jié)構(gòu)或數(shù)據(jù)發(fā)生改變時,查詢緩存中的數(shù)據(jù)不再有效。有這些INSERT、UPDATE、 DELETE、TRUNCATE、ALTERTABLE、DROPTABLE或DROPDATABASE會導(dǎo)致緩存數(shù)據(jù)失效。所以查詢緩存適合有大量相同查詢的應(yīng)用,不適合有大量數(shù)據(jù)更新的應(yīng)用。

手動失效

1.FLUSH QUERY CACHE;

2.清理查詢緩存內(nèi)存碎片

3.RESET? QUERY CACHE;

4.SQL會從查詢緩存中移出所有查詢

總結(jié):

mysql的查詢緩存利用率很低,原因是每當(dāng)有修改表內(nèi)容操作時,緩存中所有與該表相關(guān)的內(nèi)容全部要被清空。

mysql緩存還容易造成碎片問題。參考:https://blog.csdn.net/jiakairong/article/details/78958215

低版本mysql緩存默認(rèn)是開啟的,但從5.6開始默認(rèn)禁用query_cache_type參數(shù),禁用緩存。

建議:

不要使用mysql緩存,高版本的mysql的query_cache_type也是模式關(guān)閉的,咱們目前的云平臺庫中的mysql緩存也是關(guān)閉的。使用mysql數(shù)據(jù)庫緩存,加重了數(shù)據(jù)庫服務(wù)器的負(fù)擔(dān),而且利用率不一定高。尤其是對于經(jīng)常有變更(數(shù)據(jù)/結(jié)構(gòu))的情況下,頻繁寫入的緩存又不能高效利用。專人專職,大概率情況下不要讓廚子去寫代碼~

緩存服務(wù)器緩存

關(guān)于緩存穿透,緩存雪崩,key重建的話題就不聊了??梢詤⒖己啎倪@篇帖子,寫的不錯:

http://www.itdecent.cn/p/b126d466f01a

一、進(jìn)程內(nèi)緩存還是進(jìn)程外緩存

服務(wù)端緩存的分類:進(jìn)程內(nèi)緩存和進(jìn)程外緩存(redis/memcache)

進(jìn)程內(nèi)緩存的定義

將一些數(shù)據(jù)緩存在站點(diǎn),或者服務(wù)的進(jìn)程內(nèi),這就是進(jìn)程內(nèi)緩存。

進(jìn)程內(nèi)緩存的實(shí)現(xiàn)載體,最簡單的,可以是一個帶鎖的Map。又或者,可以使用第三方庫,例如leveldb。

進(jìn)程內(nèi)緩存能存儲什么?

redis/memcache等進(jìn)程外緩存服務(wù)能緩存什么,進(jìn)程內(nèi)緩存就能緩存什么。

進(jìn)程內(nèi)緩存可以存儲json數(shù)據(jù),可以存儲html頁面,可以存儲對象。

進(jìn)程內(nèi)緩存的好處:

1.與沒有緩存相比,進(jìn)程內(nèi)緩存的好處是,數(shù)據(jù)讀取不再需要訪問數(shù)據(jù)庫。

2.與進(jìn)程外緩存相比,進(jìn)程內(nèi) 緩存省去了網(wǎng)絡(luò)開銷,所以一來節(jié)省了內(nèi)網(wǎng)帶寬,而來響應(yīng)時延會更低。

進(jìn)程內(nèi)緩存的缺點(diǎn):

進(jìn)程外緩存雖然多了一次網(wǎng)絡(luò)交互,但仍然是統(tǒng)一存儲。站點(diǎn)和服務(wù)中的多個節(jié)點(diǎn)訪問統(tǒng)一的緩存服務(wù),數(shù)據(jù)統(tǒng)一存儲,容易保證數(shù)據(jù)的一致性。而進(jìn)程內(nèi)緩存,保存在多個站點(diǎn)和服務(wù)的節(jié)點(diǎn)內(nèi),數(shù)據(jù)存了多份,一致性比較難保證。

保證進(jìn)程內(nèi)緩存的數(shù)據(jù)一致性:

第一種方案:單節(jié)點(diǎn)通知其他節(jié)點(diǎn),寫請求發(fā)生在server1,在修改完自己內(nèi)存數(shù)據(jù)與數(shù)據(jù)庫中的數(shù)據(jù)后,主動通知其他server節(jié)點(diǎn),也修改內(nèi)存的數(shù)據(jù)。

缺點(diǎn):同一功能的一個集群的多個節(jié)點(diǎn),相互耦合在一起,特別是節(jié)點(diǎn)較多時,網(wǎng)狀連接關(guān)系會極其復(fù)雜。

第二種方案:通過MQ通知其他節(jié)點(diǎn)。寫請求發(fā)生在server1,在修改完自己內(nèi)存數(shù)據(jù)與數(shù)據(jù)庫中的數(shù)據(jù)之后,給MQ發(fā)布數(shù)據(jù)變化通知,其他server節(jié)點(diǎn)訂閱MQ消息,也修改內(nèi)存數(shù)據(jù)。

這種方案解決了節(jié)點(diǎn)間的耦合,但引入了MQ,使得系統(tǒng)更加復(fù)雜。

前兩種方案,節(jié)點(diǎn)數(shù)量較多,數(shù)據(jù)冗余份數(shù)越多,數(shù)據(jù)更新的原子性越難保證,一致性也就越難保證。

第三種方案:干脆放棄“實(shí)時一致性”,每個節(jié)點(diǎn)啟動一個timer,定時從后端拉取最新的數(shù)據(jù),更新內(nèi)存緩存。

缺點(diǎn):因?yàn)椴粚?shí)時,可能會讀到臟數(shù)據(jù)。

關(guān)于進(jìn)程內(nèi)緩存的總結(jié)說明:

不要頻繁使用進(jìn)程內(nèi)緩存:

分層架構(gòu)設(shè)計有一條準(zhǔn)則:站點(diǎn)層,服務(wù)層要做到無數(shù)據(jù),無狀態(tài),這樣才能任意的加節(jié)點(diǎn)水平擴(kuò)展,數(shù)據(jù)和狀態(tài)應(yīng)盡量存儲到后端的數(shù)據(jù)存儲服務(wù)(數(shù)據(jù)庫或者緩存服務(wù))。

何時可以使用進(jìn)程內(nèi)緩存:

1.只讀數(shù)據(jù),可以考慮在進(jìn)程啟動時加載到內(nèi)存。

2.一定程度上允許數(shù)據(jù)不一致的業(yè)務(wù)。(頁面對數(shù)據(jù)一致性要求較低,可以考慮使用進(jìn)程內(nèi)頁面緩存)

終了,一句話總結(jié):還是使用redis和memcache吧


二、redis還是memcache

memcache與redis的對比:

數(shù)據(jù)結(jié)構(gòu):vaue是哈希,列表,集合,有序集合這類復(fù)雜的數(shù)據(jù)結(jié)構(gòu)時,會選擇redis,因?yàn)閙c無法滿足這些需求。

持久化:mc無法滿足持久化的需求,只能選擇redis。

千萬不要把redis當(dāng)做數(shù)據(jù)庫使用:

1> redis的定期快照不能保證數(shù)據(jù)不丟失

2> redis的AOF(Append Only file 持久化功能)會降低效率,并且不能支持太大的數(shù)據(jù)量。

緩存場景下,開啟固化功能,有什么利弊?

優(yōu)點(diǎn):redis掛了重啟,內(nèi)存里能夠快速恢復(fù)熱數(shù)據(jù),不會瞬時將壓力壓到數(shù)據(jù)庫上,沒有一個cache預(yù)熱的過程。

缺點(diǎn):在 redis掛了的過程中,如果數(shù)據(jù)庫中的數(shù)據(jù)有修改,可能導(dǎo)致redis重啟后,數(shù)據(jù)庫與redis的數(shù)據(jù)不一致。

因此,只讀場景,或者允許一些不一致的業(yè)務(wù)場景,可以嘗試開啟redis的固化功能。

天然高可用:redis天然支持分布式集群功能,可以實(shí)現(xiàn)主動復(fù)制,讀寫分離。而memcache,要想實(shí)現(xiàn)高可用,需要進(jìn)行二次開發(fā)。

思考:(大部分業(yè)務(wù)場景,緩存真的需要高可用嗎)

1>緩存場景,很多時候,是允許cache miss的

2>緩存掛了,很多時候后可以通過DB讀取數(shù)據(jù)

存儲內(nèi)容比較大:memcache的value存儲,最大為1M,如果存儲的value很大,只能使用redis

redis中key和value的最大大小限制:https://redis.io/topics/data-types-intro

redis中key的數(shù)量限制:https://redis.io/topics/faq

(redis官網(wǎng)的一句話,redis不會限制你對緩存量大小的限制,僅僅取決于你機(jī)器配置的內(nèi)存)

什么時候傾向于memcache?

純KV,緩存對象小于1M,key的長度大于250個字符,沒有持久化要求,基本不需要分布式集群,高并發(fā)(秒殺場景,抗量),單臺機(jī)器多核利用(redis是單線程的)的情況,使用memcache或許更適合。

底層實(shí)現(xiàn)機(jī)制差異:

內(nèi)存分配:

memcache使用預(yù)分配內(nèi)存池的方式管理內(nèi)存,能夠省去內(nèi)存分配時間。redis則是臨時申請空間,可能導(dǎo)致碎片。

虛擬內(nèi)存使用:

memcache把所有的數(shù)據(jù)存儲在物理內(nèi)存中。redis有自己的VM機(jī)制,理論上能夠存儲比物理內(nèi)存更多的數(shù)據(jù),當(dāng)數(shù)據(jù)量超量時,會引發(fā)swap,把冷數(shù)據(jù)刷道磁盤上。

網(wǎng)絡(luò)模型:

memcache使用非阻塞IO復(fù)用模型,redis也是使用非阻塞IO復(fù)用模型。

redis還提供了一些非KV存儲之外的排序,聚合功能,在執(zhí)行這些功能時,復(fù)雜的CPU計算,會阻塞整個IO調(diào)度。

線程模型:

memcache使用多線程,主線程監(jiān)聽,worker線程接受請求,執(zhí)行讀寫,這個過程中,可能存在鎖沖突。redis使用單線程,雖無鎖沖突,但難以利用多核的特性提升整體吞吐量。

三、緩存,淘汰還是修改

1.KV緩存都緩存了一些什么數(shù)據(jù)?

(1)樸素類型的數(shù)據(jù),例如:int

(2)序列化后的對象,例如:User實(shí)體,本質(zhì)是binary

(3)文本數(shù)據(jù),例如:json或者h(yuǎn)tml

2.淘汰緩存和修改緩存有什么區(qū)別?

(1)淘汰某個key,操作簡單,直接將key置為無效,但下一次該key的訪問會cache miss

(2)修改某個key的內(nèi)容,邏輯相對復(fù)雜,但下一次該key的訪問仍會cache hit

可以看到,差異僅僅在于一次cache miss。

3.緩存中的value數(shù)據(jù)一般是怎么修改的?

(1)樸素類型的數(shù)據(jù),視情況而定。

(2)序列化后的對象:一般需要先get數(shù)據(jù),反序列化成對象,修改其中的成員,再序列化為binary,再set數(shù)據(jù)

(3)json或者h(yuǎn)tml數(shù)據(jù):一般也需要先get文本,parse成dom樹對象,修改相關(guān)元素,序列化為文本,再set數(shù)據(jù)

結(jié)論:對于對象類型,或者文本類型,修改緩存value的成本較高,一般選擇直接淘汰緩存。

糾結(jié)于到底是修改緩存還是淘汰緩存的時候就是需要對比兩者成本的時候,修改的成本小就直接修改,修改的成本較高就直接淘汰。

四、先操作數(shù)據(jù)庫還是先操作緩存?

https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961341&idx=1&sn=e27916b8e96bd771c72c055f1f53e5be&chksm=bd2d02218a5a8b37ecffd78d20b65501645ac07c7ba2eb65b7e501a3eb9de023febe63bfdb36&scene=21#wechat_redirect

這篇沈劍的帖子給出的建議是先操作緩存,然后再操作數(shù)據(jù)庫。但我感覺有問題,多線程操作的時候,可能數(shù)據(jù)不一致。

比如:A,B兩個線程要做更新,C線程做查詢。A先刪除緩存(成功),B后刪除緩存(成功),B先更新了數(shù)據(jù)庫,這時候C線程讀取到了數(shù)據(jù)庫的數(shù)據(jù)(B)的數(shù)據(jù),并更新到了緩存中,然后A后更新了數(shù)據(jù)庫,結(jié)果是:數(shù)據(jù)庫中是A的數(shù)據(jù),緩存中是B的數(shù)據(jù)。(緩存有效期,延時雙刪策略(cache aside pattern))

為何在寫數(shù)據(jù)庫的時候,是刪除緩存而不是重新設(shè)置緩存?

答:如果A,B兩個線程同時做數(shù)據(jù)更新,A先更新了數(shù)據(jù)庫,B后更新數(shù)據(jù)庫,則此時數(shù)據(jù)庫里存的是B的數(shù)據(jù)。而更新緩存的時候,是B先更新了緩存,而A后更新了緩存,則緩存里是A的數(shù)據(jù)。這樣緩存和數(shù)據(jù)庫的數(shù)據(jù)也不一致。

一、先更新緩存,再更新數(shù)據(jù)庫

(這里對緩存的操作都是刪除,而不是設(shè)置)

更新緩存(失?。辉倮^續(xù)----->合理

更新緩存(成功),更新數(shù)據(jù)庫成功---->合理

更新緩存(成功),更新數(shù)據(jù)庫失敗----->合理,僅僅是會在后續(xù)過程多一次對數(shù)據(jù)庫的查詢

但會存在我上邊提到的疑問。

二、先更新數(shù)據(jù)庫,再更新緩存

更新數(shù)據(jù)庫(失?。辉倮^續(xù)---->合理

更新數(shù)據(jù)庫(成功),更新緩存(成功)------->合理

更新數(shù)據(jù)庫(成功),再更新緩存(失敗)-------->不合理,這時讀取到的是舊數(shù)據(jù)

這種方式,可以通過集中方式來彌補(bǔ):

1.給緩存設(shè)置有效期,這樣錯誤的緩存遲早會變成正確的。而且過期時間越短,緩存的正確率越高。但是,這樣也同時會增加數(shù)據(jù)服務(wù)器的負(fù)擔(dān)。

2.當(dāng)?shù)诙街械母拢▌h除)緩存失敗的時候,進(jìn)行重試。

3.定期全量更新(定期把緩存全部失效,然后全量加載),這種方式值得商榷,有可能會有大量的請求在這時打到數(shù)據(jù)庫。


還有另外一種情況:

A,B兩個線程讀取緩存,C線程進(jìn)行寫數(shù)據(jù)庫。

A讀緩存,沒有數(shù)據(jù),進(jìn)入讀庫流程,從數(shù)據(jù)庫讀取數(shù)據(jù),讀取成功,還沒有寫緩存。

C進(jìn)來寫數(shù)據(jù)庫,寫成功。

B讀緩存,沒有數(shù)據(jù),進(jìn)入讀庫流程,從數(shù)據(jù)庫讀取數(shù)據(jù)。讀取成功(B讀取的是C寫入的最新數(shù)據(jù)),還沒有寫緩存。

B寫緩存成功,然后A寫緩存成功,結(jié)果---->數(shù)據(jù)庫是最新數(shù)據(jù),緩存中是舊的數(shù)據(jù)。

這個時候需要使用redis分布式鎖(https://www.imooc.com/article/34098)。也可以使用有效期策略。(看具體業(yè)務(wù)要求)

五、緩存與數(shù)據(jù)庫不一致,怎么辦?

(1+2)先一個寫請求,淘汰緩存,寫數(shù)據(jù)庫

(3+4+5)接著立刻一個讀請求,讀緩存,cache miss,讀從庫,寫緩存放入數(shù)據(jù),以便后續(xù)的讀能夠cache hit(主從同步?jīng)]有完成,緩存中放入了舊數(shù)據(jù))

(6)最后,主從同步完成

導(dǎo)致的結(jié)果是:舊數(shù)據(jù)放入緩存,即使主從同步完成,后續(xù)仍然會從緩存一直讀取到舊數(shù)據(jù)。

可以看到,加入緩存后,導(dǎo)致的不一致影響時間會很長,并且最終也不會達(dá)到一致。

(6)主從同步

(7)通過工具訂閱從庫的binlog,這里能夠最準(zhǔn)確的知道,從庫數(shù)據(jù)同步完成的時間

畫外音:本圖畫的訂閱工具是DTS,可以是cannal,也可以自己訂閱和分析binlog

(8)從庫執(zhí)行完寫操作,向緩存再次發(fā)起刪除,淘汰這段時間內(nèi)可能寫入緩存的舊數(shù)據(jù)

如此這般,至少能夠保證,引入緩存之后,主從不一致,不會比沒有引入緩存更壞。

畫外音:即使引入緩存,也只有一個很小的時間間隔,可能讀到舊數(shù)據(jù)。

也可以使用延時雙刪策略

六、主從數(shù)據(jù)庫不一致,怎么辦?

常見的數(shù)據(jù)庫集群架構(gòu):

一主多從,主從同步,讀寫分離

為何會主從不一致,因?yàn)橥綍袝r間差:

如何避免這種時間差導(dǎo)致的不一致:

方案一:忽略

任何脫離業(yè)務(wù)的架構(gòu)設(shè)計都是耍流氓,絕大部分業(yè)務(wù),例如:百度搜索,淘寶訂單,QQ消息,58帖子都允許短時間不一致。

如果業(yè)務(wù)能夠接受,別把系統(tǒng)架構(gòu)搞得太復(fù)雜。

方案二:強(qiáng)制讀主

(1)使用一個高可用主庫提供數(shù)據(jù)庫服務(wù)

(2)讀和寫都落到主庫上

(3)采用緩存來提升系統(tǒng)讀性能

方案三:選擇性讀

(1)寫主庫

(2)將哪個庫,哪個表,哪個主鍵三個信息拼裝一個key設(shè)置到cache里,這條記錄的超時時間,設(shè)置為“主從同步時延”

key的格式為“db:table:PK”,假設(shè)主從延時為1s,這個key的cache超時時間也為1s。

這是要讀哪個庫,哪個表,哪個主鍵的數(shù)據(jù)呢,也將這三個信息拼裝一個key,到cache里去查詢,如果,

(1)cache里有這個key,說明1s內(nèi)剛發(fā)生過寫請求,數(shù)據(jù)庫主從同步可能還沒有完成,此時就應(yīng)該去主庫查詢

(2)cache里沒有這個key,說明最近沒有發(fā)生過寫請求,此時就可以去從庫查詢

以此,保證讀到的一定不是不一致的臟數(shù)據(jù)。

總結(jié)

數(shù)據(jù)庫主庫和從庫不一致,常見有這么幾種優(yōu)化方案:

(1)業(yè)務(wù)可以接受,系統(tǒng)不優(yōu)化

(2)強(qiáng)制讀主,高可用主庫,用緩存提高讀性能

(3)在cache里記錄哪些記錄發(fā)生過寫請求,來路由讀主還是讀從



參考資料:

何時使用mysql緩存以及提高緩存命中率的建議:https://blog.csdn.net/qq_25622107/article/details/80223470

現(xiàn)代WEB應(yīng)用程序的服務(wù)器端緩存策略??https://alankent.me/2018/08/25/server-side-caching-strategies-for-modern-web-applications/

58沈劍 緩存??https://mp.weixin.qq.com/s/V1hGa6D9aGrP6PiCWEmc0w

Cache Aside Pattern?https://mp.weixin.qq.com/s/-fk-cEIo3iDCUSwT_l8d2w

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容