整理下部分基礎的Redis面試題


標籤:引數錯誤   cto   因此   sid   pattern   總結   二進位制   轉發   rand   

- Redis

Redis資料型別

  • String,一般常用在需要計數的場景,比如:使用者的訪問次數、點贊、轉發數量。
  • Hash,類似於JDK1.8之前的HashMap(陣列 + 連結串列)的實現,用來儲存物件資訊。
  • List,實現為一個雙向連結串列。常用在布與訂閱或者說訊息佇列、慢查詢。
  • Set,類似於Java中的HashSet,無序不可重複。可實現共同關注、共同喜好等求交集的需求上。
  • Zset,在Set基礎上加了引數 score,可以讓元素按照 score 排序。適合實現使用者列表、排行榜、彈幕。

Redis單執行緒模型

Redis4.0之前一直都是單執行緒模式,4.0之後才開始有多執行緒的概念,但預設還是單執行緒模式。

Redis 基於Reactor 模式設計開發了自己的一套高效的事件處理模型,這套事件處理模型在Redis中對應的是檔案事件處理器 (file event handler)

由於檔案事件處理器是單執行緒方式執行的,所以我們一般都說 Redis 是單執行緒模型。

Redis如何監聽大量的客戶端連線?

通過IO 多路複用程式 監聽來自客戶端的大量連線(或者說是監聽多個 socket)。Redis 不需要額外建立多餘的執行緒來監聽客戶端的大量連線,降低了資源的消耗(和 NIO 中的Selector元件很像)。

Redis的檔案事件處理器通過IO多路複用程式監聽多個套接字socket,並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。當被監聽的套接字準備好執行連線應答(accept)、讀取(read)、寫入(write)、關 閉(close)等操作時,與操作相對應的檔案事件就會產生,這時處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。

檔案事件處理器主要是包含 4 個部分:

  • 多個 socket(客戶端連線)
  • IO 多路複用程式(支援多個客戶端連線的關鍵)
  • 檔案事件分派器(將 socket 關聯到相應的事件處理器)
  • 事件處理器(連線應答處理器、命令請求處理器、命令回覆處理器)

Redis6.0的多執行緒

Redis6.0 引入多執行緒主要是為了提高網路 IO 讀寫效能

Redis 的多執行緒只是在網路資料的讀寫這類耗時操作上使用了, 執行命令仍然是單執行緒順序執行。因此,你也不需要擔心執行緒安全問題。

Redis6.0 的多執行緒預設是禁用的,只使用主執行緒。如需開啟需要修改配置檔案:redis.conf
io-threads-do-reads yes
還需要設定執行緒數,否則不會生效:io-threads 4 
//官網建議4核的機器建議設定為2或3個執行緒,8核的建議設定為6個執行緒

Redis是如何判斷過期資料的?

expire 關鍵字設定過期時間,可以釋放記憶體,防止OOM

Redis 通過一個叫做過期字典(類似 hash 表)來儲存資料過期的時間。過期字典的鍵指向 Redis 資料庫中的某個key(鍵),過期字典的值是一個 longlong 型別的64位整數,這個整數儲存了 key 所指向的 Redis 鍵的過期時間。

過期資料的刪除策略

  • 惰性刪除

    只會在取出 key 時才對資料進行過期檢查。這樣對 CPU 最友好,但可能會造成太多過期 的key 沒有被刪除。

  • 定期刪除

    每隔一段時間抽取一批 key 執行刪除過期 key 的操作。並且,Redis 底層會通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響。

Redis 採用的是 定期刪除 + 惰性(懶漢式) 刪除

但是,僅僅通過給 key 設定過期時間還是有問題的。因為還是可能存在漏掉了很多過期 key 的情況。這樣就導致大量過期 key 堆積在記憶體裡,然後就 Out of memory 了。

怎麼解決這個問題呢?答案就是: Redis 記憶體淘汰機制

Redis記憶體淘汰機制

MySQL 裡有 2000w 資料,Redis 中只存 20w 的資料,如何保證 Redis 中的資料都是熱點資料?

Redis 提供 6 種資料淘汰策略:

  1. volatile-lru(Least Recently Used 最近最少使用):從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰。
  2. volatile-ttl(Time To Live 生存時間):從已設定過期時間的資料集中挑選將要過期的資料淘汰。
  3. volatile-random:從已設定過期時間的資料集中任意選擇資料淘汰。
  4. allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)
  5. allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰。
  6. no-eviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯。幾乎不用。

4.0 版本後增加以下兩種:

  1. volatile-lfu(least frequently used):從已設定過期時間的資料集中挑選最不經常使用的資料淘汰。
  2. allkeys-lfu(least frequently used):當記憶體不足以容納新寫入資料時,在鍵空間中,移除最不經常使用的 key

Redis持久化:RDB 和 AOF

為什麼持久化到硬碟?如果發生了系統故障,可以利用備份恢復資料,減少了重建快取的開銷。

Redis支援兩種持久化方式:快照持久化 RDB只追加檔案 AOF

RDB

Redis 可以通過建立快照,得到儲存在記憶體裡的資料在某個時間點上的副本。

fork一個子程序,遍歷記憶體中資料,以二進位制的格式,把資料一條一條放在一起,生成了一個RDB檔案。

當需要備份的資料量大,只有讀取操作,而很少寫操作時,Redis提供了配置引數,支援週期性備份,在redis.conf 中通過以下引數控制:

save 900 1		#在900秒(15分鐘)之後,如果至少有1個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。
save 300 10		#在300秒(5分鐘)之後,如果至少有10個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。
save 60 10000	#在60秒(1分鐘)之後,如果至少有10000個key發生變化,Redis就會自動觸發BGSAVE命令建立快照。

AOF

Redis與MySQL都是基於命令式的,AOF持久化記錄所有寫操作的命令,通過重新執行這些命令來還原資料集。AOF 檔案中的命令全部以 Redis 協議規範來儲存,新命令會被追加到檔案的末尾。AOF預設關閉,通過配置檔案中的 appendonly yes 開啟。預設檔名為 appendonly.aof

Redis肯定不能每執行一條寫入命令就記錄到檔案中,所以建立了一個緩衝區,然後把要記錄的命令先臨時儲存在緩衝區中,這個緩衝區就是 aof_buf,由於作業系統也有一個緩衝區,為了將命令直接從 aof_buf 寫入檔案中,Redis提供了一個引數,主動控制從快取重新整理到檔案中。

appendfsync always    #每次有資料修改發生時都會寫入AOF檔案,這樣會嚴重降低Redis的速度
appendfsync everysec  #每秒鐘同步一次,顯示地將多個寫命令同步到硬碟
appendfsync no        #讓作業系統決定何時進行同步

AOF重寫

隨著不斷的追加寫操作的命令,aof檔案越來越大,所以有必要進行壓縮,這個過程稱之為AOF重寫。

fork出一個子程序去重寫.aof 檔案,同時建立一個重寫緩衝區,存放開始重寫之後的命令。等到子程序重寫.aof 檔案結束後,再將重寫緩衝區的命令追加到新的.aof 檔案中。最後再重新命名新的.aof檔案,替換掉原來的那個臃腫的檔案。

如何選擇 ?

Redis預設開啟 RDB 持久化方式,Redis 4.0 開始支援 RDB 和 AOF 的混合持久化(預設關閉,通過配置項 aof-use-rdb-preamble 開啟)

如果把混合持久化開啟,AOF 重寫的時候就直接把 RDB 的內容寫到 AOF 檔案開頭。這樣做的好處是可以結合 RDB 和 AOF 的優點,快速載入同時避免丟失過多的資料。當然缺點也是有的,AOF 裡面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差。

Redis事務

因為Redis 事務不支援回滾操作,所以不支援 ACID 原則中的原子性、永續性,只支援隔離性、一致性。

Redis 開發者們認為沒必要支援回滾,這樣更簡單便捷並且效能更好,即使命令執行錯誤也應該在開發過程中就被發現而不是生產過程中。

你可以將 Redis 中的事務就理解為 :Redis 事務提供了一種將多個命令請求打包的功能。然後按順序執行打包的所有命令,並且不會被中途打斷。

快取穿透

可以理解為:大量請求的 key 根本不存在於快取中,導致直接請求到了資料庫,根本沒有經過快取這一層。

舉個例子:某黑客故意製造我們快取中不存在的 key 發起大量請求,導致大量請求落到資料庫。

如何解決?

  • 快取無效的key

    如果快取、資料庫都查不到某個 key 對應的資料,就寫一個到 Redis 中去並設定過期時間。這種方式可以解決請求的 key 變化不頻繁的快取穿透問題。為防止黑客攻擊,應儘量將過期時間設的更短,實際上該方案並不能根本上解決問題。

    public Object getObjByNullKey(Integer id) {
        // 從快取中獲取資料
        Object cacheValue = cache.get(id);
        // 快取為空
        if (cacheValue == null) {
            // 從資料庫中獲取
            Object storageValue = storage.get(key);
            // 快取空物件
            cache.set(key, storageValue);
            // 如果儲存資料為空,需要設定一個過期時間(300秒)
            if (storageValue == null) {
                // 必須設定過期時間,否則有被攻擊的風險
                cache.expire(key, 60 * 5);
            }
            return storageValue;
        }
        return cacheValue;
    }
    
  • 布隆過濾器

    布隆過濾器是一個非常神奇的資料結構,通過它可以非常方便地判斷一個給定資料是否存在於海量資料中。

    具體實現:把所有可能存在的請求的值都存放在布隆過濾器中,當請求過來時,先判斷使用者發來的請求的值是否存在於布隆過濾器中,不存在的話,直接返回請求引數錯誤資訊給客戶端;存在的話,才會去執行 Redis 的查詢流程。

需要注意:布隆過濾器可能會存在誤判的情況。但總結來說: 布隆過濾器說某個元素存在,小概率會不存在。布隆過濾器說某個元素不存在,那麼這個元素一定不在。

為什麼會存在誤判?

當一個元素加入布隆過濾器中的時候會進行哪些操作

  1. 布隆過濾器中的多個雜湊函式對元素值進行計算,得到多個不同的雜湊值。
  2. 根據得到的雜湊值,在位陣列中把對應下標的值置為 1。

當判斷一個元素是否存在於布隆過濾器的時候,會進行哪些操作:

  1. 對這個元素再次進行相同的雜湊運算;
  2. 得到值之後判斷位陣列中的對應位置的值都為 1,如果值都為 1,說明這個元素在布隆過濾器中,如果存在一個值不為 1,說明該元素不在布隆過濾器中。

然後,一定會出現這樣一種情況:不同元素進行雜湊運算後的值可能相同 (可以適當增加位陣列大小或者調整我們的雜湊函式來降低概率)

快取雪崩

可以理解為:快取在同一時間大面積的失效,導致請求都直接到了資料庫上,造成資料庫短時間內承受大量請求。

在短時間內給資料庫造成巨大壓力,好比雪崩一樣。

如何解決?

  1. 採用 Redis 叢集,避免單機出現問題整個快取服務都沒辦法使用。
  2. 限流,避免同時處理大量的請求。

如何保證資料、快取一致性?

旁路快取模式Cache Aside Pattern

Cache Aside Pattern 中遇到寫請求是這樣的:更新 DB,然後直接刪除 cache 。

如果更新資料庫成功,而刪除快取這一步失敗的情況的話,簡單說兩個解決方案:

  1. 快取失效時間變短(不推薦,治標不治本)

    我們讓快取資料的過期時間變短,這樣的話快取就會從資料庫中載入資料。另外,這種解決辦法對於先操作快取後運算元據庫的場景不適用。

  2. 增加 cache 更新重試機制(常用)

    如果 cache 服務當前不可用導致快取刪除失敗的話,我們就隔一段時間進行重試,重試次數可以自己定。如果多次重試還是失敗的話,我們可以把當前更新失敗的 key 存入佇列中,等快取服務可用之後,再將 快取中對應的 key 刪除即可。

整理下部分基礎的Redis面試題

標籤:引數錯誤   cto   因此   sid   pattern   總結   二進位制   轉發   rand   

原文地址:https://www.cnblogs.com/leejk/p/15068393.html


上一篇:Redis哨兵Sentinel
下一篇:使用obd離線安裝oceanbase