Java進階專題(十八) 系統快取架構設計 (下)

有夢想的老王發表於2020-12-04

前言

上章節介紹了Redis相關知識,瞭解了Redis的高可用,高效能的原因。很多人認為提到快取,就侷限於Redis,其實快取的應用不僅僅在於Redis的使用,比如還有Nginx快取,快取佇列等等。這章節我們會將講解Nginx+Lua實現多級快取方法,來解決高併發訪問的場景。

快取的應用

我們來看一張微服務架構快取的使用

我們可以看到微服務架構中,會大量使用到快取

1.客戶端快取(手機、PC)
2.Nginx快取
3.微服務閘道器限流令牌快取
4.Nacos快取服務列表、配置檔案
5.各大微服務自身也具有快取
6.資料庫查詢Query Cache
7.Redis叢集快取
8.Kafka也屬於快取

高併發站點快取技術選型

應對高併發的最有效手段之一就是分散式快取,分散式快取不僅僅是快取要顯示的資料這麼簡單,還可以在限流、佇列削峰、高速讀寫、分散式鎖等場景發揮重大作用。分散式快取可以說是解決高併發場景的有效利器。以以下場景為例:

1、凌晨突然湧入的巨大流量。【佇列術】【限流術】
2、高併發場景秒殺、搶紅包、搶優惠券,快速存取。【快取取代MySQL操作】
3、高併發場景超賣、超額搶紅包。【Redis單執行緒取代資料庫操作】
4、高併發場景重複搶單。【Redis搶單計數器】

一談到快取架構,很多人想到的是Redis,但其實整套體系的快取架構並非只有Redis,而應該是多個層面多個軟
件結合形成一套非常良性的快取體系。比如我們們的快取架構設計就涉及到了多個層面的快取軟體。

1、HTML頁面做快取,瀏覽器端可以快取HTML頁面和其他靜態資源,防止使用者頻繁重新整理對後端造成巨大壓力
2、Lvs實現記錄不同協議以及不同使用者請求鏈路快取
3、Nginx這裡會做HTML頁面快取配置以及Nginx自身快取配置
4、資料查詢這裡用Lua取代了其他語言查詢,提高了處理的效能效率,併發處理能力將大大提升
5、資料快取採用了Redis叢集+主從架構,並實現快取讀寫分離操作
6、整合Canal實現資料庫資料增量實時同步Redis

Nginx快取

瀏覽器快取

客戶端側快取一般指的是瀏覽器快取、app快取等等,目的就是加速各種靜態資源的訪問,降低伺服器壓力。我們通過配置Nginx設定網頁快取資訊,從而降低使用者對伺服器頻繁訪問造成的巨大壓力。

HTTP 中最基本的快取機制,涉及到的 HTTP 頭欄位,包括 Cache‐Control, Last‐Modified, If‐Modified‐Since, 
Etag,If‐None‐Match 等。
 
Last‐Modified/If‐Modified‐Since
 
Etag是服務端的一個資源的標識,在 HTTP 響應頭中將其傳送到客戶端。所謂的服務端資源可以是一個Web頁面,也可
以是JSON或XML等。伺服器單獨負責判斷記號是什麼及其含義,並在HTTP響應頭中將其傳送到客戶端。比如,瀏覽器第
一次請求一個資源的時候,服務端給予返回,並且返回了ETag: "50b1c1d4f775c61:df3" 這樣的字樣給瀏覽器,當瀏
覽器再次請求這個資源的時候,瀏覽器會將If‐None‐Match: W/"50b1c1d4f775c61:df3" 傳輸給服務端,服務端拿到
該ETAG,對比資源是否發生變化,如果資源未發生改變,則返回304HTTP狀態碼,不返回具體的資源。
 
Last‐Modified :標示這個響應資源的最後修改時間。web伺服器在響應請求時,告訴瀏覽器資源的最後修改時間。
 
If‐Modified‐Since :當資源過期時(使用Cache‐Control標識的max‐age),發現資源具有 Last‐Modified 聲
明,則再次向web伺服器請求時帶上頭。
 
If‐Modified‐Since ,表示請求時間。web伺服器收到請求後發現有頭 If‐Modified‐Since 則與被請求資源的最後修
改時間進行比對。若最後修改時間較新,說明資源有被改動過,則響應整片資源內容(寫在響應訊息包體內),HTTP 
200;若最後修改時間較舊,說明資源無新修改,則響應 HTTP 304 (無需包體,節省瀏覽),告知瀏覽器繼續使用所保
存的 cache 。
 
Pragma行是為了相容 HTTP1.0 ,作用與 Cache‐Control: no‐cache 是一樣的
 
Etag/If‐None‐Match
Etag :web伺服器響應請求時,告訴瀏覽器當前資源在伺服器的唯一標識(生成規則由伺服器決定),如果給定URL中的
資源修改,則一定要生成新的Etag值。
 
If‐None‐Match :當資源過期時(使用Cache‐Control標識的max‐age),發現資源具有Etage宣告,則再次向web服
務器請求時帶上頭 If‐None‐Match (Etag的值)。web伺服器收到請求後發現有頭 If‐None‐Match 則與被請求資源
的相應校驗串進行比對,決定返回200或304。
 
Etag:
Last‐Modified 標註的最後修改只能精確到秒級,如果某些檔案在1秒鐘以內,被修改多次的話,它將不能準確標註文
件的修改時間,如果某些檔案會被定期生成,當有時內容並沒有任何變化,但 Last‐Modified 卻改變了,導致檔案沒
法使用快取有可能存在伺服器沒有準確獲取檔案修改時間,或者與代理伺服器時間不一致等情形 Etag是伺服器自動生成
或者由開發者生成的對應資源在伺服器端的唯一識別符號,能夠更加準確的控制快取。 Last‐Modified 與 ETag 是可以
一起使用的,伺服器會優先驗證 ETag ,一致的情況下,才會繼續比對 Last‐Modified ,最後才決定是否返回304。

代理快取

使用者如果請求獲取的資料不是需要後端伺服器處理返回,如果我們需要對資料做快取來提高伺服器的處理能力,我們可以按照如下步驟實現:

1、請求Nginx,Nginx將請求路由給後端服務
2、後端服務查詢Redis或者MySQL,再將返回結果給Nginx
3、Nginx將結果存入到Nginx快取,並將結果返回給使用者
4、使用者下次執行同樣請求,直接在Nginx中獲取快取資料

多級快取架構

具體流程

1、使用者請求經過Nginx
2、Nginx檢查是否有快取,如果Nginx有快取,直接響應使用者資料
3、Nginx如果沒有快取,則將請求路由給後端Java服務
4、Java服務查詢Redis快取,如果有資料,則將資料直接響應給Nginx,並將資料存入快取,Nginx將資料響應給使用者
5、如果Redis沒有快取,則使用Java程式查詢MySQL,並將資料存入到Reids,再將資料存入到Nginx中

優缺點

優點:
1、採用了Nginx快取,減少了資料載入的路徑,從而提升站點資料載入效率
2、多級快取有效防止了快取擊穿、快取穿透問題
缺點
Tomcat併發量偏低,導致快取同步併發量失衡,快取首次載入效率偏低,Tomcat 大規模叢集佔用資源高

優點
1、採用了Nginx快取,減少了資料載入的路徑,從而提升站點資料載入效率
2、多級快取有效防止了快取擊穿、快取穿透問題
3、使用了Nginx+Lua整合,無論是哪次快取載入,效率都高
4、Nginx併發量高,Nginx+Lua整合,大幅提升了併發能力

搶紅包案例架構設計分享

上面我們已經分析過紅包雨的特點,要想實現一套高效的紅包雨系統,快取架構是關鍵。我們根據紅包雨的特點設計瞭如上圖所示的紅包雨快取架構體系。

1、紅包雨分批次匯入到Redis快取而不要每次運算元據庫
2、很多使用者搶紅包的時候,為了避免1個紅包被多人搶到,我們要採用Redis的佇列儲存紅包
3、追加紅包的時候,可以追加延時發放紅包,也可以直接追加立即發放紅包
4、使用者搶購紅包的時候,會先經過Nginx,通過Lua指令碼檢視快取中是否存在紅包,如果不存在紅包,則直接終止搶紅包
5、如果還存在紅包,為了避免後臺同時處理很多請求,這裡採用佇列術快取使用者請求,後端通過消費佇列執行搶紅包

快取佇列使用場景

1、佇列控制併發溢位:併發量非常大的系統,例如秒殺、搶紅包、搶票等操作,都是存在溢位現象,比如秒殺超賣、搶紅包超額、一票多單等溢位現象,如果採用資料庫鎖來控制溢位問題,效率非常低,在高併發場景下,很有可能直接導致資料庫崩潰,因此針對高併發場景下資料溢位解決方案我們可以採用Redis快取提升效率。

2、佇列限流:解決大量併發使用者蜂擁而上的方法可以採用佇列術將使用者的請求用佇列快取起來,後端服務從佇列快取中有序消費,可以防止後端服務同時面臨處理大量請求。快取使用者請求可以用RabbitMQ、Kafka、RocketMQ、ActiveMQ等。使用者搶紅包的時候,我們用Lua指令碼實現將使用者搶紅包的資訊以生產者角色將訊息發給RabbitMQ,後端應用服務以消費者身份從RabbitMQ獲取訊息並搶紅包,再將搶紅包資訊以WebSocket方式通知給使用者。

Nginx限流

nginx提供兩種限流的方式:一是控制速率,二是控制併發連線數。

1、速率限流

控制速率的方式之一就是採用漏桶演算法。具體配置如下:

2、控制併發量

ngx_http_limit_conn_module 提供了限制連線數的能力。主要是利用limit_conn_zone和limit_conn兩個指令。利用連線數限制 某一個使用者的ip連線的數量來控制流量。

(1)配置限制固定連線數
如下,配置如下:
配置限流快取空間:

根據IP地址來限制,儲存記憶體大小10M
limit_conn_zone $binary_remote_addr zone=addr:1m;

location配置:

limit_conn addr 2;

引數說明:

limit_conn_zone $binary_remote_addr zone=addr:10m;  表示限制根據使用者的IP地址來顯示,設定儲存地址為的
記憶體大小10M
 
limit_conn addr 2;   表示 同一個地址只允許連線2次。

(2)限制每個客戶端IP與伺服器的連線數,同時限制與虛擬伺服器的連線總數。
限流快取空間配置:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

location配置

limit_conn perip 10;#單個客戶端ip與伺服器的連線數
limit_conn perserver 100; #限制與伺服器的總連線數

每個IP限流 3個
總量5個

快取災難問題如何解決

快取穿透

產生原因

當我們查詢一個快取不存在的資料,就去查資料庫,但此時如果資料庫也沒有這個資料,後面繼續訪問依然會再次查詢資料庫,當有使用者大量請求不存在的資料,必然會導致資料庫的壓力升高,甚至崩潰。

如何解決

1、當查詢到不存在的資料,也將對應的key放入快取,值為nul,這樣再次查詢會直接返回null,如果後面新增了該key的資料,就覆蓋即可。

2、使用布隆過濾器。布隆過濾器主要是解決大規模資料下不需要精確過濾的業務場景,如檢查垃圾郵件地址,爬蟲URL地址去重,解決快取穿透問題等。

快取擊穿

產生原因

當快取在某一刻過期了,一般如果再查詢這個快取,會從資料庫去查詢一次再放到快取,如果正好這一刻,大量的請求該快取,那麼請求都會打到資料庫中,可能導致資料庫打垮。

如何解決

1、儘量避免快取過期時間都在同一時間。

2、定時任務主動重新整理更新快取,或者設定快取不過去,適合那種key相對固定,粒度較大的業務。

​ 分享下我在公司的負責的系統是如何防止快取擊穿的,由於業務場景,快取的資料都是當天有效的,當天查詢的只查當日有效的資料,所以當時資料都是設定當天凌晨過期,並且快取是懶載入,這樣導致0點高峰期資料庫壓力明顯增大。後來改造了下,做了個定時任務,每天凌晨3點,跑第二天生效的資料,並且設定失效時間延長一天。有效解決了該問題,相當於快取預熱。

3、多級快取

採用多級快取也可以有效防止擊穿現象,首先通過程式將快取存入到Redis快取,且永不過期,使用者查詢的時候,先查詢Nginx快取,如果Nginx快取沒有,則查詢Redis快取,並將Redis快取存入到Nginx一級快取中,並設定更新時間。這種方案不僅可以提升查詢速度,同時又能防止擊穿問題,並且提升了程式的抗壓能力。

4、分散式鎖與佇列。解決思路主要是防止多請求同時打過去。分散式鎖,推薦使用Redisson。佇列方案可以使用nginx快取佇列,配置如下。

快取雪崩

產生原因

快取雪崩是指,由於快取層承載著大量請求,有效的保護了儲存層,但是如果快取層由於某些原因整體不能提供服
務,於是所有的請求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會掛掉的情況。

如何解決

1、做快取叢集。即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,比如 Redis Sentinel 和 Redis Cluster 都實現了高可用。

2、做好限流。微服務閘道器或者Nginx做好限流操作,防止大量請求直接進入後端,使後端載荷過重最後當機。

3、快取預熱。預先去更新快取,再即將發生大併發訪問前手動觸發載入快取不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻,不要同時失效。

4、加鎖。資料操作,如果是帶有快取查詢的,均使用分散式鎖,防止大量請求直接運算元據庫。

5、多級快取。採用多級快取,Nginx+Redis+MyBatis二級快取,當Nginx快取失效時,查詢Redis快取,Redis快取失效查詢MyBatis二級快取。

快取一致性

問題描述

資料的在增量資料,未同步到快取。導致快取與資料庫資料不一致。

解決方案Canal

使用者每次運算元據庫的時候,使用Canal監聽資料庫指定表的增量變化,在Java程式中消費Canal監聽到的增量變化,並在Java程式中實現對Redis快取或者Nginx快取的更新。
使用者查詢的時候,先通過Lua查詢Nginx的快取,如果Nginx快取沒有資料,則查詢Redis快取,Redis快取如果也沒有資料,可以去資料庫查詢。

相關文章