【redis正傳】redis淘汰+過時雙向保證高可用 | 單線程如何作到快速響應

這是我參與更文挑戰的第25天,活動詳情查看: 更文挑戰 redis爲啥單線程還能作到高響應?java

前言

  • 【redis前傳】持續更新!各類穿插更新!嘿嘿嘿

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

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

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

內存分配

  • 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內存被使用滿了後,任何的寫操做都會被拒絕!markdown

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

image-20210617163430236

volatile-lru【最近不多使用】

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

  • 首先咱們在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常見的災難場景,咱們下章節繼續分析如何產生的、而且如何進行修復進行展開討論。
相關文章
相關標籤/搜索