互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

作為一個後端開發人員,不只是要求開發人員需要掌握 Redis,也要求運維人員也要懂 Redis。由於 Redis 的運用廣泛,我們也知道它的重要性,至此面試中經常被問到。

用XMind畫了一張導圖記錄Redis的學習筆記和一些面試解析及視頻鏈接(源文件對部分節點有詳細備註和參考資料,有需要的朋友麻煩轉發後私信關鍵詞【666】免費獲取,已經完善更新):

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

一、Redis數據結構相關

1.Redis 支持的數據類型

String字符串

格式:set key valuestring類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。

Hash(哈希)

格式: hmset name key1 value1 key2 value2Redis hash 是一個鍵值(key=>value)對集合。Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。

List(列表)

Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)格式:lpush name value在 key 對應 list 的頭部添加字符串元素

格式:rpush name value在 key 對應 list 的尾部添加字符串元素格式:lrem name indexkey 對應 list 中刪除 count 個和 value 相同的元素格式:llen name返回 key 對應 list 的長度

Set(集合)

格式:sadd name valueRedis的Set是string類型的無序集合。集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。

zset(sorted set:有序集合)

格式:zadd name score valueRedis zset 和 set 一樣也是string類型元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

2.Redis有哪些常用的命令?Redis命令大全

3.Redis有哪些應用場景面試題:Redis的應用場景核心設計,看完面試不在慌!

二、Redis事務

1.什麼是事務

Redis 中的事務是一組命令的集合,是 Redis 的最小執行單位,一個事務要麼都執行,要麼都不執行。帶有以下三個重要的保證:

  1. 批量操作在發送 EXEC 命令前被放入隊列緩存。
  2. 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。
  3. 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

Redis 事務的原理是先將屬於一個事務的命令發送給 Redis,然後依次執行這些命令。

2.為什麼redis事務不具備原子性

單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行並不是原子性的。事務可以理解為一個打包的批量執行腳本,但批量指令並非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成後續的指令不做。

3. Redis 事務相關命令有哪些?

DISCARD:取消事務,放棄執行事務塊內的所有命令。EXEC:執行所有事務塊內的命令。MULTI:標記一個事務塊的開始。WATCH:Redis Watch 命令用於監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷UNWATCH :取消 WATCH 命令對所有 key 的監視。

三、Redis持久化和緩存管理

1.Redis持久化是什麼?

用一句話可以將持久化概括為:將數據(如內存中的對象)保存到可永久保存的存儲設備中。持久化的主要應用是將內存中的對象存儲在數據庫中,或者存儲在磁盤文件中、 XML 數據文件中等等。也可以從如下兩個層面來理解持久化:應用層:如果關閉( Close )你的應用,然後重新啟動則先前的數據依然存在。系統層:如果關閉( Shut Down )你的系統(電腦),然後重新啟動則先前的數據依然存在。

2.Redis 持久化機制有哪些?

Redis 提供兩種方式進行持久化。RDB 持久化:原理是將 Reids 在內存中的數據庫記錄定時 dump 到磁盤上的 RDB 持久化。AOF(append only file)持久化:原理是將 Redis 的操作日誌以追加的方式寫入文件。

3.Redis 持久化機制 AOF 和 RDB 有什麼區別?

aof,rdb是兩種 redis持久化的機制。用於crash後,redis的恢復。

rdb的特性如下:

  • fork一個進程,遍歷hash table,利用copy on write,把整個db dump保存下來。
  • save, shutdown, slave 命令會觸發這個操作。
  • 粒度比較大,如果save, shutdown, slave 之前crash了,則中間的操作沒辦法恢復。

aof有如下特性:

  • 把寫操作指令,持續的寫到一個類似日誌文件裡。(類似於從postgresql等數據庫導出sql一樣,只記錄寫操作)
  • 粒度較小,crash之後,只有crash之前沒有來得及做日誌的操作沒辦法恢復。

兩種區別就是,一個是持續的用日誌記錄寫操作,crash後利用日誌恢復;一個是平時寫操作的時候不觸發寫,只有手動提交save命令,或者是關閉命令時,才觸發備份操作。選擇的標準,就是看系統是願意犧牲一些性能,換取更高的緩存一致性(aof),還是願意寫操作頻繁的時候,不啟用備份來換取更高的性能,待手動運行save的時候,再做備份(rdb)。rdb這個就更有些 eventually consistent的意思了。

4.RDB和AOF 持久化機制的優缺點

RDB優點

  • RDB 是緊湊的二進制文件,比較適合備份,全量複製等場景
  • RDB 恢復數據遠快於 AOF

RDB缺點

  • RDB 無法實現實時或者秒級持久化;
  • 新老版本無法兼容 RDB 格式。

AOF優點

  • 可以更好地保護數據不丟失;
  • appen-only 模式寫入性能比較高;
  • 適合做災難性的誤刪除緊急恢復。

AOF缺點:

  • 對於同一份文件,AOF 文件要比 RDB 快照大;
  • AOF 開啟後,會對寫的 QPS 有所影響,相對於 RDB 來說 寫 QPS 要下降;
  • 數據庫恢復比較慢, 不合適做冷備。

5.Redis 淘汰策略有哪些?

Redis的內存淘汰策略是指在Redis的用於緩存的內存不足時,怎麼處理需要新寫入且需要申請額外空間的數據。

Redis 的緩存淘汰策略有:

noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。

volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。

6.Redis 緩存失效策略有哪些?

定時過期策略

每個設置過期時間的key都需要創建一個定時器,到過期時間就會立即清除。該策略可以立即清除過期的數據,對內存很友好;但是會佔用大量的CPU資源去處理過期的數據,從而影響緩存的響應時間和吞吐量。

惰性過期策略

只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對內存非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,佔用大量內存。

定期過期策略

每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,並清除其中已過期的key。該策略是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和內存資源達到最優的平衡效果。

Redis中同時使用了惰性過期和定期過期兩種過期策略。

所謂定期刪除,指的是 redis 默認是每隔 100ms 就隨機抽取一些設置了過期時間的 key,檢查其是否過期,如果過期就刪除。

假設 redis 裡放了 10w 個 key,都設置了過期時間,你每隔幾百毫秒,就檢查 10w 個 key,那 redis 基本上就死了,cpu 負載會很高的,消耗在你的檢查過期 key 上了。注意,這裡可不是每隔 100ms 就遍歷所有的設置過期時間的 key,那樣就是一場性能上的災難。實際上 redis 是每隔 100ms 隨機抽取一些 key 來檢查和刪除的。

但是問題是,定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉,那咋整呢?所以就是惰性刪除了。這就是說,在你獲取某個 key 的時候,redis 會檢查一下 ,這個 key 如果設置了過期時間那麼是否過期了?如果過期了此時就會刪除,不會給你返回任何東西。

獲取 key 的時候,如果此時 key 已經過期,就刪除,不會返回任何東西。

但是實際上這還是有問題的,如果定期刪除漏掉了很多過期 key,然後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?如果大量過期 key 堆積在內存裡,導致 redis 內存塊耗盡了,咋整?答案是:走內存淘汰機制。

四、Redis緩存異常方案

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

1.緩存雪崩

1.1、什麼是緩存雪崩?

如果緩存集中在一段時間內失效,發生大量的緩存穿透,所有的查詢都落在數據庫上,造成了緩存雪崩由於原有緩存失效,新緩存未到期間所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。

舉例來說, 我們在準備一項搶購的促銷運營活動,活動期間將帶來大量的商品信息、庫存等相關信息的查詢。 為了避免商品數據庫的壓力,將商品數據放入緩存中存儲。 不巧的是,搶購活動期間,大量的熱門商品緩存同時失效過期了,導致很大的查詢流量落到了數據庫之上。對於數據庫來說造成很大的壓力。

1.2、有什麼解決方案來防止緩存雪崩?

1、加鎖排隊

mutex互斥鎖解決,Redis的SETNX去set一個mutex key,當操作返回成功時,再進行加載數據庫的操作並回設緩存,否則,就重試整個get緩存的方法

2、數據預熱

緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就可以避免在用戶請求的時候,先查詢數據庫,然後再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據。可以通過緩存reload機制,預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存不同的key

3、雙層緩存策略

C1為原始緩存,C2為拷貝緩存,C1失效時,可以訪問C2,C1緩存失效時間設置為短期,C2設置為長期

4、定時更新緩存策略

實效性要求不高的緩存,容器啟動初始化加載,採用定時任務更新或移除緩存

5、設置不同的過期時間,讓緩存失效的時間點儘量均勻

2.緩存預熱

2.1.什麼是緩存預熱

緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就可以避免在用戶請求的時候,先查詢數據庫,然後再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據。如圖所示:

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

如果不進行預熱, 那麼 Redis 初識狀態數據為空,系統上線初期,對於高併發的流量,都會訪問到數據庫中, 對數據庫造成流量的壓力。

2.2.有什麼解決方案?

  1. 數據量不大的時候,工程啟動的時候進行加載緩存動作;
  2. 數據量大的時候,設置一個定時任務腳本,進行緩存的刷新;
  3. 數據量太大的時候,優先保證熱點數據進行提前加載到緩存。

3.緩存穿透

3.1、什麼是緩存穿透?

緩存穿透是指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到對應key的value,每次都要去數據庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫

3.2、有什麼解決方案來防止緩存穿透?

1、緩存空對象

簡單粗暴的方法,如果一個查詢返回的數據為空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

2、布隆過濾器

優勢:佔用內存空間很小,位存儲;性能特別高,使用key的hash判斷key存不存在將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力

4.緩存降級

降級的情況,就是緩存失效或者緩存服務掛掉的情況下,我們也不去訪問數據庫。我們直接訪問內存部分數據緩存或者直接返回默認數據。

舉例來說:

對於應用的首頁,一般是訪問量非常大的地方,首頁裡面往往包含了部分推薦商品的展示信息。這些推薦商品都會放到緩存中進行存儲,同時我們為了避免緩存的異常情況,對熱點商品數據也存儲到了內存中。同時內存中還保留了一些默認的商品信息。如下圖所示:

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

降級一般是有損的操作,所以儘量減少降級對於業務的影響程度。

5.緩存擊穿

5.1、什麼是緩存擊穿?

緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力瞬間增大,造成過大壓力

5.2、會帶來什麼問題

會造成某一時刻數據庫請求量過大,壓力劇增

5.3、如何解決

5.3.1.使用互斥鎖(mutex key)這種解決方案思路比較簡單,就是隻讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了。 如果是單機,可以用synchronized或者lock來處理,如果是分佈式環境可以用分佈式鎖就可以了(分佈式鎖,可以用memcache的add, redis的setnx, zookeeper的添加節點操作)。

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官


5.3.2."永遠不過期"

  1. 從redis上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。
  2. 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value裡,如果發現要過期了,通過一個後臺的異步線程進行緩存的構建,也就是“邏輯”過期

5.3.3.緩存屏障該方法類似於方法一:使用countDownLatch和atomicInteger.compareAndSet()方法實現輕量級鎖

<code>class MyCache{

private ConcurrentHashMap<string> map;

private CountDownLatch countDownLatch;

private AtomicInteger atomicInteger;

public MyCache(ConcurrentHashMap<string> map, CountDownLatch countDownLatch,
AtomicInteger atomicInteger) {
this.map = map;
this.countDownLatch = countDownLatch;
this.atomicInteger = atomicInteger;
}

public String get(String key){


String value = map.get(key);
if (value != null){
System.out.println(Thread.currentThread().getName()+"\\t 線程獲取value值 value="+value);
return value;
}
// 如果沒獲取到值
// 首先嚐試獲取token,然後去查詢db,初始化化緩存;
// 如果沒有獲取到token,超時等待
if (atomicInteger.compareAndSet(0,1)){
System.out.println(Thread.currentThread().getName()+"\\t 線程獲取token");
return null;
}

// 其他線程超時等待
try {
System.out.println(Thread.currentThread().getName()+"\\t 線程沒有獲取token,等待中。。。");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 初始化緩存成功,等待線程被喚醒
// 等待線程等待超時,自動喚醒
System.out.println(Thread.currentThread().getName()+"\\t 線程被喚醒,獲取value ="+map.get("key"));
return map.get(key);
}

public void put(String key, String value){

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

map.put(key, value);

// 更新狀態
atomicInteger.compareAndSet(1, 2);

// 通知其他線程
countDownLatch.countDown();
System.out.println();
System.out.println(Thread.currentThread().getName()+"\\t 線程初始化緩存成功!value ="+map.get("key"));
}

}

class MyThread implements Runnable{

private MyCache myCache;

public MyThread(MyCache myCache) {
this.myCache = myCache;
}

@Override
public void run() {
String value = myCache.get("key");
if (value == null){
myCache.put("key","value");
}

}
}

public class CountDownLatchDemo {
public static void main(String[] args) {

MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));

MyThread myThread = new MyThread(myCache);

ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(myThread);
}
}
}
/<string>/<string>/<code>

五、Redis集群架構

1.實現主從複製的兩種方式

1.1slaveof命令

  • 建立主從命令:slaveof ip port
  • 取消主從命令:slaveof no one

1.2.redis.conf配置文件配置

  • 格式:slaveof ip port
  • 從節點只讀:slave-read-only yes #配置只讀

2.複製過程瞭解嗎,講一講

  1. 從節點執行 slaveof 命令。
  2. 從節點只是保存了 slaveof 命令中主節點的信息,並沒有立即發起複製。
  3. 從節點內部的定時任務發現有主節點的信息,開始使用 socket 連接主節點。
  4. 連接建立成功後,發送 ping 命令,希望得到 pong 命令響應,否則會進行重連。
  5. 如果主節點設置了權限,那麼就需要進行權限驗證,如果驗證失敗,複製終止。
  6. 權限驗證通過後,進行數據同步,這是耗時最長的操作,主節點將把所有的數據全部發送給從節點。
  7. 當主節點把當前的數據同步給從節點後,便完成了複製的建立流程。接下來,主節點就會持續的把寫命令發送給從節點,保證主從數據一致性。

3.redis的主從結構

主從結構一是可以進行冗餘備份,二是可以實現讀寫分離

主從複製

冗餘備份(還可以稱為:主從複製,數據冗餘,數據備份,可以實現容災快速恢復)持久化保證了即使redis服務重啟也會丟失數據,因為redis服務重啟後會將硬盤上持久化的數據恢復到內存中,但是當redis服務器的硬盤損壞了可能會導致數據丟失,如果通過redis的主從複製機制就可以避免這種單點故障. 例如:我們搭建一個主叫做redis0,兩個從,分別叫做redis1和redis2,即使一臺redis服務器宕機其它兩臺redis服務也可以繼續提供服務。主redis中的數據和從redis上的數據保持實時同步,當主redis寫入數據時通過主從複製機制會複製到兩個從redis服務上。

1.一個Master可以有多個Slave,不僅主服務器可以有從服務器,從服務器也可以有自己的從服務器2.複製在Master端是非阻塞模式的,這意味著即便是多個Slave執行首次同步時,Master依然可以提供查詢服務;

3.複製在Slave端也是非阻塞模式的:如果你在redis.conf做了設置,Slave在執行首次同步的時候仍可以使用舊數據集提供查詢;你也可以配置為當Master與Slave失去聯繫時,讓Slave返回客戶端一個錯誤提示;

4.當Slave要刪掉舊的數據集,並重新加載新版數據時,Slave會阻塞連接請求

讀寫分離

主從架構中,可以考慮關閉主服務器的數據持久化功能,只讓從服務器進行持久化,這樣可以提高主服務器的處理性能。從服務器通常被設置為只讀模式,這樣可以避免從服務器的數據被誤修改。

4.什麼是Redis 慢查詢?怎麼配置?

慢查詢就是指,系統執行命令之後,計算統計每條指令執行的時間,如果執行時間超過設置的閾值,就說明該命令為慢指令。

Redis 慢查詢配置參數為:slowlog-log-slower-than:設置慢查詢定義閾值,超過這個閾值就是慢查詢。單位為微妙,默認為 10000。slowlog-max-len:慢查詢的日誌列表的最大長度,當慢查詢日誌列表處於最大長度的時候,最早插入的一個命令將從列表中移除。

5.Redis 有哪些架構模式?講講各自的特點

主從模式,一般是一個主節點,一或多個從節點,為了保證我們的主節點宕機後,數據不丟失,我們將主節點的數據備份到從節點,從節點並不進行實際操作,只做實時同步操作,並不能起到高併發的目的。

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

哨兵模式,一個哨兵集群和一組主從架構組成。比主從更好的是當我們的主節點宕機以後,哨兵會主動選舉出一個主節點繼續向外提供服務。

互聯網Java程序員面試必會Redis23題解析,看完吊打面試官

集群架構,由很多個小主從聚集在一起,一起向外提供服務的,將16384個卡槽切分存儲,並不是一個強一致性的集群架構,每一個小主從節點內會存在選舉機制,保證對外的高可用架構。

最後

喜歡文章記得關注我轉發喲,感謝支持!


分享到:


相關文章: