前言
上章節介紹了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緩存如果也沒有數據,可以去數據庫查詢。