輕松構(gòu)建微服務(wù)之高效緩存

前言

在分布式系統(tǒng)中最好耗性能的地方就是最后端的數(shù)據(jù)庫(kù),一般情況下數(shù)據(jù)庫(kù)上的insert操作很快,而update和delete操作如果帶有索引也不會(huì)慢,前提要控制好單表的數(shù)據(jù)量,并且不要建太多索引,
而最容易出現(xiàn)性能問題的往往是select語(yǔ)句,我們拋開join和group不說,大多數(shù)應(yīng)用都是讀多寫少而且,而且?guī)в信判蚝蚻imit等耗時(shí)操作,有些查詢還需要根據(jù)非索引字段進(jìn)行過濾,以及l(fā)ike操作會(huì)加劇慢查詢,
在微服務(wù)中這些查詢接口往往以rpc的形式對(duì)外提供服務(wù),因?yàn)榫W(wǎng)絡(luò)開銷導(dǎo)致整體響應(yīng)時(shí)間增加,所以在某些性能要求較高的業(yè)務(wù)中引入緩存是非常必要的,下面我們將引入緩存的具體位置進(jìn)行分類介紹.

image

客戶端緩存

移動(dòng)客戶端可以將一些靜態(tài)資源緩存在設(shè)備上,避免重復(fù)從應(yīng)用層獲取,在網(wǎng)絡(luò)不通暢的情況下也可以避免沒有數(shù)據(jù)前端UI錯(cuò)亂,
而PC端瀏覽器一般可以通過nginx設(shè)置cache-control,expires,if-modified-since來(lái)控制緩存,避免重復(fù)請(qǐng)求服務(wù)器,也可以通過
cookie將一部分?jǐn)?shù)據(jù)存在用戶瀏覽器中,下次請(qǐng)求可以將cookie發(fā)送給服務(wù)端,一般用cookie存儲(chǔ)用戶登錄信息

CDN緩存

一些靜態(tài)資源,尤其是圖片,我們可以在高并發(fā)的情況下,讓用戶優(yōu)先訪問離用戶最近并且同一個(gè)網(wǎng)絡(luò)供應(yīng)商的CDN節(jié)點(diǎn),避免跨運(yùn)營(yíng)商垮地域訪問,
相比于集中式的機(jī)房?jī)?nèi)的服務(wù)器,CDN廠商的覆蓋范圍更廣,在每個(gè)運(yùn)營(yíng)商,每個(gè)地區(qū)都有自己的POP點(diǎn),所以總可以找到更加靠近用戶網(wǎng)絡(luò)的CDN節(jié)點(diǎn)就近獲取靜態(tài)資源,CDN節(jié)點(diǎn)一般用來(lái)存儲(chǔ)
不頻繁變更的靜態(tài)圖片,頁(yè)面等資源,一般發(fā)布新版本,或者上新一個(gè)新活動(dòng)都可以提前將這些靜態(tài)資源提前推送到CDN節(jié)點(diǎn)進(jìn)行預(yù)熱,使用CDN一般通過CNAME的方式將域名解析交給CDN廠商的DNS服務(wù)器和全局負(fù)載均衡器

image

反向代理層緩存

反向代理層一般需要做動(dòng)靜分離,將靜態(tài)資源存在在ngnix本地,靜態(tài)資源一般數(shù)據(jù)庫(kù)大請(qǐng)求頻繁,做動(dòng)靜分離可以使應(yīng)用層可以有更多資源處理動(dòng)態(tài)請(qǐng)求,而靜態(tài)資源不用直接請(qǐng)求應(yīng)用層,可以極大提高系統(tǒng)吞吐量
在做了動(dòng)靜分離后,瀏覽器可以直接通過ajax請(qǐng)求服務(wù)端獲取動(dòng)態(tài)數(shù)據(jù),瀏覽器將數(shù)據(jù)進(jìn)行整合后顯示給用戶.

分布式緩存

image

image

目前分布式緩存一般單獨(dú)部署在應(yīng)用層進(jìn)行讀寫控制,讀取的時(shí)候先去查詢緩存服務(wù)器,沒有命中在去查詢數(shù)據(jù)庫(kù)并寫入緩存,更新的時(shí)候先更新數(shù)據(jù)庫(kù),然后在將緩存失效,
使用分布式緩存來(lái)替代應(yīng)用層在JVM內(nèi)緩存,可以避免各個(gè)JVM內(nèi)緩存不一致的情況,也讓緩存可以集群化部署更容易水平擴(kuò)展,

目前分布式緩存主要由memecache和redis,memecache主要提供key-value存儲(chǔ),內(nèi)存使用率較高,對(duì)大數(shù)據(jù)性能較好,但是集群支持不友好.
而redis提供多種數(shù)據(jù)結(jié)構(gòu),string,set,list,zset,hash等,還提供了RDB全量持久化,和AOF增量持久化,將內(nèi)存中得數(shù)據(jù)化存儲(chǔ)在硬盤上,重啟可以再次加載使用,不過開啟持久化后會(huì)影響redis的內(nèi)存使用率,尤其是開啟AOF同步后還會(huì)影響redis的寫性能,redis還提供了集群化master-slave數(shù)據(jù)備份以及多master進(jìn)行分片來(lái)提高吞吐量.
不過memecache采用多線程模型,分為主線程和worker線程,而redis是單線程IO復(fù)用模型,對(duì)于IO操作可以將性能發(fā)揮的最大化,但是redis也提供了排序,聚合等操作,這些操作在單線程下會(huì)影響吞吐量.
memechache集群只能通過客戶端在讀寫的時(shí)候根據(jù)統(tǒng)一的分片算法選擇對(duì)應(yīng)的機(jī)器,不支持master-slave數(shù)據(jù)備份

image

redis提供分片功能,將整個(gè)集群的16384個(gè)slot根據(jù)服務(wù)器的性能和讀寫頻率分道不同的master節(jié)點(diǎn)上,每個(gè)master可以下掛若干個(gè)slave節(jié)點(diǎn),slave從master異步同步數(shù)據(jù),當(dāng)master掛了之后,slave可以通過選舉生成新德master,
master可以提供讀寫服務(wù),而slave只提供讀服務(wù),而redis集群對(duì)外提供服務(wù)也可以單獨(dú)加一層proxy也可以直接連接客戶端,兩種方式各有利弊,可根據(jù)實(shí)際場(chǎng)景進(jìn)行選擇

像redis和memecache這種提供內(nèi)存服務(wù)的應(yīng)用,內(nèi)存管理的效率直接影響系統(tǒng)的性能,在C語(yǔ)言中我們使用malloc和free分配和釋放內(nèi)存,對(duì)于開發(fā)人員如果malloc和free不匹配容易造成內(nèi)存泄露,頻繁使用也會(huì)造成大量的內(nèi)存碎片,而且頻繁進(jìn)行這種系統(tǒng)調(diào)用也會(huì)影響性能.

memecache會(huì)預(yù)先申請(qǐng)一塊內(nèi)存,然后將這塊內(nèi)存切分為若干個(gè)chunk,chunk的大小可以根據(jù)一個(gè)增長(zhǎng)因子控制,比如增長(zhǎng)因子為1.25,第一組chunk的大小為88字節(jié),則第二組的大小為88*1.25=114字節(jié),讓后將相同大小的chunk歸類為一個(gè)slab,當(dāng)客戶端有一個(gè)寫請(qǐng)求后,會(huì)根據(jù)寫入數(shù)據(jù)的大小選擇對(duì)應(yīng)的chunk,如果這個(gè)值占用空間小于chunk大小就會(huì)造成一定空間的浪費(fèi),刪除緩存的時(shí)候會(huì)標(biāo)記這個(gè)chunk未使用.

而redis是現(xiàn)場(chǎng)申請(qǐng)內(nèi)存的方式進(jìn)行存儲(chǔ)數(shù)據(jù),也很少對(duì)內(nèi)存進(jìn)行優(yōu)化,所以redis一定程度上會(huì)產(chǎn)生內(nèi)存碎片,并且當(dāng)redis發(fā)生swap的時(shí)候也不會(huì)觸發(fā)內(nèi)存整理.

當(dāng)然redis并不是所有數(shù)據(jù)都存儲(chǔ)在內(nèi)存中,當(dāng)物理內(nèi)存用完時(shí)或者達(dá)到某一個(gè)閾值,redis可以將一些很久沒有用到的value存儲(chǔ)到磁盤,只將key存在內(nèi)存中,也就是所謂的swap操作,需要計(jì)算哪些key需要交換到磁盤,不過這種情況下當(dāng)客戶端發(fā)起一個(gè)讀請(qǐng)求,value不在內(nèi)存中得時(shí)候需要從硬盤讀取,默認(rèn)情況下redis會(huì)阻塞.

目前經(jīng)過benchmark測(cè)試,redis性能要優(yōu)于memecache,原因可能是memecache用了libevent庫(kù),而該庫(kù)為了迎合通用做了大量的代碼冗余,而redis使用libevent里面的兩個(gè)文件修改實(shí)現(xiàn)了epoll event loop,另一方面redis是單線程的,不用考慮精裝修改資源的情況,而memecache采用CAS的方式,CAS的實(shí)現(xiàn)需要為每一個(gè)cache key設(shè)置一個(gè)隱藏的token,
這個(gè)token會(huì)作為版本號(hào),在set的時(shí)候會(huì)遞增,帶來(lái)CPU和內(nèi)存的雙重開銷,盡管開銷很小,在QPS很高的情況下會(huì)帶來(lái)性能上的細(xì)微差別.

JVM本地緩存

本地緩存,這類緩存一般存儲(chǔ)在JVM堆空間內(nèi),由于容量受限制,也會(huì)影響到FullGC,當(dāng)然也可以考慮使用堆外內(nèi)存或者用jemalloc管理內(nèi)存,
所以我們只是通過本地緩存來(lái)緩存一些并發(fā)訪問量特別高并且查詢數(shù)據(jù)庫(kù)很耗時(shí)的數(shù)據(jù),而且這類數(shù)據(jù)可能不一定和數(shù)據(jù)庫(kù)完全保持一致,所以業(yè)務(wù)不會(huì)使用改變量做一些金額和狀態(tài)相關(guān)的核心操作.
這類緩存的典型代表為guava和ehcache,也有一些緩存放在ORM框架中,去緩存數(shù)據(jù)中的查詢操作.

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

數(shù)據(jù)庫(kù)本身也會(huì)有緩存功能,目前建議只針對(duì)一些讀多寫少特別頻繁的業(yè)務(wù)表開啟,大多數(shù)情況都建議關(guān)閉,因?yàn)閙ysql的緩存中當(dāng)有任何一條記錄的update操作就會(huì)將緩存失效,如果頻繁update就會(huì)導(dǎo)致數(shù)據(jù)庫(kù)頻繁緩存和清除

使用說明

  • 容量評(píng)估

在使用緩存前,最好做下容量評(píng)估,緩存系統(tǒng)主要消耗的是服務(wù)器內(nèi)存,因此使用緩存時(shí)候必須對(duì)應(yīng)用需要緩存的數(shù)據(jù)大小進(jìn)行評(píng)估,包括緩存的數(shù)據(jù)結(jié)構(gòu),過期時(shí)間,緩存大小,緩存數(shù)量,然后根據(jù)未來(lái)一定時(shí)間內(nèi)的業(yè)務(wù)增長(zhǎng)情況進(jìn)行預(yù)估.

  • 業(yè)務(wù)分離

建議將使用的緩存進(jìn)行分離,核心業(yè)務(wù)和非核心業(yè)務(wù)可以使用不同的緩存實(shí)例,最好能做到業(yè)務(wù)之間相互隔離,避免不同業(yè)務(wù)線共用一套緩存導(dǎo)致沖突.

  • 監(jiān)控

所有的緩存實(shí)例都需要有監(jiān)控,內(nèi)存使用情況,慢查詢,大對(duì)象,任何緩存key都設(shè)置過期時(shí)間,過期時(shí)間最好在原有設(shè)置上加減一個(gè)隨機(jī)值,避免一起失效導(dǎo)致雪崩。

  • 先更新數(shù)據(jù)庫(kù),后失效緩存

以下為先更新數(shù)據(jù)庫(kù)后清緩存的兩種情況,一種最后緩存清空后下一次讀請(qǐng)求就會(huì)恢復(fù),另外一種發(fā)生的概率很小


image

以下為先清緩存后更新數(shù)據(jù)庫(kù),會(huì)導(dǎo)致緩存中得數(shù)據(jù)一直是臟數(shù)據(jù)


image

其次,我們要考慮下如果數(shù)據(jù)庫(kù)是主從部署,從庫(kù)支持讀取,那么數(shù)據(jù)寫入主庫(kù)后,而應(yīng)用讀取從庫(kù)還未更新的數(shù)據(jù)并寫入緩存導(dǎo)致緩存里的數(shù)據(jù)一直是臟數(shù)據(jù),這種情況我們可以提供一種供參考的方案:通過canel訂閱mysql的binlog的方式去修改緩存可以避免該問題。

  • 應(yīng)用不要過渡依賴緩存

我們一般不會(huì)要求緩存服務(wù)器的更新和數(shù)據(jù)庫(kù)的更新在同一個(gè)事物內(nèi),所以肯定有概率緩存和數(shù)據(jù)庫(kù)不一致的情況,所以
數(shù)據(jù)的最終一致性最好不要依賴緩存,可以在應(yīng)用層和或者數(shù)據(jù)庫(kù)CAS的方式增加校驗(yàn),另外應(yīng)用也不應(yīng)該嚴(yán)重依賴緩存,當(dāng)緩存服務(wù)器掛掉之后至少要保證服務(wù)能夠在沒有高并發(fā)情況下繼續(xù)正常對(duì)外提供服務(wù),
當(dāng)然也不要過渡依賴緩存服務(wù)器的持久化功能,畢竟并不能完整復(fù)原歷史數(shù)據(jù).

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

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

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