2020互聯網Java後端面試必備解析—Redis23題

文章對Redis數據結構指令、高併發處理、持久化、集羣、複製、Redis 應用等問點作了解析。
java

用XMind畫了一張導圖記錄Redis的學習筆記和一些面試解析(源文件對部分節點有詳細備註和參考資料,歡迎關注個人公衆號:以Java架構贏天下 後臺發送【Redis】拿下載連接,已經完善更新):
面試


1、Redis數據結構相關

1.Redis 支持的數據類型

String字符串redis

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

Hash(哈希)數據庫

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

List(列表)緩存

Redis 列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
格式:lpush name value
在 key 對應 list 的頭部添加字符串元素
格式:rpush name value
在 key 對應 list 的尾部添加字符串元素
格式:lrem name index
key 對應 list 中刪除 count 個和 value 相同的元素
格式:llen name
返回 key 對應 list 的長度安全

Set(集合)服務器

格式:sadd name value
Redis的Set是string類型的無序集合。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。數據結構

zset(sorted set:有序集合)

格式:zadd name score value
Redis zset 和 set 同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
zset的成員是惟一的,但分數(score)卻能夠重複。


2.Redis有哪些經常使用的命令?

Redis命令大全

3.Redis有哪些應用場景

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

2、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 的監視。

3、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 內存塊耗盡了,咋整?答案是:走內存淘汰機制。

4、Redis緩存異常方案


1.緩存雪崩

1.一、什麼是緩存雪崩?

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

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

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

一、加鎖排隊

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

二、數據預熱

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

三、雙層緩存策略

C1爲原始緩存,C2爲拷貝緩存,C1失效時,能夠訪問C2,C1緩存失效時間設置爲短時間,C2設置爲長期

四、定時更新緩存策略

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

五、設置不一樣的過時時間,讓緩存失效的時間點儘可能均勻

2.緩存預熱

2.1.什麼是緩存預熱

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


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

2.2.有什麼解決方案?

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

3.緩存穿透

3.一、什麼是緩存穿透?

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

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

一、緩存空對象

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

二、布隆過濾器

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

4.緩存降級

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

舉例來講:

對於應用的首頁,通常是訪問量很是大的地方,首頁裏面每每包含了部分推薦商品的展現信息。這些推薦商品都會放到緩存中進行存儲,同時咱們爲了不緩存的異常狀況,對熱點商品數據也存儲到了內存中。同時內存中還保留了一些默認的商品信息。以下圖所示:


降級通常是有損的操做,因此儘可能減小降級對於業務的影響程度。

5.緩存擊穿

5.一、什麼是緩存擊穿?

緩存擊穿是指緩存中沒有但數據庫中有的數據(通常是緩存時間到期),這時因爲併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引發數據庫壓力瞬間增大,形成過大壓力

5.二、會帶來什麼問題

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

5.三、如何解決

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


5.3.2."永遠不過時"

  1. 從redis上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。
  2. 從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時



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

class MyCache{

    private ConcurrentHashMap<String, String> map;

    private CountDownLatch countDownLatch;

    private AtomicInteger atomicInteger;

    public MyCache(ConcurrentHashMap<String, 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);
        }
    }
}
複製代碼

5、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 有哪些架構模式?講講各自的特色

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


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


集羣架構,由不少個小主從彙集在一塊兒,一塊兒向外提供服務的,將16384個卡槽切分存儲,並非一個強一致性的集羣架構,每個小主從節點內會存在選舉機制,保證對外的高可用架構。

6、Redis分佈式鎖

有時候講出來的更通俗

Redis分佈式鎖正確實現視頻講解

最後

2020年面試必備的Java後端進階面試題總結了一份將近500頁的pdf文檔,歡迎關注個人公衆號:以Java架構贏天下,回覆【2020】領取這些整理的資料!
喜歡文章記得關注我點個贊喲,感謝支持!

相關文章
相關標籤/搜索