面試必會->Redis篇

来源:https://www.cnblogs.com/xw-01/p/18229771
-Advertisement-
Play Games

01- 你們項目中哪裡用到了Redis ? 在我們的項目中很多地方都用到了Redis , Redis在我們的項目中主要有三個作用 : 使用Redis做熱點數據緩存/介面數據緩存 使用Redis存儲一些業務數據 , 例如 : 驗證碼 , 用戶信息 , 用戶行為數據 , 數據計算結果 , 排行榜數據等 ...


01- 你們項目中哪裡用到了Redis ?

在我們的項目中很多地方都用到了Redis , Redis在我們的項目中主要有三個作用 :

  1. 使用Redis做熱點數據緩存/介面數據緩存
  2. 使用Redis存儲一些業務數據 , 例如 : 驗證碼 , 用戶信息 , 用戶行為數據 , 數據計算結果 , 排行榜數據等
  3. 使用Redis實現分散式鎖 , 解決併發環境下的資源競爭問題

02- Redis的常用數據類型有哪些 ?

Redis 有 5 種基礎數據結構,它們分別是:string(字元串)、list(列表)、hash(字典)、set(集 合) 和 zset(有序集合)

03- Redis的數據持久化策略有哪些 ?

Redis 提供了兩種方式,實現數據的持久化到硬碟。

  1. RDB 持久化(全量),是指在指定的時間間隔內將記憶體中的數據集快照寫入磁碟。
  2. AOF持久化(增量),以日誌的形式記錄伺服器所處理的每一個寫、刪除操作

RDB和AOF一起使用, 在Redis4.0版本支持混合持久化方式 ( 設置 aof-use-rdb-preamble yes )

04- Redis的數據過期策略有哪些 ?

  1. 惰性刪除 :只會在取出 key 的時候才對數據進行過期檢查。這樣對 CPU 最友好,但是可能會造成太多過期 key 沒有被刪除。

    數據到達過期時間,不做處理。等下次訪問該數據時,我們需要判斷

    1. 如果未過期,返回數據
    2. 發現已過期,刪除,返回nil
  2. 定期刪除 : 每隔一段時間抽取一批 key 執行刪除過期 key 操作。並且,Redis 底層會通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響。

    預設情況下 Redis 定期檢查的頻率是每秒掃描 10 次,用於定期清除過期鍵。當然此值還可以通過配置文件進行設置,在 redis.conf 中修改配置“hz”即可,預設的值為hz 10

    定期刪除的掃描並不是遍歷所有的鍵值對,這樣的話比較費時且太消耗系統資源。Redis 伺服器採用的是隨機抽取形式,每次從過期字典中,取出 20 個鍵進行過期檢測,過期字典中存儲的是所有設置了過期時間的鍵值對。如果這批隨機檢查的數據中有 25% 的比例過期,那麼會再抽取 20 個隨機鍵值進行檢測和刪除,並且會迴圈執行這個流程,直到抽取的這批數據中過期鍵值小於 25%,此次檢測才算完成

    Redis 伺服器為了保證過期刪除策略不會導致線程卡死,會給過期掃描增加了最大執行時間為 25ms

定期刪除對記憶體更加友好,惰性刪除對 CPU 更加友好。兩者各有千秋,所以 Redis 採用的是 定期刪除+惰性刪除

05- Redis的數據淘汰策略有哪些 ?

Redis 提供 8 種數據淘汰策略:

淘汰易失數據(具有過期時間的數據)

  1. volatile-lru(least recently used):從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-lfu(least frequently used):從已設置過期時間的數據集(server.db[i].expires)中挑選最不經常使用的數據淘汰
  3. volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  4. volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

淘汰全庫數據

  1. allkeys-lru(least recently used):當記憶體不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)
  2. allkeys-lfu(least frequently used):當記憶體不足以容納新寫入數據時,在鍵空間中,移除最不經常使用的 key
  3. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

不淘汰

  1. no-eviction:禁止驅逐數據,也就是說當記憶體不足以容納新寫入數據時,新寫入操作會報錯。這個應該沒人使用吧!

06- 你們使用Redis是單點還是集群 ? 哪種集群 ?

我們Redis使用的是哨兵集群 , 一主二從 , 三個哨兵 , 三台Linux機器

07- Redis集群有哪些方案, 知道嘛 ?

我所瞭解的Redis集群方案

  1. 主從複製集群 : 讀寫分離, 一主多從 , 解決高併發讀的問題
  2. 哨兵集群 : 主從集群的結構之上 , 加入了哨兵用於監控集群狀態 , 主節點出現故障, 執行主從切換 , 解決高可用問題
  3. Cluster分片集群 : 多主多從 , 解決高併發寫的問題, 以及海量數據存儲問題 , 每個主節點存儲一部分集群數據

08- 什麼是 Redis 主從同步?

Redis 的主從同步(replication)機制,允許 Slave 從 Master 那裡,通過網路傳輸拷貝到完整的數據備份,從而達到主從機制。

主資料庫可以進行讀寫操作,當發生寫操作的時候自動將數據同步到從資料庫,而從資料庫一般是只讀的,並接收主資料庫同步過來的數據。一個主資料庫可以有多個從資料庫,而一個從資料庫只能有一個主資料庫。


image


主從數據同步主要分二個階段 :

第一階段 : 全量複製階段

  • slave節點請求增量同步
  • master節點判斷replid,發現不一致,拒絕增量同步
  • master將完整記憶體數據生成RDB,發送RDB到slave
  • slave清空本地數據,載入master的RDB

第二階段 : 增量複製階段

  • master將RDB期間的命令記錄在repl_baklog,並持續將log中的命令發送給slave
  • slave執行接收到的命令,保持與master之間的同步

09- Redis分片集群中數據是怎麼存儲和讀取的 ?

Redis集群採用的演算法是哈希槽分區演算法。Redis集群中有16384個哈希槽(槽的範圍是 0 -16383,哈希槽),將不同的哈希槽分佈在不同的Redis節點上面進行管理,也就是說每個Redis節點只負責一部分的哈希槽。在對數據進行操作的時候,集群會對使用CRC16演算法對key進行計算並對16384取模(slot = CRC16(key)%16383),得到的結果就是 Key-Value 所放入的槽,通過這個值,去找到對應的槽所對應的Redis節點,然後直接到這個對應的節點上進行存取操作

10- 你們用過Redis的事務嗎 ? 事務的命令有哪些 ?

Redis 作為 NoSQL 資料庫也同樣提供了事務機制。在 Redis 中,MULTI / EXEC / DISCARD / WATCH 這四個命令事務的相關操作命令

我們在開發過程中基本上沒有用到過Redis的事務

11- Redis 和 Memcached 的區別有哪些?

  1. Redis 提供複雜的數據結構,豐富的數據操作 , Memcached 僅提供簡單的字元串。
  2. Redis原生支持集群模式 , Memcached不支持原生集群
  3. Memcached 不支持持久化存儲,重啟時,數據被清空, Redis 支持持久化存儲,重啟時,可以恢復已持久化的數據

12- Redis的記憶體用完了會發生什麼?

如果達到設置的上限,Redis 的寫命令會返回錯誤信息( 但是讀命令還可以正常返回。)
也可以配置記憶體淘汰機制, 當 Redis 達到記憶體上限時會沖刷掉舊的內容。

13- Redis和Mysql如何保證數據⼀致?

  1. 先更新Mysql,再更新Redis,如果更新Redis失敗,可能仍然不⼀致

  2. 先刪除Redis緩存數據,再更新Mysql,再次查詢的時候在將數據添加到緩存中

這種⽅案能解決1 ⽅案的問題,但是在⾼併發下性能較低,⽽且仍然會出現數據不⼀致的問題,
⽐如線程1刪除了 Redis緩存數據,正在更新Mysql,
此時另外⼀個查詢再查詢,那麼就會把Mysql中⽼數據⼜查到 Redis中

  1. 使用MQ非同步同步, 保證數據的最終一致性

我們項目中會根據業務情況 , 使用不同的方案來解決Redis和Mysql的一致性問題 :

  1. 對於一些一致性要求不高的場景 , 不做處理

    例如 : 用戶行為數據 , 我們沒有做一致性保證 , 因為就算不一致產生的影響也很小

  2. 對於時效性數據 , 設置過期時間

    例如 : 介面緩存數據 , 我們會設置緩存的過期時間為 60S , 那麼可能會出現60S之內的數據不一致, 60S後緩存過期, 重新從資料庫載入就一致了

  3. 對於一致性要求比較高但是時效性要求不那麼高的場景 , 使用MQ不斷發送消息完成數據同步直到成功為止

    例如 : 首頁廣告數據 , 首頁推薦數據

    資料庫數據發生修改----> 發送消息到MQ -----> 接收消息更新緩存

    消息不丟失/重覆消費 : 消息狀態表/消息消費表

  4. 對於一致性和時效性要求都比較高的場景 , 使用分散式事務 , Seata的TCC模式

    很少用

14- 什麼是緩存穿透 ? 怎麼解決 ?

緩存穿透是指查詢一條資料庫和緩存都沒有的一條數據,就會一直查詢資料庫,對資料庫的訪問壓力就會增大,緩存穿透的解決方案

有以下2種解決方案 :

  • 緩存空對象:代碼維護較簡單,但是效果不好。
  • 布隆過濾器:代碼維護複雜,效果很好

15- 什麼是緩存擊穿 ? 怎麼解決 ?

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

解決方案 :

  • 熱點數據提前預熱
  • 設置熱點數據永遠不過期。
  • 加鎖 , 限流

16- 什麼是緩存雪崩 ? 怎麼解決 ?

緩存雪崩/緩存失效 指的是大量的緩存在同一時間失效,大量請求落到資料庫 導致資料庫瞬間壓力飆升。

造成這種現象的 原因是,key的過期時間都設置成一樣了。

解決方案是,key的過期時間引入隨機因素

17- 資料庫有1000萬數據 ,Redis只能緩存20w數據, 如何保證Redis中的數據都是熱點數據 ?

配置Redis的內容淘汰策略為LFU演算法 , 這樣會把使用頻率較低的數據淘汰掉 , 留下的數據都是熱點數據

18- Redis分散式鎖如何實現 ?

Redis分散式鎖主要依靠一個SETNX指令實現的 , 這條命令的含義就是“SET if Not Exists”,即不存在的時候才會設置值。

只有在key不存在的情況下,將鍵key的值設置為value。如果key已經存在,則SETNX命令不做任何操作。

這個命令的返回值如下。

  • 命令在設置成功時返回1。
  • 命令在設置失敗時返回0。

假設此時有線程A和線程B同時訪問臨界區代碼,假設線程A首先執行了SETNX命令,並返回結果1,繼續向下執行。而此時線程B再次執行SETNX命令時,返回的結果為0,則線程B不能繼續向下執行。只有當線程A執行DELETE命令將設置的鎖狀態刪除時,線程B才會成功執行SETNX命令設置加鎖狀態後繼續向下執行

Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe");

當然我們在使用分散式鎖的時候也不能這麼簡單, 會考慮到一些實際場景下的問題 , 例如 :

  1. 死鎖問題

    在使用分散式鎖的時候, 如果因為一些原因導致系統宕機, 鎖資源沒有被釋放, 就會產生死鎖

    解決的方案 : 上鎖的時候設置鎖的超時時間

    Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe", 30, TimeUnit.SECONDS);
    
  2. 鎖超時問題

    如果業務執行需要的時間, 超過的鎖的超時時間 , 這個時候業務還沒有執行完成, 鎖就已經自動被刪除了

    其他請求就能獲取鎖, 操作這個資源 , 這個時候就會出現併發問題 , 解決的方案 :

    1. 引入Redis的watch dog機制, 自動為鎖續期
    2. 開啟子線程 , 每隔20S運行一次, 重新設置鎖的超時時間
  3. 歸一問題

    如果一個線程獲取了分散式鎖, 但是這個線程業務沒有執行完成之前 , 鎖被其他的線程刪掉了 , 又會出現線程併發問題 , 這個時候就需要考慮歸一化問題

    就是一個線程執行了加鎖操作後,後續必須由這個線程執行解鎖操作,加鎖和解鎖操作由同一個線程來完成。

    為瞭解決只有加鎖的線程才能進行相應的解鎖操作的問題,那麼,我們就需要將加鎖和解鎖操作綁定到同一個線程中,可以使用ThreadLocal來解決這個問題 , 加鎖的時候生成唯一標識保存到ThreadLocal , 並且設置到鎖的值中 , 釋放鎖的時候, 判斷線程中的唯一標識和鎖的唯一標識是否相同, 只有相同才會釋放
    """

    public class RedisLockImpl implements RedisLock{
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        @Override
        public boolean tryLock(String key, long timeout, TimeUnit unit){
     	 String uuid = UUID.randomUUID().toString();
     	 threadLocal.set(uuid);
     	 return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
      }
        @Override
        public void releaseLock(String key){
     	 //當前線程中綁定的uuid與Redis中的uuid相同時,再執行刪除鎖的操作
     	 if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
     	   stringRedisTemplate.delete(key);   
         }
       }
     }
    

    """


4、可重入問題

當一個線程成功設置了鎖標誌位後,其他的線程再設置鎖標誌位時,就會返回失敗。

還有一種場景就是在一個業務中, 有個操作都需要獲取到鎖, 這個時候第二個操作就無法獲取鎖了 , 操作會失敗

例如 : 下單業務中, 扣減商品庫存會給商品加鎖, 增加商品銷量也需要給商品加鎖 , 這個時候需要獲取二次鎖

第二次獲取商品鎖就會失敗 , 這就需要我們的分散式鎖能夠實現可重入

實現可重入鎖最簡單的方式就是使用計數器 , 加鎖成功之後計數器 + 1 , 取消鎖之後計數器 -1 , 計數器減為0 , 真正從Redis刪除鎖

"""

public class RedisLockImpl implements RedisLock{
 @Autowired
 private StringRedisTemplate stringRedisTemplate;

 private ThreadLocal<String> threadLocal = new ThreadLocal<String>();

 private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>();

 @Override
 public boolean tryLock(String key, long timeout, TimeUnit unit){
	 Boolean isLocked = false;
	 if(threadLocal.get() == null){
		 String uuid = UUID.randomUUID().toString();
	  threadLocal.set(uuid);
		 isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
	 }else{
		 isLocked = true;   
	 }
	 //加鎖成功後將計數器加1
	 if(isLocked){
		 Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();
		 threadLocalInteger.set(count++);
	 }
	 return isLocked;
 }

 @Override
 public void releaseLock(String key){
	 //當前線程中綁定的uuid與Redis中的uuid相同時,再執行刪除鎖的操作
	 if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
		 Integer count = threadLocalInteger.get();
		 //計數器減為0時釋放鎖
		 if(count == null || --count <= 0){
		   stringRedisTemplate.delete(key);      
		 }
	 }
 }
}

"""


5、阻塞與非阻塞問題

在使用分散式鎖的時候 , 如果當前需要操作的資源已經加了鎖, 這個時候會獲取鎖失敗, 直接向用戶返回失敗信息 , 用戶的體驗非常不好 , 所以我們在實現分散式鎖的時候, 我們可以將後續的請求進行阻塞,直到當前請求釋放鎖後,再喚醒阻塞的請求獲得分散式鎖來執行方法。

具體的實現就是參考自旋鎖的思想, 獲取鎖失敗自選獲取鎖, 直到成功為止 , 當然為了防止多條線程自旋帶來的系統資料消耗, 可以設置一個自旋的超時時間 , 超過時間之後, 自動終止線程 , 返回失敗信息


image


19- 你的項目中哪裡用到了分散式鎖

在我最近做的一個項目中 , 我們在任務調度的時候使用了分散式鎖

早期我們在進行定時任務的時候我們採用的是SpringTask實現的 , 在集群部署的情況下, 多個節點的定時任務會同時執行 , 造成重覆調度 , 影響運算結果, 浪費系統資源

這裡為了防止這種情況的發送, 我們使用Redis實現分散式鎖對任務進行調度管理 , 防止重覆任務執行

後期因為我們系統中的任務越來越多 , 執行規則也比較多 , 而且單節點執行效率有一定的限制 , 所以定時任務就切換成了XXL-JOB , 系統中就沒有再使用分散式鎖了


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄前端平臺搭建(Vue2.6,App:HBulderX)創建Vue2.6項目下載相應插件方便開發路由配置對連接後端進行一些配置(main.js文件)導入ElementUI組件組件 | Element同步與非同步axios非同步請求框架 前端平臺搭建(Vue2.6,App:HBulderX) 創建Vue ...
  • 一、場景復現 一個經典的面試題 0.1 + 0.2 0.3 // false 為什麼是false呢? 先看下麵這個比喻 比如一個數 1÷3=0.33333333...... 3會一直無限迴圈,數學可以表示,但是電腦要存儲,方便下次取出來再使用,但0.333333...... 這個數無限迴圈,再大的 ...
  • title: 深入理解Vue 3:計算屬性與偵聽器的藝術 date: 2024/5/30 下午3:53:47 updated: 2024/5/30 下午3:53:47 categories: 前端開發 tags: Vue3 計算屬性 偵聽器 路由 模板 性能優化 實戰案例 前言 Vue 3的新特性簡 ...
  • 效果預覽 視頻畫面 網路請求 代碼實現 ZLMRTCClient.js 當前使用的版本: 1.0.1 Mon Mar 27 2023 19:11:59 GMT+0800 首先需要修改 ZLMRTCClient.js 的代碼,解決由於網路導致播放失敗時無法觸發 WEBRTC_OFFER_ANWSER_ ...
  • UML類圖 類圖定義規則 屬性和方法前加上(+、-、#、留空)分別代表:公開(public)、私有(private)、保護(protected)、預設(default) 方法括弧內為參數類型,冒號後為返回值類型 下劃線表示 靜態(static),斜體表示 抽象(abstract) 類圖關係表示法 其 ...
  • 什麼是心跳包(心跳機制) 先看一下wiki上的說法: 心跳包(英語:Heartbeat)在電腦科學中指一種周期性的信號,通過硬體或軟體的形式來檢測行為的正常與否,或者與電腦系統是否一致。[1] 通常,機器間會每隔幾秒鐘發送一次心跳包。 如果接收終端沒有在指定時間內(通常是幾個心跳包發送的時間間隔 ...
  • 前言 觀察者模式(Observer Pattern)是一種行為型設計模式,它定義了一種一對多的依賴關係,當一個對象的狀態發生改變時,其所有依賴者都會收到通知並自動更新。 在觀察者模式中,有兩種主要的角色: 觀察者(Observer):觀察者是一個介面或抽象類,它定義了一個更新的介面,使得被觀察者在狀 ...
  • 今年3月份開始,就接到通知, 根據《關於開展有關人群第二劑次脊髓灰質炎滅活疫苗補種工作的通知》國疾控衛免發〔2024〕1號文件要求,在2016年3月1日至2019年9月30日之間出生的兒童,凡無接種禁忌者,需補齊2劑次脊髓灰質炎滅活疫苗。由於我家一直是異地打針【在外漂打工,懂的都懂】,疫苗本上信息又 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...