Redis 的常見問題彙總

在項目中如何使用緩存的?

Redis緩存最突出的特點就是高性能,高併發,我們在項目裏面緩存主要用在以下幾個方面:

  • 常用數據的緩存,例如數據字典,屬性值等等
  • 分佈式鎖的一個實現
  • 緩存文件預覽詳情頁的數據
  • 訪問量統計排序功能

緩存使用不當會有哪些後果?

使用緩存可能會帶來以下問題:

  • 雙寫一致性問題
  • 緩存擊穿問題
  • 緩存雪崩問題
  • 同時系統的可用性會降低

Redis都有哪些數據類型?使用場景是什麼?

Redis有一下幾種數據類型

  • String
    最簡單的數據類型,get、set就可以
  • list
    集合類型,可以存儲數據集合,和java裏面的list類似,可以分頁查詢,甚至還可以做成一個簡單的消息隊列
  • set
    set類型,是一個自動去重的集合,可以用來求交集什麼的等等
  • zset
    一個可排序的不重複的集合,可以用來做排行榜什麼的
  • hash
    哈希類型,可以存儲一些簡單的對象

Redis的線程模型是什麼?爲什麼單線程還快?

我們都知道Redis是使用單線程的,Redis的線程模型如圖
在圖中我們可以看到,當Redis客戶端去連接Redis服務器的時候會連接服務器的ServerSocket,也就是6379端口,然後把時間亞茹隊列裏面,等到文件事件選擇器收到了時間之後判斷如果是連接事件,那麼就會創建一個Socket01(圖中的S1)與當前的客戶端保持連接,同時還會把S1 的READABLE事件和命令請求處理器進行關聯,這時候如果Client1發送了一個請求過來,比如是一個Set請求,那麼Client1 會把請求發送到S1,然後多路複用器會把事件壓到隊列裏面,文件事件選擇器會把接收到的命令請求轉發給命令請求處理器去處理,然後處理完成之後把命令回覆處理器與S1的WRITEABLE事件進行關聯,然後命令恢復處理器會恢復給S1一個響應(比如OK)然後命令回覆處理器解除和S1的關聯

在這裏插入圖片描述

爲什麼Redis單線程還那麼快?

  1. 基於非阻塞的多路複用模型
  2. 純內存的操作
  3. 避免了多線程裏面的上下文切換帶來的開銷
  4. C語言實現,執行速度相對來說快一些

Redis的緩存過期了會立即刪除嗎?

Redis的數據在過期了之後不會馬上全部刪除,Redis會定時的(每隔100ms)刪除一部分數據,同時如果在執行get操作的時候發現數據已經過期了,那麼,會把這個數據刪除掉。
也就是Redis的過期策略是 定期刪除+惰性刪除

Redis內存滿了Redis會怎麼做?

這個取決於設置的Redis內存淘汰機制,一般來說Redis的內存的淘汰策略有下面幾種:

1. noeviction
如果空間不不足了,會直接報錯,不允許插入
2. allkeys-lru
如果空間滿了會刪除掉最少使用的key
3. allkeys-random
如果空間滿了會隨機刪除掉一個key
4. volatile-lru
會在設置了過期時間的key裏面刪除最近最少使用的key
5. volatile-random
會在設置了過期時間的key裏面隨機刪除一個key
6. volatile-ttl
會刪除掉快要過期的key
一般來說我們使用allkeys-lru策略會比較多

如何實現一個LRU算法

設計一個LRU淘汰算法可以基於LinkedHashMap來實現

public class LRUCache extends LinkedHashMap<K,V>{
  private  final int SIZE ;
public LRUCache(int size){
    super(size,0.75f,true);
    SIZE= size;
}
protected boolean  removeEldestEntry(){
    return this.size()>SIZE;
}
}

如何保證Redis的高併發、高可用

  • Redis通過讀寫分離保證高併發
    可以使用主從架構,把寫操作放在主節點上面執行,從節點執行讀操作,這樣就提高了Redis的併發效率,不過這樣也會有有一個問題。如果Redis主節點掛了怎麼辦?
  • 使用哨兵模式保證高可用
    在原有的主從模式下面,使用哨兵模式,當主節點掛掉的時候哨兵模式會把存活的從節點升級爲主節點,實現主從切換

主從複製的原理是啥?

主從模式的結構圖如下
主節點負責數據寫入,同時會異步把寫入的數據發送給從節點
在這裏插入圖片描述

主從複製的流程
主從複製分爲全量複製和增量複製,
全量複製:當從節點首次連接master的時候主節點會進行一次全量複製,生成一份RDB快照文件發送給從節點(同時這期間新寫入的數據會寫到緩衝區),從節點解析RDB文件,解析完成之後反饋到主節點,直接點再把緩衝區的數據異步發送給從節點
增量複製:從節點和主節點都會記錄當前數據同步的offset來保證數據的一致性,當從節點斷開連接之後再重新連接,會從offset記錄的位置開始繼續同步
在這裏插入圖片描述

哨兵機制的原理是啥?

sentinel,哨兵,是redis 裏面的一個組件,用來實現
集羣監控:
負責監控Redis的Master和Salve是否有正常工作
消息通知:
如果某一個節點發生了故障,那麼會報警給管理員
故障轉移:
如果master掛掉了,會立即提升salve爲master
配置中心:
故障 轉移完成之後,通知client客戶端新的master地址

哨兵是用來實現高可用的,所以它本身也是分佈式的,作爲一個哨兵集羣去運行, 互相協同工作。
哨兵模式僅僅是保證了系統的高可用,並沒有保證數據不丟失。
哨兵模式要求節點最少有3個,不然會發生腦裂問題
腦裂問題:
當一個從節點與master之間出現了網絡問題,無法連接,那麼這個從節點會變成主節點,這時候系統中就存在了兩個主節點,這時候就發生了腦裂問題。
腦裂問題導致數據丟失:
由於發生了腦裂,系統裏面存在了兩個主節點,client的寫請求會發送到其中一個主節點,但是,當發生腦裂的另一個主節點重新連接上集羣之後,原先寫入數據的master可能就會變爲slave,然後與master進行數據同步,slave上面的數據被清空,導致這一部分數據丟失

Redis的持久化機制有哪些?RDB和AOF的優缺點?

Redis提供了RDB和AOF兩種持久化方式
RDB持久化適合做數據的冷備份。使用RDB持久化方式Redis會每隔一段時間(可自定義,一般5分鐘)進行一次數據快照的創建,所以數據會有一段時間的延遲
如果開啓AOF持久化,Redis會在每次指令執行同時把指令日誌以append only的方式寫入OSCache,然後每隔1秒鐘刷新到磁盤。
使用RDB做備份對Redis幾乎性能影響,不過如果間隔時間太長,可能導致RDB文件太大,生成時間過長,也會對性能有一定影響,RDB每次都會生成一個新的文件,方便冷備
使用AOF的方式會對Redis的性能有些微的影響,因爲在寫入Redis的時候還要去寫OSCache,不過AOF的數據是比較完整的,由於是基於指令日誌的備份,所以在數據恢復的時候是會稍微慢一些
一般推薦兩種持久化方式都開啓

Redis的集羣工作原理是什麼?

在搭建了主從哨兵模式之後可以看到Redis實現了高可用,但是Redis的容量還是隻有一臺機器的容量,如果想要給Redis增加容量是不行的,這時候就需要使用Redis集羣了(在Redis5.X版本之後已經不需要安裝插件就可以搭建集羣了)
Redis集羣的特點:
1、Redis集羣會自動對數據進行分片,每一個master上面存放一部分數據
2、Redis集羣內部提供高可用,部分的master掛掉系統仍然可用
RedisCluster架構下會開放兩個端口,一個是原先的6379端口,用來進行客戶端通信,在一個就是16379用來進行集羣通信,使用gossip協議進行通信,減少網絡帶寬的佔用

分佈式尋址有哪些算法?什麼是一致性hash算法?如何動態增加和刪除一個節點?

分佈式尋址算法一般有:
哈希算法
哈希算法是最簡單的分佈式尋址算法,其原理就是根據請求數據的哈希值,然後對節點數進行取模運算,把請求落在不同的節點上面,哈希算法實現簡單,但是有一定的缺點,比如如果突然有一個節點下線了,那麼哈希取模的結果就會發生變化,會導致系統大部分數據都不可用。
在這裏插入圖片描述

一致性哈希算法
由於哈希算法的缺陷,所以出現了一致性hash算法。
1、那什麼是一致性hash算法?
哈希一致性算法想象成爲一個環狀的圖,上面有 0-2^32-1 個槽位,每一臺機器可以分散在每個槽位上面,把請求數據以 2^32-1 取模,然後把取模結果落在哈希環上面,一致性哈希算法會順時針尋找,直到找到一個節點,然後把數據落到那個節點上面。
2、哈希環偏斜怎麼解決
哈希環偏斜是指多個節點相隔很近,導致像個的比較遠的節點除了故障之後會丟失比較多的數據。如圖,爲了解決這個問題引入了一部分虛擬節點(實際節點的複製品)這樣節點數越多那麼分佈也就越分散
在一致性哈希算法中,如果某一個節點掛掉,由於取模結果不變,所以之前的數據還是會落到和之前一樣的節點上面,新的數據會落到順時針方向後面的一個可用節點上面
在這裏插入圖片描述

RedisCluster的hash slot 算法
在Redis的HashSlot算法中,Redis會有16384個hash槽,如果我們有3個節點,那麼Redis會把這16384個hash槽進行平均分配,到每一個節點上面,如果這時候添加了一個節點,那麼Redis會重新分配hash槽,然後把其他節點的部分數據轉移到新的節點上面,這個過程是不用停機重啓的。同理如果需要下線一個節點,那麼Redis也會重新分配,然後轉移數據

Redis的雪崩和穿透以及擊穿是什麼?怎麼解決?

  • 緩存擊穿:
    緩存擊穿指的是一個key在失效的一瞬間,大量的併發請求涌了進來,所有的請求打到了數據庫,導致數據庫崩潰。
    解決方法:可以使用分佈式鎖防止緩存擊穿
    public Response test(){
        Response res = getFromCache();
            if(res == null){
                Lock lock = getDistributeLock();
                try{
                if(lock.lock()){
                     res = getFromCache();
                     if(res==null){
                        res = getFromDb();
                        setCache(res);
                     }else{
                        return res;
                     }
                }
                }finaly{
                lock.unlock();
                }
            }else{
                return res;
            }
    }
  • 緩存穿透問題
    緩存穿透指的是大量的請求查詢一個緩存裏面不存在的數據,導致請求全部打在DB上面,然後DB就掛了
    解決辦法:布隆過濾器+空值錄入緩存
    我們可以使用布隆過濾器攔截一部分不存在的數據,然後剩下的如果查出來是null那麼也把這些null對應的可以緩存起來

  • 緩存雪崩的問題
    緩存雪崩指的是緩存服務器掛了或者大量的緩存數據在同一時間失效了,導致請求直接打到了DB,這時候DB無法抗住就掛了
    解決方案:
    對於數據過期的問題,可以錯開過期時間,如果不能錯開的話可以採取和緩存擊穿一樣的解決方案,加鎖解決
    對於緩存服務器不可用的情況,可以使用限流。假設數據庫的最大qps是2000,可以使用令牌桶或者漏桶等等進行限流,對於超過的請求直接返回友好的提示信息。

Redis雙寫一致性如何保證

Redis的雙寫一致性指的是如何保證數據庫和緩存之間的數據一致性
目前DB和Redis之間的數據同步一版是在查詢的時候更新Redis數據,在修改的時候刪除Redis的數據,那麼這時候應該考慮是在更新之前刪除呢還是在更新之後刪除呢?
1、更新之後刪除
如果在更新之後刪除,那麼如果緩存刪除失敗就會導致數據庫和緩存不一致

....
updateDB();
deleteCache();
....

2、更新之前刪除
在更新之前刪除解決了上面的不一致的情況,即使數據庫操作失敗那麼在此查詢也還是會更新。

....
deleteCache();
updateDB();
....

但是這樣還是會有問題。如果在併發很高的情況下,緩存被刪除了,DB操作還沒有完成,這時候另一個請求進行了查詢,那麼緩存裏面又會存儲舊的值,導致緩存不一致的情況。怎麼解決?
使用狀態標記,在寫操作的時候阻塞讀操作,首先需要讓負載策略把相同ID的寫操作和查詢操作路由到一臺機器上面,然後使用讀寫鎖來保證線程安全

 ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  public String getResult(String id){
        Lock lock = readWriteLock.readLock();
        lock.lock();
        try {
            if (StringUtils.isEmpty(id)) {
                return null;
            }
            String res = CACHE.get(id);
            if (res != null) {
                return res;
            } else {
                // get from db
                res = getFromDB(id);
                CACHE.put(id, res);
            }
            return res;
        }finally {
            lock.unlock();
        }
 }
    
 
 public void setValue(String id){
        Lock lock = readWriteLock.writeLock();
        lock.lock();
        try {
            //clear cache
            CACHE.remove(id);
            //sleep random time 20ms~120ms
            Thread.sleep((long) (Math.random()*100+20));
            DB.put(id,"value"+id+"new");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
}

Redis併發競爭問題是什麼?怎麼解決?Redis事物CAS是什麼?

Redis的競爭問題類似於多線程編程裏面的線程安全的問題。儘管Redis本身是線程安全的,但是如果業務不是線程安全的那麼還是會出現競爭問題。比如需要把一個key先修改爲1,然後改爲2,然後改爲3,注意:這些操作是併發的
解決方案:
1、分佈式鎖,使用鎖的方式來保證線程的安全性,不過無法保證修改操作的順序性
2、分佈式鎖+時間戳,一版我們寫入數據庫的數據都會帶上一個更新時間戳,我們可以根據這個時間戳來判斷當前數據的順序

生產環境怎麼部署Redis

我們的生產環境部署的Redis是使用集羣的方式來部署的,一共有六臺機器,三主三從,每臺機器提供給Redis進程的內存是8G,

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