輕鬆構建微服務之高效緩存

微信公衆號:內核小王子 關注可瞭解更多關於數據庫,JVM內核相關的知識; 若是你有任何疑問也能夠加我pigpdong[^1]前端

前言

在分佈式系統中最好耗性能的地方就是最後端的數據庫,通常狀況下數據庫上的insert操做很快,而update和delete操做若是帶有索引也不會慢,前提要控制好單表的數據量,而且不要建太多索引, 而最容易出現性能問題的每每是select語句,咱們拋開join和group不說,大多數應用都是讀多寫少並且,並且帶有排序和limit等耗時操做,有些查詢還須要根據非索引字段進行過濾,以及like操做會加重慢查詢, 在微服務中這些查詢接口每每以rpc的形式對外提供服務,由於網絡開銷致使總體響應時間增長,因此在某些性能要求較高的業務中引入緩存是很是必要的,下面咱們將引入緩存的具體位置進行分類介紹.mysql

image

客戶端緩存

移動客戶端能夠將一些靜態資源緩存在設備上,避免重複從應用層獲取,在網絡不通暢的狀況下也能夠避免沒有數據前端UI錯亂, 而PC端瀏覽器通常能夠經過nginx設置cache-control,expires,if-modified-since來控制緩存,避免重複請求服務器,也能夠經過 cookie將一部分數據存在用戶瀏覽器中,下次請求能夠將cookie發送給服務端,通常用cookie存儲用戶登陸信息nginx

CDN緩存

一些靜態資源,尤爲是圖片,咱們能夠在高併發的狀況下,讓用戶優先訪問離用戶最近而且同一個網絡供應商的CDN節點,避免跨運營商垮地域訪問, 相比於集中式的機房內的服務器,CDN廠商的覆蓋範圍更廣,在每一個運營商,每一個地區都有本身的POP點,因此總能夠找到更加靠近用戶網絡的CDN節點就近獲取靜態資源,CDN節點通常用來存儲 不頻繁變動的靜態圖片,頁面等資源,通常發佈新版本,或者上新一個新活動均可以提早將這些靜態資源提早推送到CDN節點進行預熱,使用CDN通常經過CNAME的方式將域名解析交給CDN廠商的DNS服務器和全局負載均衡器ajax

image

反向代理層緩存

反向代理層通常須要作動靜分離,將靜態資源存在在ngnix本地,靜態資源通常數據庫大請求頻繁,作動靜分離可使應用層能夠有更多資源處理動態請求,而靜態資源不用直接請求應用層,能夠極大提升系統吞吐量 在作了動靜分離後,瀏覽器能夠直接經過ajax請求服務端獲取動態數據,瀏覽器將數據進行整合後顯示給用戶.redis

分佈式緩存

image
image

目前分佈式緩存通常單獨部署在應用層進行讀寫控制,讀取的時候先去查詢緩存服務器,沒有命中在去查詢數據庫並寫入緩存,更新的時候先更新數據庫,而後在將緩存失效, 使用分佈式緩存來替代應用層在JVM內緩存,能夠避免各個JVM內緩存不一致的狀況,也讓緩存能夠集羣化部署更容易水平擴展,算法

目前分佈式緩存主要由memecache和redis,memecache主要提供key-value存儲,內存使用率較高,對大數據性能較好,可是集羣支持不友好. 而redis提供多種數據結構,string,set,list,zset,hash等,還提供了RDB全量持久化,和AOF增量持久化,將內存中得數據化存儲在硬盤上,重啓能夠再次加載使用,不過開啓持久化後會影響redis的內存使用率,尤爲是開啓AOF同步後還會影響redis的寫性能,redis還提供了集羣化master-slave數據備份以及多master進行分片來提升吞吐量. 不過memecache採用多線程模型,分爲主線程和worker線程,而redis是單線程IO複用模型,對於IO操做能夠將性能發揮的最大化,可是redis也提供了排序,聚合等操做,這些操做在單線程下會影響吞吐量. memechache集羣只能經過客戶端在讀寫的時候根據統一的分片算法選擇對應的機器,不支持master-slave數據備份sql

image

redis提供分片功能,將整個集羣的16384個slot根據服務器的性能和讀寫頻率分道不一樣的master節點上,每一個master能夠下掛若干個slave節點,slave從master異步同步數據,當master掛了以後,slave能夠經過選舉生成新德master, master能夠提供讀寫服務,而slave只提供讀服務,而redis集羣對外提供服務也能夠單獨加一層proxy也能夠直接鏈接客戶端,兩種方式各有利弊,可根據實際場景進行選擇數據庫

像redis和memecache這種提供內存服務的應用,內存管理的效率直接影響系統的性能,在C語言中咱們使用malloc和free分配和釋放內存,對於開發人員若是malloc和free不匹配容易形成內存泄露,頻繁使用也會形成大量的內存碎片,並且頻繁進行這種系統調用也會影響性能.後端

memecache會預先申請一塊內存,而後將這塊內存切分爲若干個chunk,chunk的大小能夠根據一個增加因子控制,好比增加因子爲1.25,第一組chunk的大小爲88字節,則第二組的大小爲88*1.25=114字節,讓後將相同大小的chunk歸類爲一個slab,當客戶端有一個寫請求後,會根據寫入數據的大小選擇對應的chunk,若是這個值佔用空間小於chunk大小就會形成必定空間的浪費,刪除緩存的時候會標記這個chunk未使用.瀏覽器

而redis是現場申請內存的方式進行存儲數據,也不多對內存進行優化,因此redis必定程度上會產生內存碎片,而且當redis發生swap的時候也不會觸發內存整理.

固然redis並非全部數據都存儲在內存中,當物理內存用完時或者達到某一個閾值,redis能夠將一些好久沒有用到的value存儲到磁盤,只將key存在內存中,也就是所謂的swap操做,須要計算哪些key須要交換到磁盤,不過這種狀況下當客戶端發起一個讀請求,value不在內存中得時候須要從硬盤讀取,默認狀況下redis會阻塞.

目前通過benchmark測試,redis性能要優於memecache,緣由多是memecache用了libevent庫,而該庫爲了迎合通用作了大量的代碼冗餘,而redis使用libevent裏面的兩個文件修改實現了epoll event loop,另外一方面redis是單線程的,不用考慮精裝修改資源的狀況,而memecache採用CAS的方式,CAS的實現須要爲每個cache key設置一個隱藏的token, 這個token會做爲版本號,在set的時候會遞增,帶來CPU和內存的雙重開銷,儘管開銷很小,在QPS很高的狀況下會帶來性能上的細微差異.

JVM本地緩存

本地緩存,這類緩存通常存儲在JVM堆空間內,因爲容量受限制,也會影響到FullGC,固然也能夠考慮使用堆外內存或者用jemalloc管理內存, 因此咱們只是經過本地緩存來緩存一些併發訪問量特別高而且查詢數據庫很耗時的數據,並且這類數據可能不必定和數據庫徹底保持一致,因此業務不會使用改變量作一些金額和狀態相關的核心操做. 這類緩存的典型表明爲guava和ehcache,也有一些緩存放在ORM框架中,去緩存數據中的查詢操做.

數據庫緩存

數據庫自己也會有緩存功能,目前建議只針對一些讀多寫少特別頻繁的業務表開啓,大多數狀況都建議關閉,由於mysql的緩存中當有任何一條記錄的update操做就會將緩存失效,若是頻繁update就會致使數據庫頻繁緩存和清除

使用說明

  • 容量評估

在使用緩存前,最好作下容量評估,緩存系統主要消耗的是服務器內存,所以使用緩存時候必須對應用須要緩存的數據大小進行評估,包括緩存的數據結構,過時時間,緩存大小,緩存數量,而後根據將來必定時間內的業務增加狀況進行預估.

  • 業務分離

建議將使用的緩存進行分離,核心業務和非核心業務可使用不一樣的緩存實例,最好能作到業務之間相互隔離,避免不一樣業務線共用一套緩存致使衝突.

  • 監控

全部的緩存實例都須要有監控,內存使用狀況,慢查詢,大對象,任何緩存key都設置過時時間,過時時間最好在原有設置上加減一個隨機值,避免一塊兒失效致使雪崩。

  • 先更新數據庫,後失效緩存

如下爲先更新數據庫後清緩存的兩種狀況,一種最後緩存清空後下一次讀請求就會恢復,另一種發生的機率很小

image

如下爲先清緩存後更新數據庫,會致使緩存中得數據一直是髒數據

image

其次,咱們要考慮下若是數據庫是主從部署,從庫支持讀取,那麼數據寫入主庫後,而應用讀取從庫還未更新的數據並寫入緩存致使緩存裏的數據一直是髒數據,這種狀況咱們能夠提供一種供參考的方案:經過canel訂閱mysql的binlog的方式去修改緩存能夠避免該問題。

  • 應用不要過渡依賴緩存

咱們通常不會要求緩存服務器的更新和數據庫的更新在同一個事物內,因此確定有機率緩存和數據庫不一致的狀況,因此 數據的最終一致性最好不要依賴緩存,能夠在應用層和或者數據庫CAS的方式增長校驗,另外應用也不該該嚴重依賴緩存,當緩存服務器掛掉以後至少要保證服務可以在沒有高併發狀況下繼續正常對外提供服務, 固然也不要過渡依賴緩存服務器的持久化功能,畢竟並不能完整復原歷史數據.

相關文章
相關標籤/搜索