Redis之常見問題以及解決方案

Redis通常是用來做緩存來使用的,使用緩存可以顯著的提高程序的性能。但是在高併發的時候也容易出現一些問題。

緩存雪崩

緩存雪崩的問題可能是最常見的問題了,大家可能都遇到過。所謂緩存雪崩指的是大量key在同一時間大面積失效,請求緩存的時候請求不到,這個時候就會全都打到數據庫中。針對緩存雪崩常見的解決方案有:

  • 給key設置失效時間的時候加一個隨機值,這樣子就不會造成大量的key在同一時間失效
  • 針對熱點數據的key設置永不過期
  • 使用分佈式緩存集羣(持久化、主從複製、哨兵和集羣)

當然還有很小的機率以上的方案還是沒有抵擋住緩存雪崩,那麼我們就需要做限流處理,儘量保證我們的數據庫不被打死,只要數據庫不掛掉我們的服務就是可以用的。

緩存穿透

在這裏插入圖片描述
假如用戶想查詢一個商品信息,這個時候用id = -1去調getProduct(Long id)查詢商品信息,這個時候緩存中沒有數據,數據庫也沒有數據,如果沒有做參數合法性校驗的話,用戶頻繁的請求接口,就會給數據庫造成很大的壓力,嚴重的情況可能會導致數據庫掛掉,從而導致服務不可用。

所謂緩存穿透就是用戶訪問一個緩存和數據庫中都不存在的數據,導致每次都要查詢一次緩存和數據庫,這種情況就叫做緩存穿透。緩存穿透常見的解決方案有:

  • 對請求參數做合法性校驗,比如id <= 0時,直接返回參數不正確,這一步是必須要做的
  • 如果緩存和數據庫都不存在,這個時候在緩存中給這個key的value設置成null,這樣子下次請求就可以存緩存中拿到null,從而不必再次請求數據庫。但是這個key的過期時間應該設置比較短的時間,比如10s,防止數據庫中更新了值,用戶不能及時的取到。
  • 使用布隆過濾器(BloomFilter),判斷一個key是否已經查過了,如果已經查過了,就不去數據庫查詢。
緩存擊穿

緩存擊穿和緩存雪崩有點像,所謂緩存擊穿指的是一個熱點key在不斷的抗住大併發,但是這個key過期了,就會導致很多的請求一下子全都打到了數據庫,一下子就把數據庫打掛了,從而導致了服務癱瘓。常見的解決方案有:

  • 同樣還是要做參數的合法性校驗
  • 針對熱點數據可以將key的過期時間設置的長一點,甚至有些熱點key可以設置永不過期
  • 使用互斥鎖,在請求數據的時候鎖住,返回結果後將值設到緩存中,然後再釋放鎖,下面寫一段僞代碼
public static String getData(String key){
	// 從redis中讀取數據
	String value = getDataByRedis(key);
	// 鎖住
	if(StringUtils.isBlank(value)){
		if(lock.tryLock()){
			value = getDataByDB(key);
			// 放到redis中
			set2Redis(key, value);
			lock.unLock(); 
		}
	}
	return value;
}
數據庫緩存雙寫不一致問題

首先我們先弄明白什麼是雙寫不一致問題:雙寫不一致指的是緩存和數據庫中的值不一樣

在使用數據庫緩存的時候,讀和寫的流程往往是這樣的:

  • 讀取的時候,先讀取緩存,如果緩存中沒有,就直接從數據庫中讀取,然後取出數據後放入緩存
  • 更新的時候,先刪除緩存,再更新數據庫

所謂雙寫不一致,就是在發生寫操作(更新)的時候或寫操作之後,可能會存在數據庫裏面的值和緩存中的值不同的情況。

爲什麼更新的時候要先刪除緩存,再更新數據庫?因爲如果先更新數據庫,然後在刪除緩存的時候失敗了,就會造成緩存裏面的值和數據庫的值不一致。

然而這樣並不能完全避免雙寫不一致問題。假設在大併發情景下,一個線程先刪除緩存,然後取更新數據庫,這個時候另一個線程去取緩存,發現沒有值,於是去讀數據庫,然後把數據庫舊的值設置進緩存。等第一個線程更新完數據庫後,數據庫裏面就是新的值,而緩存裏面是舊的值,所以就存在了數據不一致的問題。

  • 一個比較簡單的解決辦法是把過期時間設置得比較低,這樣就只有在緩存沒過期之前存在數據不一致問題,在一些業務場景下也還能接受。

  • 另一種解決方案是使用隊列輔助。先更新數據庫,再刪除緩存。如果刪除失敗,就放進隊列。然後另一個任務從隊列中取出消息,不斷去重試刪除相應的key。

  • 還有一種解決方案是對一個數據使用一個隊列,使讀寫操作串行化。比如對id爲n的數據建立一個隊列。對這條數據的寫操作,刪除緩存後,放進一個隊列;然後另一個線程過來了,發現沒有緩存,則把這個讀操作也放進這個隊列裏面。不過這樣會增加程序的複雜性,串行化也會降低程序的吞吐量,可能得不償失。一般主流的解決方案還是先刪除緩存,再更新數據庫。可以滿足絕大部分需求。

Redis相比Memcached有哪些優勢
  • 首先redis支持更加豐富的數據結構,而Memcached只有String類型
  • redis是支持RDB和AOF持久化的,而Memcached是不支持持久化的
  • redis支持集羣模式(Redis Cluster),而Memcached不支持集羣的
  • redis在2.6以後支持事務,而Memcached不支持事務
Redis爲什麼快
  • redis是完全基於內存的,就像HashMap一樣,時間複雜度是O(1)
  • Redis使用的是非阻塞I/O,I/O多路複用,使用了單線程來輪詢描述符,將數據庫的開、關、讀、寫都轉換成了事件,減少了線程切換時上下文的切換和競爭
  • redis採用了單線程的模型,保證了每個操作的原子性,也減少了線程的上下文切換和競爭
  • redis採用自己實現的事件分離器,效率比較高,內部採用非阻塞的執行方式,吞吐能力比較大
  • 最主要的redis定義了自己的數據結構,請看這篇文章
Redis哨兵

在這裏插入圖片描述
哨兵必須用三個實例去保證自己的健壯性,哨兵 + 主從 並不能保證數據不丟失 ,但是可以保證集羣的高可用。工作原理:

  • 每個 Sentinel 節點以每秒一次的頻率向它所知的主服務器、從服務器和其他 Sentinel 節點發送一個 PING 命令
  • 如果 一個實例舉例最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 所指定的值,那麼這個實例就會被標記爲主觀下線
  • 如果一個主服務器被標記爲主觀下線,那麼正在監視這個服務器的所有 Sentinel 節點會以每秒一次的頻率確認主服務器的確進入了主觀下線狀態
  • 如果有足夠數量的 Sentinel (至少要達到配置文件中指定的數量)在指定的時間範圍內同意這一判斷,那麼這個主服務器就會被標記爲客觀下線
  • 當主服務器被標記爲客觀下線後,Sentinel 節點會向下線主服務器的所有從服務器發送 INFO 命令的頻率從 10 秒一次改爲每秒一次
  • Sentinel 節點會協商客觀下線主服務器的狀態,如果處於 SDOWN 狀態,則投票自動選出新的主節點,將剩下從節點指向新的主節點進行數據複製。
  • 當沒有足夠數量的 Sentinel 節點泳衣主服務器下線是,主服務器的客觀下線狀態就會被移除。或者當主服務器重新向 Sentinel 的 PING 命令返回有效回覆是,主服務器的主觀下線狀態就會被移除
Redis集羣原理

Redis Sentinel(哨兵)着眼於高可用,在master 宕機時會自動將slave提升爲master,繼續提供服務。Redis Cluster(集羣)着眼於擴展性,在單個redis內存不足時,使用Cluster進行分片存儲。選主策略:

  • slave 的 priority 設置的越低,優先級越高
  • 同等情況下,slave 複製的數據越多優先級越高
  • 相同的條件下,runid 越小越容易被選中
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章