上章節介紹了Redis相關知識,瞭解了Redis的高可用,高性能的緣由。不少人認爲提到緩存,就侷限於Redis,其實緩存的應用不只僅在於Redis的使用,好比還有Nginx緩存,緩存隊列等等。這章節咱們會將講解Nginx+Lua實現多級緩存方法,來解決高併發訪問的場景。nginx
咱們來看一張微服務架構緩存的使用web
咱們能夠看到微服務架構中,會大量使用到緩存算法
1.客戶端緩存(手機、PC)
2.Nginx緩存
3.微服務網關限流令牌緩存
4.Nacos緩存服務列表、配置文件
5.各大微服務自身也具備緩存
6.數據庫查詢Query Cache
7.Redis集羣緩存
8.Kafka也屬於緩存數據庫
應對高併發的最有效手段之一就是分佈式緩存,分佈式緩存不只僅是緩存要顯示的數據這麼簡單,還能夠在限流、隊列削峯、高速讀寫、分佈式鎖等場景發揮重大做用。分佈式緩存能夠說是解決高併發場景的有效利器。以如下場景爲例:json
一、凌晨忽然涌入的巨大流量。【隊列術】【限流術】 二、高併發場景秒殺、搶紅包、搶優惠券,快速存取。【緩存取代MySQL操做】 三、高併發場景超賣、超額搶紅包。【Redis單線程取代數據庫操做】 四、高併發場景重複搶單。【Redis搶單計數器】
一談到緩存架構,不少人想到的是Redis,但其實整套體系的緩存架構並不是只有Redis,而應該是多個層面多個軟
件結合造成一套很是良性的緩存體系。好比我們的緩存架構設計就涉及到了多個層面的緩存軟件。後端
一、HTML頁面作緩存,瀏覽器端能夠緩存HTML頁面和其餘靜態資源,防止用戶頻繁刷新對後端形成巨大壓力 二、Lvs實現記錄不一樣協議以及不一樣用戶請求鏈路緩存 三、Nginx這裏會作HTML頁面緩存配置以及Nginx自身緩存配置 四、數據查找這裏用Lua取代了其餘語言查找,提升了處理的性能效率,併發處理能力將大大提高 五、數據緩存採用了Redis集羣+主從架構,並實現緩存讀寫分離操做 六、集成Canal實現數據庫數據增量實時同步Redis
客戶端側緩存通常指的是瀏覽器緩存、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。
用戶若是請求獲取的數據不是須要後端服務器處理返回,若是咱們須要對數據作緩存來提升服務器的處理能力,咱們能夠按照以下步驟實現:緩存
一、請求Nginx,Nginx將請求路由給後端服務 二、後端服務查詢Redis或者MySQL,再將返回結果給Nginx 三、Nginx將結果存入到Nginx緩存,並將結果返回給用戶 四、用戶下次執行一樣請求,直接在Nginx中獲取緩存數據
具體流程服務器
一、用戶請求通過Nginx 二、Nginx檢查是否有緩存,若是Nginx有緩存,直接響應用戶數據 三、Nginx若是沒有緩存,則將請求路由給後端Java服務 四、Java服務查詢Redis緩存,若是有數據,則將數據直接響應給Nginx,並將數據存入緩存,Nginx將數據響應給用戶 五、若是Redis沒有緩存,則使用Java程序查詢MySQL,並將數據存入到Reids,再將數據存入到Nginx中
優缺點架構
優勢: 一、採用了Nginx緩存,減小了數據加載的路徑,從而提高站點數據加載效率 二、多級緩存有效防止了緩存擊穿、緩存穿透問題 缺點 Tomcat併發量偏低,致使緩存同步併發量失衡,緩存首次加載效率偏低,Tomcat 大規模集羣佔用資源高
優勢 一、採用了Nginx緩存,減小了數據加載的路徑,從而提高站點數據加載效率 二、多級緩存有效防止了緩存擊穿、緩存穿透問題 三、使用了Nginx+Lua集成,不管是哪次緩存加載,效率都高 四、Nginx併發量高,Nginx+Lua集成,大幅提高了併發能力
上面咱們已經分析過紅包雨的特色,要想實現一套高效的紅包雨系統,緩存架構是關鍵。咱們根據紅包雨的特色設計瞭如上圖所示的紅包雨緩存架構體系。
一、紅包雨分批次導入到Redis緩存而不要每次操做數據庫 二、不少用戶搶紅包的時候,爲了不1個紅包被多人搶到,咱們要採用Redis的隊列存儲紅包 三、追加紅包的時候,能夠追加延時發放紅包,也能夠直接追加當即發放紅包 四、用戶搶購紅包的時候,會先通過Nginx,經過Lua腳本查看緩存中是否存在紅包,若是不存在紅包,則直接終止搶紅包 五、若是還存在紅包,爲了不後臺同時處理不少請求,這裏採用隊列術緩存用戶請求,後端經過消費隊列執行搶紅包
一、隊列控制併發溢出:併發量很是大的系統,例如秒殺、搶紅包、搶票等操做,都是存在溢出現象,好比秒殺超賣、搶紅包超額、一票多單等溢出現象,若是採用數據庫鎖來控制溢出問題,效率很是低,在高併發場景下,頗有可能直接致使數據庫崩潰,所以針對高併發場景下數據溢出解決方案咱們能夠採用Redis緩存提高效率。
二、隊列限流:解決大量併發用戶一擁而上的方法能夠採用隊列術將用戶的請求用隊列緩存起來,後端服務從隊列緩存中有序消費,能夠防止後端服務同時面臨處理大量請求。緩存用戶請求能夠用RabbitMQ、Kafka、RocketMQ、ActiveMQ等。用戶搶紅包的時候,咱們用Lua腳本實現將用戶搶紅包的信息以生產者角色將消息發給RabbitMQ,後端應用服務以消費者身份從RabbitMQ獲取消息並搶紅包,再將搶紅包信息以WebSocket方式通知給用戶。
nginx提供兩種限流的方式:一是控制速率,二是控制併發鏈接數。
一、速率限流
控制速率的方式之一就是採用漏桶算法。具體配置以下:
二、控制併發量
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個
產生緣由
當咱們查詢一個緩存不存在的數據,就去查數據庫,但此時若是數據庫也沒有這個數據,後面繼續訪問依然會再次查詢數據庫,當有用戶大量請求不存在的數據,必然會致使數據庫的壓力升高,甚至崩潰。
如何解決
一、當查詢到不存在的數據,也將對應的key放入緩存,值爲nul,這樣再次查詢會直接返回null,若是後面新增了該key的數據,就覆蓋便可。
二、使用布隆過濾器。布隆過濾器主要是解決大規模數據下不須要精確過濾的業務場景,如檢查垃圾郵件地址,爬蟲URL地址去重,解決緩存穿透問題等。
產生緣由
當緩存在某一刻過時了,通常若是再查詢這個緩存,會從數據庫去查詢一次再放到緩存,若是正好這一刻,大量的請求該緩存,那麼請求都會打到數據庫中,可能致使數據庫打垮。
如何解決
一、儘可能避免緩存過時時間都在同一時間。
二、定時任務主動刷新更新緩存,或者設置緩存不過去,適合那種key相對固定,粒度較大的業務。
分享下我在公司的負責的系統是如何防止緩存擊穿的,因爲業務場景,緩存的數據都是當天有效的,當天查詢的只查當日有效的數據,因此當時數據都是設置當天凌晨過時,而且緩存是懶加載,這樣致使0點高峯期數據庫壓力明顯增大。後來改造了下,作了個定時任務,天天凌晨3點,跑次日生效的數據,而且設置失效時間延長一天。有效解決了該問題,至關於緩存預熱。
三、多級緩存
採用多級緩存也能夠有效防止擊穿現象,首先經過程序將緩存存入到Redis緩存,且永不過時,用戶查詢的時候,先查詢Nginx緩存,若是Nginx緩存沒有,則查詢Redis緩存,並將Redis緩存存入到Nginx一級緩存中,並設置更新時間。這種方案不只能夠提高查詢速度,同時又能防止擊穿問題,而且提高了程序的抗壓能力。
四、分佈式鎖與隊列。解決思路主要是防止多請求同時打過去。分佈式鎖,推薦使用Redisson。隊列方案可使用nginx緩存隊列,配置以下。
產生緣由
緩存雪崩是指,因爲緩存層承載着大量請求,有效的保護了存儲層,可是若是緩存層因爲某些緣由總體不能提供服
務,因而全部的請求都會達到存儲層,存儲層的調用量會暴增,形成存儲層也會掛掉的狀況。
如何解決
一、作緩存集羣。即便個別節點、個別機器、甚至是機房宕掉,依然能夠提供服務,好比 Redis Sentinel 和 Redis Cluster 都實現了高可用。
二、作好限流。微服務網關或者Nginx作好限流操做,防止大量請求直接進入後端,使後端載荷太重最後宕機。
三、緩存預熱。預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存不一樣的key,設置不一樣的過時時間,讓緩存失效的時間點儘可能均勻,不要同時失效。
四、加鎖。數據操做,若是是帶有緩存查詢的,均使用分佈式鎖,防止大量請求直接操做數據庫。
五、多級緩存。採用多級緩存,Nginx+Redis+MyBatis二級緩存,當Nginx緩存失效時,查找Redis緩存,Redis緩存失效查找MyBatis二級緩存。
問題描述
數據的在增量數據,未同步到緩存。致使緩存與數據庫數據不一致。
解決方案Canal
用戶每次操做數據庫的時候,使用Canal監聽數據庫指定表的增量變化,在Java程序中消費Canal監聽到的增量變化,並在Java程序中實現對Redis緩存或者Nginx緩存的更新。 用戶查詢的時候,先經過Lua查詢Nginx的緩存,若是Nginx緩存沒有數據,則查詢Redis緩存,Redis緩存若是也沒有數據,能夠去數據庫查詢。