最近有朋友去位元組面試,面試前後進行了20天左右,包含4輪電話面試、1輪筆試、1輪主管視訊面試、1輪HR視訊面試。
據他所說,80%的人都會栽在第一輪面試,要不是他面試前做足準備,估計都堅持不完後面幾輪面試。
其實,第一輪的電話面試除了一些正常的自我介紹外,問的都是一些基礎的專業知識,主要目的就是篩選掉一些基礎比較弱的人,以免浪費大家時間。問的問題主要比較深奧的問題。涉及Redis緩存方面的問題也比較多,應該是重視程式開發的性能問題,今天就針對Redis緩存的問題總結如下:
1、什麼是緩存擊穿?該如何解決
緩存擊穿是指一個熱點的Key在某個瞬間過期失效了,持續的并發請求在緩存擷取不到資料後直接請求資料庫的現象。
1.1、如何解決
使用互斥鎖
在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先去設定一個互斥鎖(比如Redis的SETNX),當擷取到鎖再進行load db的操作并回設緩存;否則就重試擷取緩存的方法。
僞代碼如下圖:
1.2、永遠不過期
- 不設定過期時間,就保證了不會出現熱點key過期問題,也就是“實體”不過期。
- 我們把過期時間存在key對應的value裡,如果發現要過期了,通過一個背景的異步線程進行緩存的建構。
2、什麼是緩存穿透?該如何解決
緩存穿透是指查詢一個根本不存在的資料,緩存層和存儲層都不會命中,每次請求都要到存儲層去查詢,失去了緩存保護後端存儲的意義。
造成緩存穿透的基本原因有兩個
- 自身業務代碼或者資料出現問題。
- 一些惡意攻擊、爬蟲等造成大量空命中。
如何解決
緩存空對象
當存儲層不命中,到資料庫查發現也沒有命中,那麼設定空值到緩存層中。不過空值做了緩存,意味着緩存層中存了更多的鍵,需要更多的記憶體空間,比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。
布隆過濾器攔截
在通路緩存層和存儲層之前,将存在的key用布隆過濾器提前儲存起來,做第一層攔截。如果布隆過濾器認為該使用者id不存在,那麼就不會通路存儲層,在一定程度保護了存儲層。
這種方法适用于資料命中不高、資料相對固定、實時性低(通常是資料集較大)的應用場景,代碼維護較為複雜,但是緩存空間占用少。
3、什麼是緩存雪崩?該如何解決
緩存雪崩的英文原意是stampeding herd(奔逃的野牛),指的是緩存層宕掉後,流量會像奔逃的野牛一樣,打向存儲。
緩存層由于某些原因不能提供服務,比如同一時間緩存資料大面積失效,所有的請求都會達到存儲層,存儲層的調用量會暴增,造成級聯當機的情況。
如何解決
- 保證緩存層服務高可用性。
- 依賴隔離元件為後端限流并降級。
- 将緩存失效時間分散開,降低緩存過期時間的重複率。
4、什麼是BigKey?該如何解決
bigkey是指key對應的value所占的記憶體空間比較大,例如一個字元串類型的value可以最大存到512MB,一個清單類型的value最多可以存儲23-1個元素。
bigkey的危害
- 記憶體空間不均勻:在Redis Cluster中,bigkey 會造成節點的記憶體空間使用不均勻。
- 逾時阻塞:由于Redis單線程的特性,操作bigkey比較耗時,也就意味着阻塞Redis可能性增大。
- 網絡擁塞:每次擷取bigkey産生的網絡流量較大
假設一個bigkey為1MB,每秒通路量為1000,那麼每秒産生1000MB 的流量,對于普通的千兆網卡(按照位元組算是128MB/s)的伺服器來說簡直是滅頂之災。
發現bigkey
redis-cli --bigkeys可以指令統計bigkey的分布。
但是在生産環境中,開發和運維人員更希望自己可以定義bigkey的大小,而且更希望找到真正的bigkey都有哪些,這樣才可以去定位、解決、優化問題。
判斷一個key是否為bigkey,隻需要執行debug object key檢視serializedlength屬性即可,它表示 key對應的value序列化之後的位元組數。
解決bigkey
主要思路為拆分,對 big key 存儲的資料 (big value)進行拆分,變成value1,value2… valueN等等。
例如big value 是個大list,可以拆成将list拆成。= list_1, list_2, list3, …listN
5、redis過期政策都有哪些?
當 Redis 記憶體超出實體記憶體限制時,記憶體的資料會開始和磁盤産生頻繁的交換 (swap)。交換會讓 Redis 的性能急劇下降,對于通路量比較頻繁的 Redis 來說,這樣龜速的存取效率基本上等于不可用。
在生産環境中我們是不允許 Redis 出現交換行為的,為了限制最大使用記憶體,Redis 提供了配置參數 maxmemory 來限制記憶體超出期望大小。
當實際記憶體超出 maxmemory 時,Redis 提供了幾種可選政策(maxmemory-policy) 來讓使用者自己決定該如何騰出新的空間以繼續提供讀寫服務。
noeviction : 不會繼續服務寫請求,DEL請求可以繼續服務,讀請求可以繼續進行。這樣可以保證不會丢失資料,但是會讓線上的業務不能持續進行。這是預設的淘汰政策。
volatile-lru : 淘汰設定了過期時間的key,最少使用的key優先被淘汰。
volatile-ttl : 淘汰設定了過期時間的key,過期時間最接近的key優先被淘汰。
volatile-random : 淘汰設定了過期時間的key,随機選擇一個key。
allkeys-lru : 所有的 key 中,最少使用的key優先被淘汰。
allkeys-random :所有的 key 中,淘汰随機的 key。
6、講一講Redis緩存的資料一緻性問題和處理方案
隻要使用到緩存,無論是本地記憶體做緩存還是使用 redis 做緩存,那麼就會存在資料同步的問題。通常有以下幾種同步方法:
6.1、先更新緩存,再更新資料庫
這個方案我們一般不考慮。這種方案如果不處理好,比如更新資料庫失敗之後沒有復原緩存,就會拿到錯誤的值。
6.2、先更新資料庫,再更新緩存
這個方案也我們一般不考慮,原因跟第一個一樣,資料庫更新成功了,緩存更新失敗,同樣會出現資料不一緻問題。同時還有以下問題:
并發問題:同時有請求A和請求B進行更新操作,那麼可能會出現:
(1)線程A更新了資料庫
(2) 線程B更新了資料庫
(3) 線程B更新了緩存
(4) 線程A更新了緩存
這樣會因為緩存更新順序問題造成髒資料的産生。
業務場景問題:如果是一個寫資料庫場景比較多,而讀資料場景比較少的業務需求,采用這種方案就會導緻:資料壓根還沒讀到,緩存就被頻繁的更新,浪費性能。
6.3、先删除緩存,後更新資料庫
該方案也會出問題,具體出現的原因如下。
- 此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)
- 請求 A 會先删除 Redis 中的資料,然後去資料庫進行更新操作
- 此時請求 B 看到 Redis 中的資料時空的,會去資料庫中查詢該值,但是此時請求 A 并沒有更新成功,得到舊值補錄到 Redis 中
利用延時雙删政策解決這一問題,是在更新資料庫後睡眠一會,将B寫的髒資料再次删除,僞代碼如下:
redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
6.4、先更新資料庫,後删除緩存
這種方式,被稱為Cache Aside Pattern,讀的時候,先讀緩存,緩存沒有的話,就讀資料庫,然後取出資料後放入緩存,同時傳回響應。更新的時候,先更新資料庫,然後再删除緩存。
一般情況下我們可能會先用先更新DB,後删除緩存的操作。因為這種情況下緩存不一緻性的情況隻有可能是查詢比删除慢的情況,而這種情況相對來說會少很多。