前言
redis
和資料相比除了他們的結構型顛覆以外!還有他們儲存位置也是不相同。傳統資料庫將資料儲存在硬碟上每次資料操作都需要IO
而Redis
是將資料儲存在記憶體上的。這裡稍微解釋下IO是啥意思。IO就是輸入流輸出流方式將資料在硬碟和記憶體之間進行互動!而redis
直接在記憶體上就剩下了IO
操作。這也是redis
快的原因之一吧
- 記憶體相對於硬碟來說很寶貴。我們平時的電腦也是硬碟是記憶體的幾百倍。既然記憶體很寶貴而
redis
又將資料儲存在記憶體上那麼redis
肯定不能肆無忌憚的進行儲存 。這就需要redis
和開發者們作出相應的優化 - 首先
redis
在配置檔案(redis.conf
)中通過maxmemory
引數指定redis
設定整個對記憶體的支配大小! - 其次就是要求我們開發者在想
redis
中填值的時候根據自己的需求設定相應的key過期時間。這樣不必要的資料就會被redis
過期驅逐策略清楚。從而節省記憶體供別人使用
本文凌駕於redis
基礎之上,這裡筆者預設大家都已經安裝了redis
. 並實際使用過redis
記憶體分配
maxmemory
指定大小。在redis.conf
配置檔案中可以直接指定。他的單位時byte。
- 上圖中註釋部分是給大家的解釋,實際中#配置需要換行哈!!!友情提示
- 另外我們可以連線上
redis
通過config
命令來設定
- 兩種方式都可以設定,前者是全域性設定重啟之後仍然有效!後者是臨時設定重啟之後就會重新載入
redis.conf
中的配置。
鍵過期
- 上面我們從
redis
本身角度出發設定了記憶體限制,這樣不用擔心他們吞噬系統記憶體!下面就需要我們開發者設計角度約束自己了。
設定過期時間
expire key time
-
設定過期時間預設單位時S 。
-
然後通過ttl 命令可以檢視剩餘過期時間
- 經過多次執行
ttl
能夠觀察到剩餘時間在不斷的減少!當減少到0的時候就被給驅逐策略驅逐!注意這裡說的是驅逐策略驅逐並不是意味著立馬被刪除
更新過期時間
del key
直接將key刪除了那麼該key對應的過期自然也就不存在了!這種情況筆者也算作是更新過期時間set
getset
等命令重新設定key、value方式會覆蓋過期時間 , 直接被覆蓋成-1
set
、getset
包括del
嚴格意義是覆蓋過期時間。真正做到更新過期時間的還是expire .在expire是已最新為準的!- 上面其實都修改了key才會應發原本的過期時間失效的!因為此key非彼key 。 但是
append
、incr
等命令是改變值這種命令是不會影響到原來的過期設定的
淘汰策略
- 根據上面配置我們可以將我們的
redis
最大記憶體設定為1MB
, 設定大小隨便最好能小點。然後我們通過Java
小程式不斷向redis
中填充。最終當記憶體不夠使用時就會報錯
- 報錯就是因為記憶體滿了,新增的key被
redis
拒絕了!不僅僅是新增的被拒絕,就算此時我們想改變已經在redis
中的key的值也是不可用的
public static void main(String[] args) {
Jedis jedis = new Jedis("39.102.60.114", 6379);
jedis.auth("Qq025025");
Integer index = 1;
while (true) {
String uuid = UUID.randomUUID().toString();
jedis.set(index.toString(), uuid);
System.out.println(index++);
}
}
-
不管是append 還是set 都是報
OOM command not allowed when used memory > maxmemory
。程式碼中列印和redis
鍵個數一致;說明我們預設的淘汰策略是直接拒絕 -
總結下來就是:當
redis
記憶體被使用滿了後,任何的寫操作都會被拒絕! -
當沒有足夠記憶體時難道就這麼直接拒絕嗎?上面也提到了需要我們程式設計師自己根據需求設定鍵過期已釋放記憶體供其他有需要的key使用!那麼設定了過期key之後這些key又是怎麼被清除的呢? 這就牽扯出我們的淘汰策略
volatile-lru【最近很少使用】
當記憶體告警時
redis
會將近期很少使用且設定了過期時間的key剔除出去,即使該key還沒有到過期時間。如果沒有符合的key也就是執行之後記憶體仍然不足時將會和預設淘汰策略noeviction
丟擲一樣的錯誤OOM command not allowed when used memory > maxmemory
- 首先我們在
redis.conf
中配置我們最近很少使用策略.maxmemory-policy volatile-lru
。 然後重啟我們的redis
服務 。重啟之後flushall
清空所有資料,我們在通過上面的Java
程式重新生成下資料!
- Java程式中我們設定前100個key新增過期時間
public static void main(String[] args) {
Jedis jedis = new Jedis("39.102.60.114", 6379);
jedis.auth("Qq025025");
Integer index = 1;
while (true) {
String uuid = UUID.randomUUID().toString();
if (index < 100) {
jedis.setex(index.toString(),360, uuid);
} else {
jedis.set(index.toString(), uuid);
}
System.out.println(index++);
}
}
- 簡單分析下為什麼程式計數器大於
redis
庫中的key數量!就是因為我們為前100設定了過期時間。當記憶體不足時redis
就會將當前設定了過期時間的key中最近最少使用的key進行剔除!所以我們計數器會大於鍵數量。因為有部分鍵被清除了!我們獲取前100的key都是null , 說明被刪除了! 那麼為什麼本次計數器不是比上次多100 。 那是因為我們每次儲存進來的是uuid, 所佔長度都不是固定的。還有本身淘汰策略也是佔用記憶體的
策略總結
- 上面演示了最近最少使用的淘汰策略!除此之外還有其他的策略
noeviction:拒絕寫請求,正常提供讀請求,這樣可以保證已有資料不會丟失(預設策略);
2. volatile-lru:嘗試淘汰設定了過期時間的key,雖少使用的key被淘汰,沒有設定過期時間的key不會淘汰;
3. volatile-ttl:跟volatile-lru幾乎一樣,但是他是使用的key的ttl值進行比較,最先淘汰ttl最小的key;
4. volatile-random:其他同上,唯一就是他通過很隨意的方式隨機選擇淘汰key集合中的key;
5. allkeys-lru:區別於volatile-lru的地方就是淘汰目標是全部key,沒設定過期時間的key也不能倖免;
6. allkeys-random:這種方式同上,隨機的淘汰所有的key。
- 使用哪種淘汰策略需要我們結合自己的專案場景來配合使用!!!
過期刪除
- 上面我們從【鍵過期】、【淘汰策略】兩個角度分析了
redis
。 僅僅這兩方面還沒有完全高效使用記憶體!淘汰策略是瀕臨記憶體不足時觸發。那麼當設定了過期時間的鍵真正到了過期時間而此時記憶體尚夠使用?這種場景是不是需要將過期鍵刪除呢? - 因為
redis
是單執行緒,那麼在鍵過期的時候如何不影響對外服務的同時清除過期鍵呢?答案是【不行】。嚴格意義是無法解決的因為單執行緒同時只能做一件事!既然無法解決那麼我們可以達到一種協調狀態!如果同一時刻出現一個過期鍵那麼清除鍵很快這時候阻塞外部服務的時間很短可能毫秒級設定納秒級! - 但是如何同一時間發生上萬鍵過期,如果想要刪除上萬鍵那肯定需要花費一定時間這時候就會阻塞對外服務!這肯定是不能接受的,阻塞時間過長會導致客戶端連線超時報錯的。這在併發場景下更是無法接受的!所以
redis
如何應對同一時間過多資料過期的場景,他的刪除過期鍵的方法略有不同!
定時清除
- 針對每個過期鍵設定一個定時器,在過期時就會進行清理該鍵!
- 該做法能夠做到資料實時被清理從而保證記憶體不會被長期佔用!提高了記憶體的使用率!
- 但是問題也隨之而來,每一個
key
需要設定一個定時器進行跟蹤。redis
這裡筆者猜測應該是啟用另外執行緒來進行定時跟蹤!這裡有清除的還請幫忙解答下? - 當同一時間過期
key
很多的時候!我們的CPU就需要不斷的執行這些定時器從而導致CPU資源緊張。最終會影響到redis
服務的效能
定期清除
- 定期刪除就是上面我們圖示效果,
redis
會每隔100ms
執行一次定時器,定時器的任務就是隨機抽取20個設定過期的key
。 判斷是否進行清除。上面圖示中說明中寫錯了不是10S , 而是每隔100ms
。請原諒我的粗心!!!
- 定期刪除和定時刪除作用是相反的!定期刪除是將key集中進行處理同時為了保證服務的高可用在處理時加入的時間限制。每次執行總時長不能超過
25ms
。 也就是說對於客戶端來說服務端的延遲不會超過25ms
。 - 他的優點就是不需要CPU頻繁的進行操作key清除!因為他是定期進行清除所以就會導致一部分資料沒有來得及清除從而導致記憶體使用上會被一直佔用!
惰性清除
- 關於惰性刪除我們在平時開發中也經常使用這種方式!當資料過期時
redis
並不急著去清除這些資料,而是等到該key被再次請求時進行刪除!這樣在最終效果上是沒有問題的。 - 優點和定期清除一樣他保證了CPU不必頻繁的進行切換!但是缺點也很明顯會導致很多已經過期的key任然在
redis
中。
惰性清除+定期清除
- 我們開頭說過了既要高可用又要實時清理過期key 這是無法做到的!既然無法做到我們就需要在CPU和記憶體中間做一個權衡!
redis
內部是使用惰性清除和定期清除兩種方式結合使用,最終保重CPU和記憶體之間的一種平衡!
總結
- 相信大家對上面三個概念有點模糊了。【鍵過期】、【淘汰策略】、【過期清除】
- 首先【鍵過期】是
redis
給我們開發者提供的功能。我們可以根據自己的業務需求合理的設定鍵的過期時間,從而保證記憶體的高可用 - 其次【過期清除】在我們之前設定的過期的key如何進行合理的清除,並不能一股腦一下子進行清除因為資料過大會導致服務的卡頓。這個時候我們需要通過定期清除減緩清除key程式碼的卡頓。在
redis.conf
中我們可以設定hz 10
代表1S
中平均執行幾次這也是我們上面所說的100MS
的由來。但是僅僅定期刪除會產生遺漏資料所以我們還需要加上惰性清除,最終保證對客戶端來說資料是準確實時清除的。 - 那麼關於【淘汰策略】又是啥呢?在上面過期清除是如果使用者一直不請求過期的key ,並且隨著業務產生越來越多的過期key . 這時候
redis
服務中還會堆積很多過期的無效key 。這個時候如果記憶體不夠用了的話那又該怎麼辦呢?這時候我們需要設定淘汰策略比如果volatile-lru
. 就會將最近最少使用的設定過期key進行清除從而保證儘可能的接收更多的有效資料! - 這就是為什麼會設計三者的原因!好好理解上面三個主題我們再去想想為什麼會發生【快取雪崩】、【快取奔潰】、【快取擊穿】就好理解一點了呢?
- 關於上述的
redis
常見的災難場景,我們下章節繼續分析如何產生的、並且如何進行修復進行展開討論。