redis淘汰+過期雙向保證高可用 | redis 爲什麼那麼快?

前言

redis和數據相比除了他們的結構型顛覆以外!還有他們存儲位置也是不相同。傳統數據庫將數據存儲在硬盤上每次數據操作都需要IORedis是將數據存儲在內存上的。這裏稍微解釋下IO是啥意思。IO就是輸入流輸出流方式將數據在硬盤和內存之間進行交互!而redis直接在內存上就剩下了IO操作。這也是redis快的原因之一吧

  • 內存相對於硬盤來說很寶貴。我們平時的電腦也是硬盤是內存的幾百倍。既然內存很寶貴而redis又將數據存儲在內存上那麼redis肯定不能肆無忌憚的進行存儲 。這就需要redis和開發者們作出相應的優化
  • 首先redis在配置文件(redis.conf)中通過maxmemory參數指定redis 設置整個對內存的支配大小!
  • 其次就是要求我們開發者在想redis中填值的時候根據自己的需求設置相應的key過期時間。這樣不必要的數據就會被redis過期驅逐策略清楚。從而節省內存供別人使用

本文凌駕於redis基礎之上,這裏筆者默認大家都已經安裝了redis . 並實際使用過redis

內存分配

  • maxmemory 指定大小。在redis.conf配置文件中可以直接指定。他的單位時byte。

image-20210617144838455

  • 上圖中註釋部分是給大家的解釋,實際中#配置需要換行哈!!!友情提示
  • 另外我們可以連接上redis通過config命令來設置

image-20210617145851812

  • 兩種方式都可以設置,前者是全局設置重啓之後仍然有效!後者是臨時設置重啓之後就會重新加載redis.conf中的配置。

鍵過期

  • 上面我們從redis本身角度出發設置了內存限制,這樣不用擔心他們吞噬系統內存!下面就需要我們開發者設計角度約束自己了。

設置過期時間

expire key time
  • 設置過期時間默認單位時S 。

  • 然後通過ttl 命令可以查看剩餘過期時間

image-20210617151445320

  • 經過多次執行ttl能夠觀察到剩餘時間在不斷的減少!當減少到0的時候就被給驅逐策略驅逐!注意這裏說的是驅逐策略驅逐並不是意味着立馬被刪除

更新過期時間

  • del key 直接將key刪除了那麼該key對應的過期自然也就不存在了!這種情況筆者也算作是更新過期時間
  • set getset等命令重新設置key、value方式會覆蓋過期時間 , 直接被覆蓋成-1

image-20210617152121178

  • setgetset包括del嚴格意義是覆蓋過期時間。真正做到更新過期時間的還是expire .在expire是已最新爲準的!
  • 上面其實都修改了key纔會應發原本的過期時間失效的!因爲此key非彼key 。 但是appendincr 等命令是改變值這種命令是不會影響到原來的過期設置的

image-20210617152731013

淘汰策略

  • 根據上面配置我們可以將我們的redis最大內存設置爲1MB , 設置大小隨便最好能小點。然後我們通過Java小程序不斷向redis中填充。最終當內存不夠使用時就會報錯

image-20210617161613440

  • 報錯就是因爲內存滿了,新增的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++);
    }
}

image-20210617162804867

  • 不管是append 還是set 都是報OOM command not allowed when used memory > maxmemory 。代碼中打印和redis鍵個數一致;說明我們默認的淘汰策略是直接拒絕

  • 總結下來就是:當redis內存被使用滿了後,任何的寫操作都會被拒絕!

  • 當沒有足夠內存時難道就這麼直接拒絕嗎?上面也提到了需要我們程序員自己根據需求設置鍵過期已釋放內存供其他有需要的key使用!那麼設置了過期key之後這些key又是怎麼被清除的呢? 這就牽扯出我們的淘汰策略

image-20210617163430236

volatile-lru【最近很少使用】

當內存告警時redis會將近期很少使用且設置了過期時間的key剔除出去,即使該key還沒有到過期時間。如果沒有符合的key也就是執行之後內存仍然不足時將會和默認淘汰策略noeviction拋出一樣的錯誤OOM command not allowed when used memory > maxmemory

  • 首先我們在redis.conf中配置我們最近很少使用策略. maxmemory-policy volatile-lru 。 然後重啓我們的redis服務 。重啓之後flushall清空所有數據,我們在通過上面的Java程序重新生成下數據!

image-20210617165407096

  • 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++);
    }
}

image-20210617171334220

  • 簡單分析下爲什麼程序計數器大於redis庫中的key數量!就是因爲我們爲前100設置了過期時間。當內存不足時redis就會將當前設置了過期時間的key中最近最少使用的key進行剔除!所以我們計數器會大於鍵數量。因爲有部分鍵被清除了!我們獲取前100的key都是null , 說明被刪除了! 那麼爲什麼本次計數器不是比上次多100 。 那是因爲我們每次存儲進來的是uuid, 所佔長度都不是固定的。還有本身淘汰策略也是佔用內存的

image-20210617173746769

策略總結

  • 上面演示了最近最少使用的淘汰策略!除此之外還有其他的策略
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服務的性能

定期清除

image-20210618101552195

  • 定期刪除就是上面我們圖示效果,redis會每隔100ms執行一次定時器,定時器的任務就是隨機抽取20個設置過期的key 。 判斷是否進行清除。上面圖示中說明中寫錯了不是10S , 而是每隔100ms 。請原諒我的粗心!!!

image-20210618102943955

  • 定期刪除和定時刪除作用是相反的!定期刪除是將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常見的災難場景,我們下章節繼續分析如何產生的、並且如何進行修復進行展開討論。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章