MySQL數(shù)據(jù)庫成為瓶頸后,動態(tài)數(shù)據(jù)的查詢要如何加速?

一般電商系統(tǒng)在完成DB主從分離和分庫分表后,可支撐十幾萬DAU。

DB分了主庫和、從庫,數(shù)據(jù)也被切分到多個DB節(jié)點(diǎn)。但隨并發(fā)增加,存儲數(shù)據(jù)量增多,DB磁盤IO逐漸成系統(tǒng)瓶頸,需要一種訪問更快的組件來降低請求響應(yīng)時間,提升整體系統(tǒng)性能。

這時,就該緩存上場了!

什么是緩存

緩存,一種存儲數(shù)據(jù)的組件,讓對數(shù)據(jù)的請求更快返回。

經(jīng)常會把緩存放在內(nèi)存, 所以有人就以為內(nèi)存=緩存,這是外行見解。

某些場景下可能還會使用SSD作為冷數(shù)據(jù)的緩存。比如說360開源的Pika就是使用SSD存儲數(shù)據(jù)解決Redis的容量瓶頸的。

凡是位于速度相差較大的兩種硬件之間,用于協(xié)調(diào)兩者數(shù)據(jù)傳輸速度差異的結(jié)構(gòu),均可稱之為緩存。

常見硬件組件的延時情況是什么樣的了,這樣在做方案的時候可以對延遲有更直觀的印象。

做一次內(nèi)存尋址大概需要100ns,而做一次磁盤的查找則需要10ms。

使用內(nèi)存作為緩存的存儲介質(zhì)相比于以磁盤作為主要存儲介質(zhì)的數(shù)據(jù)庫來說,性能上會提高多個數(shù)量級,同時也能夠支撐更高的并發(fā)量。所以,內(nèi)存是最常見的一種緩存數(shù)據(jù)的介質(zhì)。

緩存作為一種常見的空間換時間的性能優(yōu)化手段,在很多地方都有應(yīng)用。

緩存的適用場景

Linux內(nèi)存管理通過MMU(Memory Management Unit)硬件,實(shí)現(xiàn)從虛擬地址到物理地址的轉(zhuǎn)換,但如果每次轉(zhuǎn)換都要做這么復(fù)雜計(jì)算,無疑會造成性能損耗,所以借助TLB(Translation Lookaside Buffer)組件緩存最近轉(zhuǎn)換過的虛擬地址,和物理地址的映射。這就是一種緩存組件,緩存復(fù)雜運(yùn)算的結(jié)果

短視頻實(shí)際上是使用內(nèi)置的網(wǎng)絡(luò)播放器來完成的。網(wǎng)絡(luò)播放器接收的是數(shù)據(jù)流,將數(shù)據(jù)下載下來之后經(jīng)過分離音視頻流,解碼等流程后輸出到外設(shè)設(shè)備上播放。

如果我們在打開一個視頻的時候才開始下載數(shù)據(jù)的話,無疑會增加視頻的打開速度(我們叫首播時間),并且播放過程中會有卡頓。所以我們的播放器中通常會設(shè)計(jì)一些緩存的組件,在未打開視頻時緩存一部分視頻數(shù)據(jù),比如我們打開抖音,服務(wù)端可能一次會返回三個視頻信息,我們在播放第一個視頻的時候,播放器已經(jīng)幫我們緩存了第二、三個視頻的部分?jǐn)?shù)據(jù),這樣在看第二個視頻的時候就可以給用戶“秒開”的感覺。

HTTP協(xié)議也是有緩存機(jī)制的。當(dāng)我們第一次請求靜態(tài)的資源時,比如一張圖片,服務(wù)端除了返回圖片信息,在響應(yīng)頭里面還有一個“Etag”的字段。瀏覽器會緩存圖片信息以及這個字段的值。當(dāng)下一次再請求這個圖片的時候,瀏覽器發(fā)起的請求頭里面會有一個“If-None-Match”的字段,并且把緩存的“Etag”的值寫進(jìn)去發(fā)給服務(wù)端。服務(wù)端比對圖片信息是否有變化,如果沒有,則返回瀏覽器一個304的狀態(tài)碼,瀏覽器會繼續(xù)使用緩存的圖片信息。通過這種緩存協(xié)商的方式,可以減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)大小,從而提升頁面展示的性能。

緩存與緩沖區(qū)

緩沖和緩存只有一字之差,它們有什么區(qū)別呢?

緩存:

  • 可提高低速設(shè)備的訪問速度
  • 減少復(fù)雜耗時的計(jì)算帶來的性能問題

理論上可通過緩存解決所有“慢”問題,比如從磁盤隨機(jī)讀取數(shù)據(jù)慢,從數(shù)據(jù)庫查詢數(shù)據(jù)慢,只是不同場景消耗的存儲成本不同。

緩沖區(qū)則是一塊臨時存儲數(shù)據(jù)的區(qū)域,這些數(shù)據(jù)后面會被傳輸?shù)狡渌O(shè)備上。緩沖區(qū)更像MQ,用以彌補(bǔ)高速設(shè)備和低速設(shè)備通信時的速度差。比如,我們將數(shù)據(jù)寫入磁盤時并不是直接刷盤,而是寫到一塊緩沖區(qū)里面,內(nèi)核會標(biāo)識這個緩沖區(qū)為臟。當(dāng)經(jīng)過一定時間或者臟緩沖區(qū)比例到達(dá)一定閾值時,由單獨(dú)的線程把臟塊刷新到硬盤上。這樣避免了每次寫數(shù)據(jù)都要刷盤帶來的性能問題。

所以TLB的命名是有問題的,它應(yīng)該是緩存而不是緩沖區(qū)。

緩存分類

靜態(tài)緩存

在Web 1.0時期是非常著名的,它一般通過生成Velocity模板或者靜態(tài)HTML文件來實(shí)現(xiàn)靜態(tài)緩存,在Nginx上部署靜態(tài)緩存可以減少對于后臺應(yīng)用服務(wù)器的壓力。例如,我們在做一些內(nèi)容管理系統(tǒng)的時候,后臺會錄入很多的文章,前臺在網(wǎng)站上展示文章內(nèi)容,就像新浪,網(wǎng)易這種門戶網(wǎng)站一樣。

當(dāng)然,我們也可以把文章錄入到數(shù)據(jù)庫里面,然后前端展示的時候穿透查詢數(shù)據(jù)庫來獲取數(shù)據(jù),但是這樣會對數(shù)據(jù)庫造成很大的壓力。即使我們使用分布式緩存來擋讀請求,但是對于像日均PV幾十億的大型門戶網(wǎng)站來說,基于成本考慮仍然是不劃算的。

所以我們的解決思路是每篇文章在錄入的時候渲染成靜態(tài)頁面,放置在所有的前端Nginx或者Squid等Web服務(wù)器上,這樣用戶在訪問的時候會優(yōu)先訪問Web服務(wù)器上的靜態(tài)頁面,在對舊的文章執(zhí)行一定的清理策略后,依然可以保證99%以上的緩存命中率。

這種緩存只能針對靜態(tài)數(shù)據(jù)來緩存,對于動態(tài)請求就無能為力了。那么我們?nèi)绾吾槍討B(tài)請求做緩存呢?這時你就需要分布式緩存了。

分布式緩存

Memcached、Redis就是分布式緩存的典型例子。它們性能強(qiáng)勁,通過一些分布式的方案組成集群可以突破單機(jī)的限制。所以在整體架構(gòu)中,分布式緩存承擔(dān)著非常重要的角色(接下來的課程我會專門針對分布式緩存,帶你了解分布式緩存的使用技巧以及高可用的方案,讓你能在工作中對分布式緩存運(yùn)用自如)。

對于靜態(tài)的資源的緩存你可以選擇靜態(tài)緩存,對于動態(tài)的請求你可以選擇分布式緩存,那么什么時候要考慮熱點(diǎn)本地緩存呢?

答案是當(dāng)我們遇到極端的熱點(diǎn)數(shù)據(jù)查詢的時候。熱點(diǎn)本地緩存主要部署在應(yīng)用服務(wù)器的代碼中,用于阻擋熱點(diǎn)查詢對于分布式緩存節(jié)點(diǎn)或者數(shù)據(jù)庫的壓力。

比如某一位明星在微博上有了熱點(diǎn)話題,“吃瓜群眾”會到他(她)的微博首頁圍觀,這就會引發(fā)這個用戶信息的熱點(diǎn)查詢。這些查詢通常會命中某一個緩存節(jié)點(diǎn)或者某一個數(shù)據(jù)庫分區(qū),短時間內(nèi)會形成極高的熱點(diǎn)查詢。

本地緩存

如HashMap,Guava Cache或者是Ehcache等,它們和應(yīng)用程序部署在同一個進(jìn)程中,優(yōu)勢是不需要跨網(wǎng)絡(luò)調(diào)度,速度極快,所以可以來阻擋短時間內(nèi)的熱點(diǎn)查詢。來看個例子。

比方說你的垂直電商系統(tǒng)的首頁有一些推薦的商品,這些商品信息是由編輯在后臺錄入和變更。你分析編輯錄入新的商品或者變更某個商品的信息后,在頁面的展示是允許有一些延遲的,比如說30秒的延遲,并且首頁請求量最大,即使使用分布式緩存也很難抗住,所以你決定使用Guava Cache來將所有的推薦商品的信息緩存起來,并且設(shè)置每隔30秒重新從數(shù)據(jù)庫中加載最新的所有商品。

首先,我們初始化Guava 的Loading Cache:

CacheBuilder<String, List<Product>> cacheBuilder = CacheBuilder.newBuilder().maximumSize(maxSize).recordStats(); 
//設(shè)置緩存最大值
cacheBuilder = cacheBuilder.refreshAfterWrite(30, TimeUnit.Seconds); 
//設(shè)置刷新間隔

LoadingCache<String, List<Product>> cache = cacheBuilder.build(new CacheLoader<String, List<Product>>() {  
  @Override   
 public List<Product> load(String k) throws Exception {  
      return productService.loadAll(); 
      // 獲取所有商品    
          }
      }
  );

獲取所有商品信息時,可調(diào)用Loading Cache的get,優(yōu)先從本地緩存獲取商品信息,如果不存在,會使用CacheLoader中的邏輯從DB加載所有商品。

由于本地緩存部署在應(yīng)用服務(wù)器,通常集群部署,當(dāng)數(shù)據(jù)更新時,不能確定哪臺服務(wù)器本地中了緩存,更新或刪除所有服務(wù)器的緩存不是一個好的選擇,所以通常會等待緩存過期。因此,這種緩存的有效期很短,通常為分鐘或秒級別,以避免返回前端臟數(shù)據(jù)。

緩存的不足

緩存主要是提升訪問速度,從而抗更高并發(fā)。那不意味著它就是銀彈。

  • 緩存適于讀多寫少,并且數(shù)據(jù)最好帶有一定熱點(diǎn)屬性。因?yàn)榫彺媸芟薮鎯橘|(zhì),不可能緩存所有數(shù)據(jù),當(dāng)數(shù)據(jù)有熱點(diǎn)屬性時,才能保證緩存命中率。比如朋友圈這種20%內(nèi)容會占80%流量。所以,一旦當(dāng)業(yè)務(wù)場景讀少寫多時或無明顯熱點(diǎn),比如在搜索場景,每個人搜索的詞都不同,無明顯熱點(diǎn),此時緩存作用不大。
  • 緩存會給整體系統(tǒng)帶來復(fù)雜度,且有數(shù)據(jù)不一致風(fēng)險。當(dāng)更新數(shù)據(jù)庫成功,更新緩存失敗的場景下,緩存中就會存在臟數(shù)據(jù)??煽紤]使用較短過期時間或者手動清理。
  • 緩存通常使用內(nèi)存作為存儲介質(zhì),但內(nèi)存并不是無限。因此,使用緩存時要做數(shù)據(jù)存儲量級評估,需消耗極大存儲成本的數(shù)據(jù),慎用緩存。緩存一定要設(shè)置過期時間,可保證緩存中的會是熱點(diǎn)數(shù)據(jù)。
  • 緩存會給運(yùn)維也帶來一定的成本,運(yùn)維需要對緩存組件有一定的了解,排查問題時,也多了個組件考量。

但緩存對性能提升的意義毋庸置疑,架構(gòu)設(shè)計(jì)時一定要把它考慮在內(nèi),只是具體方案需要對緩存設(shè)計(jì)有更細(xì)致思考,最大化發(fā)揮緩存優(yōu)勢。

小結(jié)

緩存可以有多層,比如

靜態(tài)緩存處在負(fù)載均衡層

分布式緩存處在應(yīng)用層和數(shù)據(jù)庫層之間

本地緩存處在應(yīng)用層。

需要將請求盡量擋在上層,因?yàn)樵酵聦?,對于并發(fā)的承受能力越差;

緩存命中率是緩存最重要的監(jiān)控項(xiàng)。

緩存不僅僅是一種組件的名字,更是一種設(shè)計(jì)思想,任何能夠加速讀請求的組件和設(shè)計(jì)方案都是緩存思想的體現(xiàn)。

而這種加速通常是通過兩種方式來實(shí)現(xiàn):

使用更快的介質(zhì),如內(nèi)存;

緩存復(fù)雜運(yùn)算的結(jié)果,如TLB。

當(dāng)你在實(shí)際工作中碰到“慢”問題,緩存就是你的第一考量。

?著作權(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)容