天天看點

緩存中間件-緩存架構的實作(上)

緩存中間件-緩存架構的實作(上)

前言

一眨眼,2019年就過去了。我希望從按照中間件,分别闡述一些常見的架構問題,以及解決方案。一方面這些問題與解決方案具備一定通用性 。另一方面,也算是面試中常見的問題。

我希望根據自己待過各種規模公司的經驗來談一些看法。

  • 如果是針對大部分小公司的工作或面試,這些問題都稍微留下個印象即可。因為小公司的技術對這些問題并不是很看重,或者說機會用不到(小型公司往往追求産品功能的實作,業務的推進等)。
  • 如果是針對大部分中型公司的工作或面試,希望可以完整地知道這些問題與解決方案。因為在中型公司中,這些問題都或多或少遇到,甚至是需要迫切解決的。
  • 如果是大型公司的話,那麼不僅僅需要知道這些問題與解決方案。還需要從中了解為什麼會有這樣的問題,為什麼這樣解決,在現有的項目中應該如何應用,是否提升空間等。因為在大公司中,一方面其内部往往采用自研架構,其它架構能夠借鑒的隻有方案,思想等精髓;另一方面大公司不缺乏那些應用開源架構的人,缺的是把握方案通用思想的人。

如果上述無法了解的話,大家可以從功能性追求與非功能性追求兩個方面去思考。就像寫一個簡單的方法一樣,最基本的要求是實作其功能,緊接着就是不斷追求其非功能性(如性能,擴充性,安全性等)。放大來看,對于公司的技術發展也是如此,或者說更為嚴格。

之後找個機會,專門寫個部落格,來談談我對公司技術與公司的看法。

話題收回來,接下來,讓我開始有關中間件問題與解決方案的闡述吧。

概述

緩存的認識

既然提及緩存中間件相關的問題及方案,首先就要談談這個緩存。

原本我想通過高速緩存舉例,但是想了想還是用記憶體舉例子吧。

比如我們現在玩的單機遊戲,往往都容量都非常大(幾十G,乃至上百G),輕輕松松都超過了電腦記憶體(16G)。那麼很明顯電腦在運作遊戲時,是不可能将整個遊戲檔案都放入記憶體的。但是如果檔案都在硬碟裡,需要的時候再讀取,顯然硬碟的讀寫速度時不夠的(由于遊戲檔案類别很多,是以硬碟不可能一直順序讀寫),那遊戲也會經常卡頓,加載緩慢等。那麼該如何解決這個問題呢?

其實這個問題和我們業務中遇到的一些問題是很類似的。一方面我們希望使用者可以在保證使用者體驗的前提下查詢資料(如裝置清單,訂單清單等),另一方面我們不可能将所有資料都放在記憶體(記憶體的讀寫速度比硬碟快,是以就不解釋為什麼用硬碟了)中。那麼到底該怎麼解決這個問題呢?

這裡就需要說到局部性原理了。局部性原理指的是資料的通路往往趨向于聚集在較小的連續區域。這裡的連續區域包含兩個方面:

  • 時間次元:一個被使用的資料,在接下來較短的時間内,往往會被再次使用。
  • 空間次元:一個被使用的資料,其關聯的資料,往往也會被使用。

局部性原理是在記憶體,高速緩存部分,提出來用于解決問題的。

其實,我與朋友交流分布式的一些想法時,經常說:分布式系統和單機内部是非常相似的,很多理念都是相通的。當想通了這點後,就可以去思考兩者的差別的。

緩存中間件其實就是利用了局部性原理,不過緩存中間件本身隻實作了局部性原理的時間次元。這也是為什麼很多人都說緩存中間件是用來儲存熱點資料,符合二八定律。不過我們可以在應用部分實作局部性原理的空間次元。

緩存的定位

五六年前,有人就提出一個有關緩存的問題,那就是緩存作為一個非持久化資料,我們該怎麼劃分它。是否需要保證它的可用性。其中就有一位阿裡的前輩在他的書中提到,他更傾向于認為緩存并不是一種持久化資料,不該将緩存作為一種可靠資料源。但是這位前輩也表示現有的架構中對緩存依賴較重,應該在一定程度上保護它們,避免緩存雪崩等情況。

我的看法是,在現有的技術體系中,緩存中間件等已經不再隻是一個緩存了。一方面我們已經将Session等重要資料放在了緩存中,并且目前沒有一個更合适的對應存儲(我認為暫時也不需要一個新的存儲方式。但是如果需要的話,可以将緩存中間件執行個體等按照内容的生命周期等進行分組)。另一方面,我們會需要明确緩存在系統中職責,它隻是用來作為緩存,以及一些分布式記憶體。但是諸如單機所有的内部調用,應該通過消息中間件或RPC等來實作。并且明确不同緩存的職責,如Session不該放在Cookie中等。

緩存的分類

緩存架構大緻可以從用戶端到資料源,分成以下分類。

  • 浏覽器緩存
    • Cookie
    • LocalStorage
    • SessionStorage
  • CDN緩存
  • 負載層緩存
    • Nginx緩存子產品
    • Squid緩存伺服器
    • Lua擴充
  • 應用層緩存
    • Etag
    • ThreadLocal
    • Guava
  • 外部緩存
    • Redis
  • 資料庫緩存
    • MySql緩存

我特意查詢了一下百度,首頁上的有關緩存架構的部落格,一半都隻是在圍繞着緩存中間件闡述緩存架構,剩下的一般也往往在大分類上有所遺漏(如浏覽器緩存,資料庫緩存)。當然也有一些部落格在專門的領域闡述得較為深入,或者層次的劃分比較不錯。故本部落格隻是在闡述現階段我對緩存架構的認識(也借鑒了一些書籍,課程的緩存體系)。

浏覽器緩存,也是很多時候被後端所遺忘的部分。因為這已經不屬于後端的工作了,但這一定屬于架構師或者相關技術負責的職責。當然還有一個原因是我做過專門的前端開發。

說白了,就是在浏覽器儲存一部分資料,當然這需要前端進行開發。

這裡直接上圖,大家可以看一下Cookie,LocalStorage,SessionStorage:

PS:圖檔來自網絡

優勢

由于是浏覽器緩存,位于整個web請求相應架構的client端,是以對業務提供方沒有任何負載壓力與影響。隻是用戶端的浏覽器存在些許的存儲占據與計算負載。

注意

  • Cookie等的存儲容量是有限的,需要注意配置設定。
  • Cookie等的存儲是明文的,不可以存儲敏感資料,否則會存在安全隐患。
  • Cookie等需要注意存儲時間時間的有效設定。
  • Cookie等存在一定的學習成本,與相關特性(如Cookie的域名設定問題,父域名無法讀子域名的Cookie資料)。
  • Cookie等需要明确業務中有哪些資料适合放在這裡,如域名等。

實際應用

在我之前負責的IOT項目中,頁面往往存在大量的資料,如終端清單,傳感器清單,監測點清單等。并且資料間存在一定資料關系,如需要通過現存的終端清單來擷取對應傳感器清單,又如通過傳感器清單來擷取對應報警清單等。

為了避免頁面切換時,為了擷取一個清單而需要多次請求(如為了獲得已標明的終端清單的傳感器清單,需要先請求終端清單),是以通過LocalStorage來存儲終端清單。

CDN,Content Delivery Network,即内容分發網絡。

  • CDN是建構網絡上的内容分發網絡
  • CDN可以使得使用者就近擷取所需内容,避免網絡擁塞,提高使用者通路速度
  • CDN依靠部署在各地的伺服器,通過鏡像伺服器實作内容同步,其包括負載均衡,内容分發,排程等子產品。

  • 降低通路延遲。使得使用者就近擷取所需内容,避免過多路由造成使用者通路延遲問題。
  • 降低伺服器壓力。畢竟放在CDN伺服器的内容,就不用到應用伺服器擷取了。
  • 消除營運商差别。消除營運商之家互聯的瓶頸造成的影響,使得所有使用者獲得同樣的通路品質
  • 叢集抗攻擊。廣泛分布的CDN節點,可有有效避免DDOS等攻擊。

缺點

  • 同步緩慢。由于CDN是大量且分層的節點分布,是以資料的下發與同步會比較緩慢。
  • 如果是使用收費服務,則需要一定支出。如果是自建CDN,則需要技術付出。個人推薦,不必要的話,還是直接采用CDN收費服務吧,成本效益更高一些。
  • 自身Web體系需要進行相應的調整。如CDN檔案更新與伺服器檔案更新(版本号等手段)等問題。

關鍵技術

該部分内容,引自網易雲課堂。

  • 緩存
    • 緩存代理軟體:Squid
    • 緩存算法決定命中率,源伺服器壓力,FTP節點存儲能力
  • 分發能力
    • 分發能力取決于IDC(網絡資料中西)能力和IDC政策性分布
  • 負載均衡
    • 負載均衡軟體:Nginx
    • 負載均衡(智能排程)決定最佳路由,響應時間,可用性,服務品質
  • 基于DNS
    • DNS伺服器軟體:BIND
    • 基于DNS的負載均衡以CNAME實作域名中專,智取最優節點服務
    • 緩存點有用戶端浏覽器緩存,本地DNS伺服器緩存
    • 緩存内哦讓那個有DNS位址緩存,客戶請求内容緩存,動态内容緩存
  • 支援協定
    • 靜動态加速(圖檔加速,https帶證書加速)
    • 下載下傳加速
    • 流媒體加速
    • 企業應用加速
    • 手機應用加速

就當擴充一下見識吧(囧)

如果寫過前端代碼,會知道有的時候,我們采用的jQuery等通用JS,CSS等大多是使用公共的cdn位址。

有的公司,會将公司的一些公共JS,圖檔等靜态資源(尤其是公司Logo等),放在CDN上。進行網頁開發時,直接引用對應的CDN位址。

負載層緩存一般是與負載均衡器相關的緩存,這裡我就拿Nginx舉例。

Nginx可以通過以下三種手段,實作緩存:

  • 本身的緩存子產品
  • 轉發請求至對應緩存伺服器
  • 可以通過lua子產品,直接從外部緩存(如Redis等)擷取緩存資料

接下來一一闡述

Nginx的http_proxy子產品,可以實作類似于Squid的緩存功能.

Nginx對用戶端已經通路的内容在Nginx伺服器本地建立緩存副本,那麼在一定時間内再次通路這些内容時,就不需要請求後面的應用伺服器了。

與此同時,當後面的應用伺服器無法提供服務時(如當機),Nginx伺服器上的緩存資源還能夠回應相關的使用者請求,提高了後面應用伺服器的魯棒性(健壯性)。

  • 商業成本無。Nginx是開源的,無需商業付費。
  • 技術疊代成本低。現有的Web體系大多采用Nginx,進行技術疊代時,在Nginx隻需要增加一個新的子產品即可。
  • 可定制。可以根據需要,對指定路徑,指定資源等進行定制化的緩存政策。

  • 需要對Nginx的緩存子產品進行一定的認識與學習。畢竟很多人使用Nginx都隻是CV一下配置。
  • 需要根據業務需要與技術特點,進行緩存政策的調整。如果缺乏經驗與足夠的認識,可能會指定出不恰當的緩存技術規範(如哪些資料該走Nginx緩存子產品等)。

基本認識

緩存檔案位置設定

通過proxy_cache_path參數指定。proxy_cache_path有兩個必填參數:

  • 第一個參數為緩存目錄。
  • 第二個keys_zone參數指定緩存名稱和占用記憶體空間的大小。
指定特定請求被緩存
  • Nginx預設會緩存所有get和head方法的請求結果,緩存的key預設使用請求字元串
  • 自定義key。如proxy_cache_key "$host$request_uri$cookie_user";
  • 指定請求至少被發送了多少次以上才被緩存,進而避免低頻請求被緩存。如proxy_cache_min_uses 5;
  • 指定哪些方法的請求被緩存。如proxy_cache_methods GET HEAD POST;
緩存有效期

預設情況下,緩存内容是長期留存,除非緩存的容量超出誰知的限制。也可以自定義設定有效時間。如:

  • 響應狀态碼為200 302時,10分鐘有效期限:proxy_cache_valid 200 302 10m;
  • 對任何狀态碼,5分鐘有效期限:proxy_cache_valid any 5m;
部分請求跳過緩存

通過proxy_cache_bypass指令,明确請求對應的響應來自原始資料,而不是緩存。

例如(該示例來自網易雲課堂) proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;

表示:如果任何一個參數不為空,或者不等于0,nginx就不會查找緩存,直接進行代理轉發。

擴充

網頁的緩存是由HTTP消息頭中的“Cache-control”來控制的,常見的取值有private,no-cache,max-age,must-revalidate等,預設為private。詳見下表:

緩存中間件-緩存架構的實作(上)

其實Squid緩存伺服器與Nginx緩存十分類似(畢竟Nginx的緩存就是仿照Squid的),是以這裡隻是表示有這麼個選擇,不做深入。

Nginx是C語言開發(這也是Nginx高性能的根本原因之一),并且Nginx子產品需要用C開發,并且需要符合一系列複雜的規則,還需要熟悉Nginx源碼。

ngx_lua子產品

是以Nginx提供了ngx_lua子產品,通過lua解釋器內建進Nginx。而ngx_lua子產品具備以下特性:

  • 高并發,非阻塞地處理各種請求。
  • Lua内建協程(可對比golang),進而将異步回調轉換成順序調用的形式。
  • 每個協程都有一個獨立的全局環境(變量空間),繼承于全局共享的,隻讀的“comman data”。

上述隻是簡單提一下Lua擴充,感興趣的可以查詢相關資料。

這裡繼續闡述Lua擴充,實作緩存功能。

為了幫助大家了解,先說一下實際應用。

Nginx針對HTTP請求處理,有十一個階段。與之相對的,ngx_lua子產品的執行指令都包含在了上述的十一個階段。這裡隻說一下其中的content_by_lua指令,針對的是Nginx的content階段,可以在location,location if範圍内使用,主要作為内容處理器,接收請求處理并輸出響應。

具體配置如下:

緩存中間件-緩存架構的實作(上)

這樣配置後,直接浏覽器通路本地ip(或者通過curl指令),可以看到“Hello,world”。

當然,這種用法相對比較初級。在OpenResty中存在一些元件,可以幫助ngx_lua子產品直接通路Redis這樣的資料源。這樣就可以将一些簡單的資料通過這種方式來進行通路,降低應用伺服器壓力。

  • 降低應用伺服器壓力
  • 門檻較低。可以按照一些配置模闆,直接進行使用
  • 擴充性較強。ngx_lua子產品的應用上限還是比較高的
  • 靈活性強。ngx_lua子產品的靈活性,表示其在緩存方面具有較高的靈活性

  • 精通難。想要精通這部分的話,需要了解lua腳本,以及Nginx的HTTP請求階段等。
  • 額外的開發任務。除了應用開發外,還需要專門的lua開發。
  • 耦合性較高。一個頁面,一個功能,卻往往需要進行Nginx與後端聯合開發。
  • 任務難以界定。在業務上難以界定一些功能的開發該歸于哪個子產品(Nginx,後端)。

總結

至此,我們已經了解了緩存架構中最靠近使用者的三層緩存:浏覽器緩存,CDN緩存,負載層緩存。

如果存在什麼問題,或者疑惑,可以私信或@我。