共計 4917 個字符,預計需要花費 13 分鐘才能閱讀完成。
本篇內容介紹了“Redis 遇到并發、雪崩問題怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
一、Redis 雪崩、穿透、并發等 5 大難題解決方案
緩存雪崩
數據未加載到緩存中,或者緩存同一時間大面積的失效,從而導致所有請求都去查數據庫,導致數據庫 CPU 和內存負載過高,甚至宕機。
比如一個雪崩的簡單過程:
1、redis 集群大面積故障
2、緩存失效,但依然大量請求訪問緩存服務 redis
3、redis 大量失效后,大量請求轉向到 mysql 數據庫
4、mysql 的調用量暴增,很快就扛不住了,甚至直接宕機
5、由于大量的應用服務依賴 mysql 和 redis 的服務,這個時候很快會演變成各服務器集群的雪崩,最后網站徹底崩潰。
如何預防緩存雪崩:
1. 緩存的高可用性
緩存層設計成高可用,防止緩存大面積故障。即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如 Redis Sentinel 和 Redis Cluster 都實現了高可用。
2. 緩存降級
可以利用 ehcache 等本地緩存(暫時支持),但主要還是對源服務訪問進行限流、資源隔離(熔斷)、降級等。
當訪問量劇增、服務出現問題仍然需要保證服務還是可用的。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級,這里會涉及到運維的配合。
降級的最終目的是保證核心服務可用,即使是有損的。
比如推薦服務中,很多都是個性化的需求,假如個性化需求不能提供服務了,可以降級補充熱點數據,不至于造成前端頁面是個大空白。
在進行降級之前要對系統進行梳理,比如:哪些業務是核心 (必須保證),哪些業務可以容許暫時不提供服務(利用靜態頁面替換) 等,以及配合服務器核心指標,來后設置整體預案,比如:
(1)一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在 95~100% 之間),可以自動降級或人工降級,并發送告警;
(3)錯誤:比如可用率低于 90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。
3.Redis 備份和快速預熱
1)Redis 數據備份和恢復
2)快速緩存預熱
4. 提前演練
最后,建議還是在項目上線前,演練緩存層宕掉后,應用以及后端的負載情況以及可能出現的問題,對高可用提前預演,提前發現問題。
緩存穿透
緩存穿透是指查詢一個一不存在的數據。例如:從緩存 redis 沒有命中,需要從 mysql 數據庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到數據庫去查詢,造成緩存穿透。
解決思路:
如果查詢數據庫也為空,直接設置一個默認值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會繼續訪問數據庫。設置一個過期時間或者當有值的時候將緩存中的值替換掉即可。
可以給 key 設置一些格式規則,然后查詢之前先過濾掉不符合規則的 Key。
緩存并發
這里的并發指的是多個 redis 的 client 同時 set key 引起的并發問題。其實 redis 自身就是單線程操作,多個 client 并發操作,按照先到先執行的原則,先到的先執行,其余的阻塞。當然,另外的解決方案是把 redis.set 操作放在隊列中使其串行化,必須的一個一個執行。
緩存預熱
緩存預熱就是系統上線后,將相關的緩存數據直接加載到緩存系統。
這樣就可以避免在用戶請求的時候,先查詢數據庫,然后再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!
解決思路:
1、直接寫個緩存刷新頁面,上線時手工操作下;
2、數據量不大,可以在項目啟動的時候自動進行加載;
目的就是在系統上線前,將數據加載到緩存中。
二、Redis 為什么是單線程,高并發快的 3 大原因詳解
Redis 的高并發和快速原因
1.redis 是基于內存的,內存的讀寫速度非常快;
2.redis 是單線程的,省去了很多上下文切換線程的時間;
3.redis 使用多路復用技術,可以處理并發的連接。非阻塞 IO 內部實現采用 epoll,采用了 epoll+ 自己實現的簡單的事件框架。epoll 中的讀、寫、關閉、連接都轉化成了事件,然后利用 epoll 的多路復用特性,絕不在 io 上浪費一點時間。
下面重點介紹單線程設計和 IO 多路復用核心設計快的原因。
為什么 Redis 是單線程的?
1. 官方答案
因為 Redis 是基于內存的操作,CPU 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且 CPU 不會成為瓶頸,那就順理成章地采用單線程的方案了。
2. 性能指標
關于 redis 的性能,官方網站也有,普通筆記本輕松處理每秒幾十萬的請求。
3. 詳細原因
1)不需要各種鎖的性能消耗
Redis 的數據結構并不全是簡單的 Key-Value,還有 list,hash 等復雜的結構,這些結構有可能會進行很細粒度的操作,比如在很長的列表后面添加一個元素,在 hash 當中添加或者刪除
一個對象。這些操作可能就需要加非常多的鎖,導致的結果是同步開銷大大增加。
總之,在單線程的情況下,就不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗。
2)單線程多進程集群方案
單線程的威力實際上非常強大,每核心效率也非常高,多線程自然是可以比單線程有更高的性能上限,但是在今天的計算環境中,即使是單機多線程的上限也往往不能滿足需要了,需要進一步摸索的是多服務器集群化的方案,這些方案中多線程的技術照樣是用不上的。
所以單線程、多進程的集群不失為一個時髦的解決方案。
3)CPU 消耗
采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU。
但是如果 CPU 成為 Redis 瓶頸,或者不想讓服務器其他 CUP 核閑置,那怎么辦?
可以考慮多起幾個 Redis 進程,Redis 是 key-value 數據庫,不是關系數據庫,數據之間沒有約束。只要客戶端分清哪些 key 放在哪個 Redis 進程上就可以了。
Redis 單線程的優劣勢
單進程單線程優勢
代碼更清晰,處理邏輯更簡單不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗不存在多進程或者多線程導致的切換而消耗 CPU
單進程單線程弊端
無法發揮多核 CPU 性能,不過可以通過在單機開多個 Redis 實例來完善;
IO 多路復用技術
redis 采用網絡 IO 多路復用技術來保證在多連接的時候,系統的高吞吐量。
多路 - 指的是多個 socket 連接,復用 - 指的是復用一個線程。多路復用主要有三種技術:select,poll,epoll。epoll 是最新的也是目前最好的多路復用技術。
這里“多路”指的是多個網絡連接,“復用”指的是復用同一個線程。采用多路 I/O 復用技術可以讓單個線程高效的處理多個連接請求(盡量減少網絡 IO 的時間消耗),且 Redis 在內存中操作數據的速度非常快(內存內的操作不會成為這里的性能瓶頸),主要以上兩點造就了 Redis 具有很高的吞吐量。
Redis 高并發快總結
1. Redis 是純內存數據庫,一般都是簡單的存取操作,線程占用的時間很多,時間的花費主要集中在 IO 上,所以讀取速度快。
2. 再說一下 IO,Redis 使用的是非阻塞 IO,IO 多路復用,使用了單線程來輪詢描述符,將數據庫的開、關、讀、寫都轉換成了事件,減少了線程切換時上下文的切換和競爭。
3. Redis 采用了單線程的模型,保證了每個操作的原子性,也減少了線程的上下文切換和競爭。
4. 另外,數據結構也幫了不少忙,Redis 全程使用 hash 結構,讀取速度快,還有一些特殊的數據結構,對數據存儲進行了優化,如壓縮表,對短數據進行壓縮存儲,再如,跳表,使用有序的數據結構加快讀取的速度。
5. 還有一點,Redis 采用自己實現的事件分離器,效率比較高,內部采用非阻塞的執行方式,吞吐能力比較大。
三、Redis 緩存和 MySQL 數據一致性方案詳解
需求起因
在高并發的業務場景下,數據庫大多數情況都是用戶并發訪問最薄弱的環節。所以,就需要使用 redis 做一個緩沖操作,讓請求先訪問到 redis,而不是直接訪問 MySQL 等數據庫。
這個業務場景,主要是解決讀數據從 Redis 緩存,一般都是按照下圖的流程來進行業務操作。
讀取緩存步驟一般沒有什么問題,但是一旦涉及到數據更新:數據庫和緩存更新,就容易出現緩存 (Redis) 和數據庫(MySQL)間的數據一致性問題。
不管是先寫 MySQL 數據庫,再刪除 Redis 緩存;還是先刪除緩存,再寫庫,都有可能出現數據不一致的情況。舉一個例子:
1. 如果刪除了緩存 Redis,還沒有來得及寫庫 MySQL,另一個線程就來讀取,發現緩存為空,則去數據庫中讀取數據寫入緩存,此時緩存中為臟數據。
2. 如果先寫了庫,在刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致情況。
因為寫和讀是并發的,沒法保證順序, 就會出現緩存和數據庫的數據不一致的問題。
如來解決?這里給出兩個解決方案,先易后難,結合業務和技術代價選擇使用。
緩存和數據庫一致性解決方案
1. 第一種方案:采用延時雙刪策略
在寫庫前后都進行 redis.del(key)操作,并且設定合理的超時時間。
偽代碼如下:
public void write(String key,Object data){ redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
具體的步驟就是:
先刪除緩存;再寫數據庫;休眠 500 毫秒;再次刪除緩存。
那么,這個 500 毫秒怎么確定的,具體該休眠多久呢?
需要評估自己的項目的讀數據業務邏輯的耗時。這么做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的緩存臟數據。
當然這種策略還要考慮 redis 和數據庫主從同步的耗時。最后的的寫數據的休眠時間:則在讀數據業務邏輯的耗時基礎上,加幾百 ms 即可。比如:休眠 1 秒。
設置緩存過期時間
從理論上來說,給緩存設置過期時間,是保證最終一致性的解決方案。所有的寫操作以數據庫為準,只要到達緩存過期時間,則后面的讀請求自然會從數據庫中讀取新值然后回填緩存。
該方案的弊端
結合雙刪策略 + 緩存超時設置,這樣最差的情況就是在超時時間內數據存在不一致,而且又增加了寫請求的耗時。
2、第二種方案:異步更新緩存(基于訂閱 binlog 的同步機制)
技術整體思路:
MySQL binlog 增量訂閱消費 + 消息隊列 + 增量數據更新到 redis
讀 Redis:熱數據基本都在 Redis 寫 MySQL: 增刪改都是操作 MySQL 更新 Redis 數據:MySQ 的數據操作 binlog,來更新到 Redis
Redis 更新
1)數據操作主要分為兩大塊:
一個是全量 (將全部數據一次寫入到 redis) 一個是增量(實時更新)
這里說的是增量, 指的是 mysql 的 update、insert、delate 變更數據。
2)讀取 binlog 后分析,利用消息隊列, 推送更新各臺的 redis 緩存數據。
這樣一旦 MySQL 中產生了新的寫入、更新、刪除等操作,就可以把 binlog 相關的消息推送至 Redis,Redis 再根據 binlog 中的記錄,對 Redis 進行更新。
其實這種機制,很類似 MySQL 的主從備份機制,因為 MySQL 的主備也是通過 binlog 來實現的數據一致性。
這里可以結合使用 canal(阿里的一款開源框架),通過該框架可以對 MySQL 的 binlog 進行訂閱,而 canal 正是模仿了 mysql 的 slave 數據庫的備份請求,使得 Redis 的數據更新達到了相同的效果。
當然,這里的消息推送工具你也可以采用別的第三方:kafka、rabbitMQ 等來實現推送更新 Redis。
“Redis 遇到并發、雪崩問題怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!