Redis 高併發帶來的一些問題

2022-06-24 22:09:17 字數 4350 閱讀 5798

前言

本文講述redis在遇到高併發時的一些問題。即遇到大量請求時需要思考的點,如快取穿透 快取擊穿 快取雪崩 熱key處理。一般中小型傳統軟體企業,很難碰到這個問題。如果有大併發的專案,流量有幾百萬左右。這些要深刻考慮。

簡單的講就是如果該資料原本就不存在,那麼就會發生快取穿透;如果快取內容因為各種原因失效,那麼就會發生快取擊穿。具體一點來說,如果快取中不存在需要查詢的內容,一般情況下需要再深入一層進行查詢,一般為不能承受壓力的關係型資料庫(承壓能力為快取的1%,甚至更低),如果資料庫中不存在,則叫做快取穿透;反之,如果資料庫中存在這個資料,則叫做快取擊穿。這種查詢在流量不高的情況下,不會出現問題,如果查詢資料庫的流量過高,尤其是資料庫中不存在的情況下,嚴重時會導致資料庫不可用,連帶影響使用資料庫的其他業務,本業務也有很大的可能性受到影響。

快取穿透指快取中資料不存在,然後大量請求查資料庫導致資料庫異常。快取沒有資料,資料庫也有資料,穿透兩層。

快取擊穿指快取中資料不存在,資料庫中有此資料,請求穿透一層。

解決方案如下

1.1.1 空值快取

既然該資料本身就不存在,最簡單粗暴的方式就是直接將不存在的值定義為空(視具體業務和快取的方式定義為null或者」」)。具體方式是每次查詢完資料庫,我們可以將key在快取中設定對應的值為空,短期內再次查詢這個key的時候就不用查詢資料庫了。

為了系統的最終一致性,這些key必須設定過期時間,或者必須存在更新方式,防止這個key的資料後期真實存在,但該key始終為空,導致資料不一致的情況出現。

缺點:如果key數量巨大且分散無任何規律,就會浪費大量快取空間,並且不能抗住瞬時流量衝擊(尤其是遇到惡意的攻擊的時候,有可能將快取空間打爆,影響範圍更大),需要額外配置降級開關(查詢資料庫的開關或者限流),這時本方案就顯得沒想象的那麼美好。針對不能抗住瞬時流量的情況,常見的處理方式是使用計數器,對不存在的key進行計數,當某個key在一定時間達到一定的量級,就查詢一次資料庫,按照資料庫的返回值對key進行快取。未達指定閾值數量之前,按照商定的空值返回。

應用場景:key全集資料資料量級較小,並且完全可**,可以通過提前填充的方式直接將資料快取。

1.1.2 布隆過濾器(bloomfilter)

提供乙個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的key。迅速判斷出,請求所攜帶的key是否合法有效。如果不合法,則直接返回。

實際應用中,google bigtable,apache hbbase 和 apache cassandra 使用布隆過濾器減少對不存在的行和列的查詢。

高效的hash演算法:建議的演算法包括murmurhash、fnv的穩定高效的演算法。

解決方案如下:

1.2.1 利用互斥鎖

利用互斥鎖,快取失效的時候,先去獲得鎖,得到鎖了,再去請求資料庫。沒得到鎖,則休眠一段時間重試

這是比較常見的做法,是在快取失效的時候,不是立即去查詢資料庫,先搶互斥鎖(比如redis的setnx乙個mutex key),當操作返回成功時(即獲取到互斥鎖),再進行查詢資料庫的操作並回設快取;否則,就重試整個獲取快取的方法或者直接返回空。

1.2.3 非同步構建快取

採用非同步更新策略,無論key是否取到值,都直接返回。value值中維護乙個快取失效時間,快取如果過期,非同步起乙個執行緒去讀資料庫,更新快取。需要做快取預熱(專案啟動前,先載入快取)操作。

當快取失效時,不是立刻去查詢資料庫,而是先建立快取更新的非同步任務,然後直接返回空值。這種做法不會阻塞當前執行緒,並且對於資料庫的壓力基本可控,但犧牲了整體資料的一致性。從實際的使用看,這種方法對於效能非常友好,唯一不足的就是構建快取時候,所有查詢返回的內容均為空值,但是對於一致性要求不高的網際網路功能來說這個還是可以忍受。

快取雪崩,即快取同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到資料庫上,從而導致資料庫連線異常。

(一)給快取的失效時間,加上乙個隨機值,避免集體失效,但是不能徹底規避。

(二)使用互斥鎖,但是該方案吞吐量明顯下降了。

(三)雙快取。我們有兩個快取,快取a和快取b。快取a的失效時間為20分鐘,快取b不設失效時間。自己做快取預熱操作。然後細分以下幾個小點

熱key問題說來也很簡單,就是瞬間有幾十萬的請求去訪問redis上某個固定的key,從而壓垮快取服務的情情況。

方法一:憑藉業務經驗,進行預估哪些是熱key

其實這個方法還是挺有可行性的。比如某商品在做秒殺,那這個商品的key就可以判斷出是熱key。缺點很明顯,並非所有業務都能預估出哪些key是熱key。

方法二:在客戶端進行收集

這個方式就是在操作redis之前,加入一行**進行資料統計。那麼這個資料統計的方式有很多種,也可以是給外部的通訊系統傳送乙個通知資訊。缺點就是對客戶端**造成入侵。

方法三:在proxy層做收集

有些集群架構是下面這樣的,proxy可以是twemproxy,是統一的入口。可以在proxy層做收集上報,但是缺點很明顯,並非所有的redis集群架構都有proxy。

graph lr

clinet-->proxy

proxy-->redis1

proxy-->redis2

proxy-->redis3

方法四:用redis自帶命令

(1)monitor命令,該命令可以實時抓取出redis伺服器接收到的命令,然後寫**統計出熱key是啥。當然,也有現成的分析工具可以給你使用,比如redis-faina。但是該命令在高併發的條件下,有記憶體增暴增的隱患,還會降低redis的效能。

(2)hotkeys引數,redis 4.0.3提供了redis-cli的熱點key發現功能,執行redis-cli時加上–hotkeys選項即可。但是該引數在執行的時候,如果key比較多,執行起來比較慢。

方法五:自己抓包評估

redis客戶端使用tcp協議與服務端進行互動,通訊協議採用的是resp。自己寫程式監聽埠,按照resp協議規則解析資料,進行分析。缺點就是開發成本高,維護困難,有丟包可能性。

以上五種方案,各有優缺點。根據自己業務場景進行抉擇即可。那麼發現熱key後,如何解決呢?

(1) 利用二級快取

比如利用ehcache,或者乙個hashmap都可以。在你發現熱key以後,把熱key載入到系統的jvm中。

針對這種熱key請求,會直接從jvm中取,而不會走到redis層。

假設此時有十萬個針對同乙個key的請求過來,如果沒有本地快取,這十萬個請求就直接懟到同一臺redis上了。

現在假設,你的應用層有50臺機器,ok,你也有jvm快取了。這十萬個請求平均分散開來,每個機器有2000個請求,會從jvm中取到value值,然後返回資料。避免了十萬個請求懟到同一臺redis上的情形。

(2) 備份熱key

這個方案主要防止熱key放在一台redis伺服器中,把熱key服務到集群中的多台伺服器中。根據redis集群數量構建乙個新的key,判斷這個key不存在後,把熱key資料複製到這個新key上,這個新key也冗餘到了其他redis集群中,下次取的時候計算方式是新key方式,取到了。

有辦法在專案執行過程中,自動發現熱key,然後程式自動處理麼?

(1)監控熱key

(2)通知系統做處理

監控熱key的方式上面有說到,監控服務監控到熱key後通過手段(如zk)通知各個業務系統快取熱key。如下圖所示

解決方案:

(1)如果對這個key操作,不要求順序

這種情況下,準備乙個分布式鎖,大家去搶鎖,搶到鎖就做set操作即可,比較簡單。

(2)如果對這個key操作,要求順序

假設有乙個key1,系統a需要將key1設定為valuea,系統b需要將key1設定為valueb,系統c需要將key1設定為valuec.

期望按照key1的value值按照 valuea-->valueb-->valuec的順序變化。這種時候我們在資料寫入資料庫的時候,需要儲存乙個時間戳。假設時間戳如下

系統a key 1 

系統b key 1

系統c key 1

那麼,假設這會系統b先搶到鎖,將key1設定為。接下來系統a搶到鎖,發現自己的valuea的時間戳早於快取中的時間戳,那就不做set操作了。加版本號的方式。以此類推。

其他方法,比如利用佇列,將set方法變成序列訪問也可以。總之,靈活變通。

references

事務併發帶來的問題

髒讀 事務a對某個資料進行修改,但是還沒提交到資料庫中 此時事務b對這個資料進行訪問,我們把b事務訪問到的這個未提交的資料稱為 髒資料 事務b的這種行為稱為 髒讀 丟失修改 事務a和事務b同時對某乙個資料進行訪問,且此時a修改了資料如a a 1,b同時也對a進行修改,a a 1,此時a的修改結果將會...

redis的一些問題

一 redis的併發競爭問題如何解決?redis為單程序單執行緒模式,採用佇列模式將併發訪問變為序列訪問。redis本身沒有鎖的概念,redis對於多個客戶端連線並不存在競爭,但是在jedis客戶端對redis進行併發訪問時會發生連線超時 資料轉換錯誤 阻塞 客戶端關閉連線等問題,這些問題均是由於客...

class field所帶來的一些問題

public field 和private field組成了class field,這篇文章不講private field 只談public field所帶來的問題 我會根據例子來講解,這樣會更好理解一些 class safeuser 上面 非常簡單,就是宣告了乙個safeuser類,接下來我們根據...