redis面試題收集和整理 (附答案)

       最近有一些小夥伴面試,對自己回答redis的問題結果比較不滿意,這裏收集了網上常見的面試題並做了整理,部分答案加入了自己的看法和思路,希望可以幫助到大家

 

相關文章

redis分佈式鎖實例

Docker部署Redis 圖文教程

redis可視化工具 redis desktop manager 下載和安裝圖文教程

 

1、什麼是redis

      redis本質上是一個Key-Value類型的內存數據庫,和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。

2、redis 與 memcached 相比有哪些優勢?

  1. redis支持更豐富的數據類型(支持更復雜的應用場景),redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。memcache支持簡單的數據類型,String。
  2. redis支持數據的備份,即master-slave模式的數據備份。
  3. redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啓的時候可以再次加載進行使用,而Memecache把數據全部存在內存之中。
  4. redis的速度比memcached快很多。
  5. memcached是多線程,非阻塞IO複用的網絡模型;Redis使用單線程的IO複用模型。

3、Redis 支持哪幾種數據類型?

String、List、Set、Sorted Set(zset  有序集合)、Hash

4、redis 常見數據結構以及使用場景分析

1. String

常用命令: set,get,decr,incr,mget 等。

String數據結構是簡單的key-value類型,value其實不僅可以是String,也可以是數字。 常規key-value緩存應用; 常規計數:微博數,粉絲數等。

2.Hash

常用命令: hget,hset,hgetall 等。

Hash 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象,後續操作的時候,你可以直接僅僅修改這個對象中的某個字段的值。 比如我們可以Hash數據結構來存儲用戶信息,商品信息等等。比如下面我就用 hash 類型存放了我本人的一些信息:

key=JavaUser293847
value={
  “id”: 1,
  “name”: “SnailClimb”,
  “age”: 22,
  “location”: “Wuhan, Hubei”
}

3.List

常用命令: lpush,rpush,lpop,rpop,lrange等

list 就是鏈表,Redis list 的應用場景非常多,也是Redis最重要的數據結構之一,比如微博的關注列表,粉絲列表,消息列表等功能都可以用Redis的 list 結構來實現。

Redis list 的實現爲一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內存開銷。

另外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基於 list 實現分頁查詢,這個很棒的一個功能,基於 redis 實現簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高。

4.Set

常用命令: sadd,spop,smembers,sunion 等

set 對外提供的功能與list類似是一個列表的功能,特殊之處在於 set 是可以自動排重的。

當你需要存儲一個列表數據,又不希望出現重複數據時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。可以基於 set 輕易實現交集、並集、差集的操作。

比如:在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合。Redis可以非常方便的實現如共同關注、共同粉絲、共同喜好等功能。這個過程也就是求交集的過程,具體命令如下:

sinterstore key1 key2 key3     將交集存在key1內

5.Sorted Set

常用命令: zadd,zrange,zrem,zcard等

和set相比,sorted set增加了一個權重參數score,使得集合中的元素能夠按score進行有序排列。

舉例: 在直播系統中,實時排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解爲按消息維度的消息排行榜)等信息,適合使用 Redis 中的 SortedSet 結構進行存儲。

5、MySQL裏有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據?

redis內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。

6、Redis 有哪幾種數據淘汰策略?

  1. noeviction:返回錯誤當內存限制達到,並且客戶端嘗試執行會讓更多內存被使用的命令。
  2. allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。
  3. volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存放。
  4. allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。
  5. volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。
  6. volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。

7、爲什麼 Redis 需要把所有數據放到內存中?

        redis 爲了達到最快的讀寫速度將數據都讀到內存中,並通過異步的方式將數據寫入磁盤。 所以 redis 具有快速和數據持久化的特徵,如果不將數據放在內存中,磁盤 I/O 速度爲嚴重影響 redis 的性能。 在內存越來越便宜的今天,redis 將會越來越受歡迎, 如果設置了最大使用的內存,則數據已有記錄數達到內存限值後不能繼續插入新值。

8、redis常見性能問題和解決方案

  1. Master最好不要做任何持久化工作,如RDB內存快照和AOF日誌文件
  2. 如果數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次
  3. 爲了主從複製的速度和連接的穩定性,Master和Slave最好在同一個局域網內
  4. 儘量避免在壓力很大的主庫上增加從庫

9、Redis有哪些適合的場景?

(1)會話緩存(Session Cache)   

       最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在於:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?
       幸運的是,隨着 Redis 這些年的改進,很容易找到怎麼恰當的使用Redis來緩存會話的文檔。甚至廣爲人知的商業平臺Magento也提供Redis的插件。   

(2)全頁緩存(FPC) 

        除基本的會話token之外,Redis還提供很簡便的FPC平臺。回到一致性問題,即使重啓了Redis實例,因爲有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。
        再次以Magento爲例,Magento提供一個插件來使用Redis作爲全頁緩存後端。
        此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。 

(3)隊列       

       reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作爲一個很好的消息隊列平臺來使用。Redis作爲隊列使用的操作,就類似於本地程序語言(如Python)對 list 的 push/pop 操作。
       如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的後端工具,以滿足各種隊列需求。例如,Celery有一個後臺就是使用Redis作爲broker,你可以從這裏去查看。

(4)排行榜/計數器   

        redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。
        所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之爲“user_scores”,我們只需要像下面一樣執行即可:
        當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:

ZRANGE user_scores 0 10 WITHSCORES

       Agora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的,你可以在這裏看到。

(5)發佈/訂閱

       最後(但肯定不是最不重要的)是Redis的發佈/訂閱功能。發佈/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作爲基於發佈/訂閱的腳本觸發器,甚至用Redis的發佈/訂閱功能來建立聊天系統!

10、Redis支持的Java客戶端都有哪些?官方推薦用哪個?

       Redisson、Jedis、lettuce等等,官方推薦使用Redisson。

11、Redis和Redisson有什麼關係?

       Redisson是一個高級的分佈式協調Redis客服端,能幫助用戶在分佈式環境中輕鬆實現一些Java的對象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

12、Jedis與Redisson對比有什麼優缺點?

      1、Jedis是Redis的Java實現的客戶端,其API提供了比較全面的Redis命令的支持;
      2、Redisson實現了分佈式和可擴展的Java數據結構,和Jedis相比,功能較爲簡單,不支持字符串操作,不支持排序、事務、管道、分區等Redis特性。Redisson的宗旨是促進使用者對Redis的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。

13、說說Redis哈希槽的概念?

       Redis集羣沒有使用一致性hash,而是引入了哈希槽的概念,Redis集羣有16384個哈希槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽,集羣的每個節點負責一部分hash槽。

14、Redis集羣的主從複製模型是怎樣的?

       爲了使在部分節點失敗或者大部分節點無法通信的情況下集羣仍然可用,所以集羣使用了主從複製模型,每個節點都會有N-1個複製品.

15、Redis集羣會有寫操作丟失嗎?爲什麼?

       Redis並不能保證數據的強一致性,這意味這在實際中集羣在特定的條件下可能會丟失寫操作。

16、Redis集羣之間是如何複製的?

       異步複製

17、Redis集羣最大節點個數是多少?

      16384個。

18、Redis集羣如何選擇數據庫?

       Redis集羣目前無法做數據庫選擇,默認在0數據庫。

19、Redis中的管道有什麼用?

        一次請求/響應服務器能實現處理新的請求即使舊的請求還未被響應。這樣就可以將多個命令發送到服務器,而不用等待回覆,最後在一個步驟中讀取該答覆。
        這就是管道(pipelining),是一種幾十年來廣泛使用的技術。例如許多POP3協議已經實現支持這個功能,大大加快了從服務器下載新郵件的過程。

20、怎麼理解Redis事務?

        事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
        事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

21、Redis事務相關的命令有哪幾個?

        MULTI、EXEC、DISCARD、WATCH

22、Redis key的過期時間和永久有效分別怎麼設置?

        EXPIRE和PERSIST命令。

23、使用過 Redis 分佈式鎖麼,它是怎麼實現的?

參考之前寫的文章:https://blog.csdn.net/weixin_43841693/article/details/99677422

24、Redisson實現Redis分佈式鎖的底層原理

(1)、加鎖機制,看下列源碼

/**
KEYS[1] :需要加鎖的key,這裏需要是字符串類型。
ARGV[1] :鎖的超時時間,防止死鎖,,默認30秒
ARGV[2] :加鎖端的唯一標識,示例:id(UUID.randomUUID()) + “:” + threadId
*/
Future tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
            // 檢查是否key已經被佔用,如果沒有則設置超時時間和唯一標識,初始化value=1
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                "return nil; " +
            "end; " +
            // 如果鎖重入,通過這個命令,對客戶端1的加鎖次數,累加1
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                //鎖重入重新設置超時時間
                "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                "return nil; " +
            "end; " +
            //返回剩餘的過期時間
            "return redis.call('pttl', KEYS[1]);",
            Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

(2)、鎖互斥機制

       如果客戶端2來嘗試加鎖,執行了上圖的一段lua腳本,會咋樣呢?很簡單,第一個if判斷會執行“exists myLock”,發現myLock這個鎖key已經存在了。接着第二個if判斷,判斷一下,myLock鎖key的hash數據結構中,是否包含客戶端2的ID,但是明顯不是的,因爲那裏包含的是客戶端1的ID。所以,客戶端2會獲取到pttl myLock返回的一個數字,這個數字代表了myLock這個鎖key的剩餘生存時間。比如還剩15000毫秒的生存時間。此時客戶端2會進入一個while循環,不停的嘗試加鎖。

(3)、watch dog(看門狗機制)

       watch dog自動延期機制客戶端1加鎖的鎖key默認生存時間才30秒,如果超過了30秒,客戶端1還想一直持有這把鎖,怎麼辦呢?簡單!只要客戶端1一旦加鎖成功,就會啓動一個watch dog看門狗,他是一個後臺線程,會每隔10秒檢查一下,如果客戶端1還持有鎖key,那麼就會不斷的延長鎖key的生存時間。

(4)、可重入鎖機制

      如上圖lua腳本,如果鎖重入,通過鎖重入命令,對客戶端1的加鎖次數,累加1,然後重新設置超時時間

(5)、釋放鎖機制,看下源碼

/**
KEYS[1] :需要加鎖的key,這裏需要是字符串類型。
KEYS[2] :redis消息的ChannelName,一個分佈式鎖對應唯一的一個channelName:“redisson_lock__channel__{” + getName() + “}”
ARGV[1] :reids消息體,這裏只需要一個字節的標記就可以,主要標記redis的key已經解鎖,再結合redis的Subscribe,能喚醒其他訂閱解鎖消息的客戶端線程申請鎖。
ARGV[2] :鎖的超時時間,防止死鎖
ARGV[3] :鎖的唯一標識,也就是剛纔介紹的 id(UUID.randomUUID()) + “:” + threadId
*/
public void unlock() {
        Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        // 如果key已經不存在,說明已經被解鎖,直接發佈(publihs)redis消息
        "if (redis.call('exists', KEYS[1]) == 0) then " +
        "redis.call('publish', KEYS[2], ARGV[1]); " +
        "return 1; " +
        "end;" +
        // key和field不匹配,說明當前客戶端線程沒有持有鎖,不能主動解鎖。
        "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
        "return nil;" +
        "end; " +
        //對當前加鎖的key加鎖次數減1。
        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
        // 如果counter>0說明鎖在重入,不能刪除key
        "if (counter > 0) then " +
        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
        "return 0; " +
        "else " +
        // 刪除key並且publish 解鎖消息
        "redis.call('del', KEYS[1]); " +
        "redis.call('publish', KEYS[2], ARGV[1]); " +
        "return 1; "+
        "end; " +
        "return nil;",
        Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId()));
        if (opStatus == null) {
                throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                        + id + " thread-id: " + Thread.currentThread().getId());
        }
        // 解鎖成功之後取消更新鎖expire的時間任務
        if (opStatus) {
                cancelExpirationRenewal();
        }
    }

       每次都對myLock數據結構中的那個加鎖次數減1。如果發現加鎖次數是0了,說明這個客戶端已經不再持有鎖了,此時就會用:“del myLock”命令,從redis裏刪除這個key,對鎖進行釋放。

25、redis 持久化機制(怎麼保證 redis 掛掉之後再重啓數據可以進行恢復)

       很多時候我們需要持久化數據也就是將內存中的數據寫入到硬盤裏面,大部分原因是爲了之後重用數據(比如重啓機器、機器故障之後回覆數據),或者是爲了防止系統故障而將數據備份到一個遠程位置。

        Redis不同於Memcached的很重一點就是,Redis支持持久化,而且支持兩種不同的持久化操作。Redis的一種持久化方式叫快照(snapshotting,RDB),另一種方式是隻追加文件(append-only file,AOF).這兩種方法各有千秋,下面我會詳細這兩種持久化方法是什麼,怎麼用,如何選擇適合自己的持久化方法。

(1)、快照(snapshotting)持久化(RDB)

       Redis可以通過創建快照來獲得存儲在內存裏面的數據在某個時間點上的副本。Redis創建快照之後,可以對快照進行備份,可以將快照複製到其他服務器從而創建具有相同數據的服務器副本(Redis主從結構,主要用來提高Redis性能),還可以將快照留在原地以便重啓服務器的時候使用。

快照持久化是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命令創建快照。

(2)、AOF(append-only file)持久化

       與快照持久化相比,AOF持久化 的實時性更好,因此已成爲主流的持久化方案。默認情況下Redis沒有開啓AOF(append only file)方式的持久化,可以通過appendonly參數開啓:

appendonly yes

       開啓AOF持久化後每執行一條會更改Redis中的數據的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的,默認的文件名是appendonly.aof。

       在Redis的配置文件中存在三種不同的 AOF 持久化方式,它們分別是:

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

       爲了兼顧數據和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現系統崩潰,用戶最多隻會丟失一秒之內產生的數據。當硬盤忙於執行寫入操作的時候,Redis還會優雅的放慢自己的速度以便適應硬盤的最大寫入速度。

26、什麼是緩存擊穿?什麼是緩存穿透?如何避免?

緩存擊穿:

        緩存擊穿是如:redis的熱點數據某一時間段消失,大併發集中對這一個點進行訪問,當這個Key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,緩存穿透是緩存無數據,數據庫有數據。

如何避免:

  1. 熱點數據直接進行持久化,不設失效時間,更新時刷新。
  2. 互斥鎖(單機用lock,分佈式的話需要用分佈式鎖)

 

緩存穿透:
        一般的緩存系統,都是按照 key 去緩存查詢,如果不存在對應的 value,就應該去後端系統查找(比如DB)。一些惡意的請求會故意查詢不存在的 key,請求量很大,就會對後端系統造成很大的壓力。這就叫做緩存穿透。


如何避免:

  1. 對查詢結果爲空的情況也進行緩存,緩存時間設置短一點,或者該 key 對應的數據 insert 了之後清理緩存。
  2. 對一定不存在的 key 進行過濾。可以把所有的可能存在的 key 放到一個大的 Bitmap 中,查詢時通過該 bitmap 過濾。
  3. 使用布隆過濾器,布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
  4. 下方的布隆過濾器只適用於單機,分佈式的話需要使用redis來實現布隆過濾器,原理是Setbit 命令
# google guava實現的布隆過濾器依賴

<dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>19.0</version>
</dependency>

----------------------------------------------------------
# 布隆過濾器存在誤差,默認0.03D

package com.example.demo.smm.Test;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

import java.util.ArrayList;
import java.util.List;

public class BloomFilterTest {
    private static final int size = 1000000;// 100萬
    private static BloomFilter<Integer> bloomFilter =BloomFilter.create(Funnels.integerFunnel(), size);

    public static void main(String[] args) {
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }

        List<Integer> list = new ArrayList<>(1000);
        //故意取10000個不在過濾器裏的值,看看有多少個會被認爲在過濾器裏
        for (int i = size + 20000; i < size + 30000; i++) {
            if (bloomFilter.mightContain(i)) {
                list.add(i);
            }
        }
        System.out.println("誤判的數量:" + list.size()); //在300左右
    }
}

27、什麼是緩存雪崩?何如避免?

緩存雪崩:
       當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給後端系統帶來很大壓力。導致系統崩潰。


如何避免:

  1. 在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個 key 只允許一個線程查詢數據和寫緩存,其他線程等待。
  2. 做二級緩存,A1 爲原始緩存,A2 爲拷貝緩存,A1 失效時,可以訪問 A2,A1 緩存失效時間設置爲短期,A2 設置爲長期。
  3. 不同的 key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。
  4. 儘量保證整個 redis 集羣的高可用性,發現機器宕機儘快補上。選擇合適的內存淘汰策略。
  5. 利用 redis 持久化機制保存的數據儘快恢復緩存
  6. 熱點數據直接進行持久化,不設失效時間,更新時刷新

28、redis如何大量插入

     參考文檔:http://www.redis.cn/topics/mass-insert.html

 

以上面試題收集參考了下列文章,挑選了比較常見的問題進行羅列和答案整理:

http://blog.itpub.net/31545684/viewspace-2213990/

https://msd.misuland.com/pd/3065794831805580868

https://www.jianshu.com/p/65765dd10671

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章