Java面試八股—Redis篇

一、Redis的使用場景

(一)緩存

1.Redis使用場景緩存

場景:緩存熱點數據(如用戶信息、商品詳情),減少數據庫訪問壓力,提升響應速度。

2.緩存穿透

正常的訪問是:根據ID查詢文章,先查Redis,如果Redis中命中,返回結果;Redis查不到,查DB,DB查詢到結構,返回(返回之前數據存儲到Redis)。

緩存穿透是什么?
緩存穿透是大量請求訪問緩存和數據庫中都不存在的數據(如非法ID或隨機攻擊),導致請求穿透緩存直接打到數據庫,mysql查詢不到數據也不會直接寫入緩存,每次請求都查數據庫,引發數據庫壓力

簡單回答:查詢不存在的數據,mysql查詢不到數據也不會直接寫入緩存,導致請求穿透緩存直接打到數據庫。

3.緩存穿透解決方案

(1)方案一:緩存空值。

①緩存空數據,查詢返回的數據為空,仍把這個空結果進行緩存,設置較短的過期時間。
②優點:簡單。
③缺點:消耗內存,可能會發生不一致的問題。

(2)方案二:布隆過濾器

在這里插入圖片描述
①緩存預熱時,需要將布隆過濾器給初始化。例如有一批熱點數據,先將這些熱點數據批量添加到緩存中,與此同時,要將這些熱點數據添加到布隆過濾器中。
②布隆過濾器依靠位圖(bitmap):相當于是一個以(bit)位為單位的數組,數組中每個單元只能存儲二進制數0或1。
③布隆過濾器作用:可以用于檢索一個元素是否在一個集合中。
④布隆過濾器存儲數據和查詢數據,初始的bitmap中數組中都是0。
存儲數據時:假設存儲id=1的數據,通過多個hash函數獲取hash值,根據hash計算數組的對應位置,將對應位置0改為1。
查詢數據時:使用相同hash函數獲取hash值,判斷對應位置是否都為1。
⑤布隆過濾器存在誤判。查詢數據時,根據hash函數獲取hash值時,可能出現重疊,也就是誤判,明明不存在的數據,被誤判存在。
誤判率:數組越小誤判率越大;數組越大誤判率越小,但是同時帶來了更多的內存消耗。誤判不可能不存在,一般設置誤判率為0.05(5%)。
在這里插入圖片描述
⑥優點:內存占用較少,沒有多余的key
⑦缺點:實現復雜,存在誤判。
⑧布隆過濾器實現方案:Redisson或Guava。

在這里插入圖片描述

4.緩存擊穿

緩存擊穿定義:當某一個key設置了過期時間,當key過期的時候,恰好這時間點對這個key有大量的并發請求過來,這些請求發現緩存過期,一般都會從后端 DB 加載數據并回設到緩存,這個時候大并發的請求可能會瞬間把 DB 壓垮。

5.緩存擊穿解決方案

(1)方案一:互斥鎖

假設有兩個線程。
線程1查詢緩存,未命中,緩存中沒有查詢需要的數據。接著獲取互斥鎖成功,線程1查詢數據庫,查詢之后返回數據給緩存,重建緩存數據。將返回的數據寫入緩存,最后釋放鎖。
線程2在線程1查詢過程中也發起查詢緩存,發現未命中,線程2嘗試獲取互斥鎖但是失敗。(線程1目前正獲取互斥鎖)線程2休眠一會兒后,再返回到發起查詢緩存,進行不斷的重試,直到線程1釋放互斥鎖,那么線程2就可以在查詢緩存中,緩存命中,返回數據。

第一,可以使用互斥鎖:當緩存失效時,不立即去load db,先使用如 Redis 的 SETNX 去設置一個互斥鎖。當操作成功返回時,再進行 load db的操作并回設緩存,否則重試get緩存的方法。

在這里插入圖片描述
特點:確保數據的強一致性,但性能低,且有可能產生死鎖的問題。

(2)方案二:邏輯過期

在這里插入圖片描述

在這里插入圖片描述

第二種方案是設置當前key邏輯過期,大概思路如下:1) 在設置key的時候,設置一個過期時間字段一塊存入緩存中,不給當前key設置過期時間;2) 當查詢的時候,從redis取出數據后判斷時間是否過期;3) 如果過期,則開通另外一個線程進行數據同步,當前線程正常返回數據,這個數據可能不是最新的。

特點:具有高可用性,性能比較高,但數據同步無法做到強一致,不能保證數據絕對一致。

在這里插入圖片描述

6.緩存雪崩

緩存雪崩是指在同一時段大量的緩存key同時失效或者Reids服務宕機,導致大量請求到達數據庫,帶來巨大壓力。與緩存擊穿的區別是:雪崩是很多key,而擊穿是某一個key緩存。

7.緩存雪崩的解決方案

(1)方案一:給不同的key的TTL(失效時間)添加隨機值

讓不同的key的過期時間是不一樣的就可以,例如可以在原有的失效時間上添加隨機值。這樣,每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。

(2)方案二:搭建Redis集群提高服務的可用性,例如哨兵模式、集群模式。
(3)方案三:給緩存業務添加降級限流策略,例如ngxin或spring cloud gateway。
(4)方案四:給業務添加多級緩存,例如Guava或Caffeine

在這里插入圖片描述

8.雙寫一致性

一定要設置前提,比如項目中要求一致性要求高,還是允許延遲一致,兩者是不同的。

(1)定義

雙寫一致性:當修改了數據庫的數據也要同時更新緩存的數據,緩存和數據庫的數據要保持一致。由于數據庫和緩存的讀寫操作可能并發執行,導致緩存與數據庫不一致,Redis采取不同策略來解決該問題。
在這里插入圖片描述

讀操作:緩存命中,直接返回數據;緩存未命中查詢數據庫,返回之前將數據寫入緩存,設定該數據的過期時間。

(2)雙寫一致性問題原因

①先刪除緩存,再操作數據庫

并發讀寫沖突:
當更新操作(如先刪緩存再更新數據庫)和查詢操作并發執行時,查詢可能讀取到舊數據并重新寫入緩存,導致緩存與數據庫不一致。
時序問題:更新操作刪除緩存后,數據庫尚未完成更新,此時查詢將舊數據重新加載到緩存。
?主從同步延遲:數據庫主從同步需要時間,從庫可能短暫存在舊數據,導致查詢結果不一致。

正常情況下:
假設初始緩存=10,數據庫=10;
線程1任務:對數據庫進行更新操作,將數據庫改為20。
線程2任務:查詢數據。
線程1先刪除緩存中存放的數據,現在緩存=空。然后線程1再去更新數據庫,將其改為20;目前數據庫=20,緩存=空。
在線程1更新完數據庫后。線程2現在要進行查詢操作。線程2查詢緩存,未命中,再查詢數據庫,查到20,將20寫入緩存。目前數據庫=20,緩存=20,兩者一致。
在這里插入圖片描述

沖突情況下:
假設初始緩存=10,數據庫=10;
線程1任務:對數據庫進行更新操作,將數據庫改為20。
線程2任務:查詢數據。
線程1先去刪除緩存中的數據。數據庫還沒有進行更改。目前數據庫=10,緩存=空。等線程1要去進行下一步更新數據庫時。線程2來了。
線程2查詢緩存未命中,緩存=空,接著線程2去查詢數據庫,發現數據庫=10,線程2就將該數據寫入緩存。目前數據庫=10,緩存=10。
線程2執行完查詢操作后,線程1接著來進行更新數據庫操作,將數據庫改為20。目前數據庫=20,緩存=10。數據不一致了,無法滿足雙寫一致性,出現了臟讀。
在這里插入圖片描述

②先操作數據庫,再刪除緩存。
正常情況下:
假設初始緩存=10,數據庫=10;
線程1任務:對數據庫進行更新操作,將數據庫改為20。
線程2任務:查詢數據。
此時線程1先進行數據庫操作,將數據庫改為20,再刪除緩存。目前數據庫=20,緩存=空。
接著線程2在線程1刪除緩存之后開始查詢數據,查詢緩存,未命中,去查詢數據庫得到20,接著寫入緩存。目前數據庫=20,緩存=20。兩者一致。
在這里插入圖片描述

沖突情況下:
正常情況下:
假設初始緩存=10,數據庫=10;
線程1任務:對數據庫進行更新操作,將數據庫改為20。
線程2任務:查詢數據。
線程2先查詢數據,查詢緩存,但是并發情況下,可能該數據過期了,線程2緩存未命中,將去查詢數據庫,獲得數據庫=10;接著線程1開始更新數據庫,數據庫=20,再刪除緩存,目前緩存=空;線程1執行完畢,線程2將查詢到的數據庫10寫入緩存,那么此時緩存=10,數據庫=20,雙寫不一致。

先刪緩存 vs 先更新數據庫:無論哪種順序,在高并發場景下都可能因線程切換導致臟數據殘留。

(3)雙寫一致性問題解決方案1—延遲雙刪

在寫操作采用延遲雙刪
在這里插入圖片描述

①延遲雙刪原理

先刪除緩存,然后進行更新操作,修改數據庫,更新操作完成后,延遲一定時間再次刪除緩存。

②為什么要刪除兩次緩存?

先刪除緩存,之后再刪除一次緩存,就是為了降低臟數據出現。

③為什么要延時呢?

因為數據庫主從同步需要時間,一般情況下數據庫是主從模式的,是讀寫分離的,所以需要延時,讓主節點把數據同步到從節點。

④延遲雙刪的實現方式

定時任務:通過 ScheduledExecutorService 的 schedule() 實現,指定時間延遲刪除Redis中的緩存。
消息隊列:在更新操作之后,設置一條刪除Redis中指定緩存的延遲消息,通過發送該延遲消息觸發二次刪除。

⑤延遲雙刪的特點

優點:
1)性能高,由于讀和寫是并發的,性能很高。
2)實現簡單:定時任務和消息隊列實現都比較簡單。
3)保證了數據最終一致性,最終數據是一致的。
缺點:
1)無法保證數據的強一致性,且延遲時間需根據業務調整:由于延遲刪除緩存的時刻可能與數據更新完畢(主從同步之后)的時刻間隔了不少時間,在這期間數據的一致性無法保障。

⑥延遲雙刪使用的場景

允許短暫不一致但對性能要求較高的場景(如文章瀏覽量統計)。

(4)雙寫一致性問題解決方案2—讀寫鎖機制
①讀寫鎖原理

通過Redission提供的共享鎖(讀鎖)和排他鎖(寫鎖)控制并發。

②實現過程。

共享鎖:對于刪除緩存操作即讀操作,加共享鎖,允許多線程讀,但阻塞寫操作。直到刪除緩存執行完之后,釋放鎖。
排他鎖:對于更新操作即寫操作,加排他鎖,阻塞其他讀寫操作,直到更新緩存之后,解鎖。
在這里插入圖片描述

③特點

優點:
1)強一致性保障。
缺點:
1)性能較低。

④使用場景

適用于對一致性要求極高但并發量適中的場景。

(5)異步消息隊列(MQ/Canal)
①MQ異步通知原理

數據庫更新完數據后,發送消息到MQ,而消費者也就是緩存服務器監聽到消息后,更新緩存。
MQ異步通知特點:
優點:解耦數據庫和緩存操作,支持最終一致性。
缺點:存在短暫延遲,高并發下會出現數據不一致的情況。
在這里插入圖片描述

②基于Canal的異步通知原理

Canal是基于MySQL的主從同步來實現的。
MySQL進行更新操作后,數據庫發生變化,將這種變化記錄于BinLog文件中,Canal監聽mysql的Binlog,然后解析binlog,發送消息給緩存,將數據變更情況告知給緩存服務器,接著再更新緩存。
Canal特點:
無代碼入侵,適用于復雜系統。

二進制日志(BinLog)記錄了所有的DDL(數據定義語言)語句和DML(數據操縱語言)語句,但不包括數據查詢(Select、show)語句。

在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

9.數據持久化策略

(1)RDB
①定義

RDB全稱Redis Database Backup file(Redis數據備份文件),也被叫做Redis數據快照。簡單來說就是把內存中的所有數據都記錄到磁盤中。當Redis實例故障重啟后,從磁盤讀取快照文件,恢復數據。
save和bgsave是人工手動備份
在這里插入圖片描述
Redis內部有觸發RDB的機制,可以在redis.conf文件中找到,格式如下:
在這里插入圖片描述

②RDB執行原理

簡單理解:
bgsave開始時,主進程通過調用fork()函數創建子進程,子進程共享主進程的內存數據,子進程是異步的,對主進程幾乎沒有阻塞。完成fork后,子進程讀取內存數據并寫入RDB文件,此時內存為只讀。fork采用copy-on-write技術,當主進程執行讀操作時,訪問共享內存;當主進程執行寫操作時,拷貝一份內存數據,得到數據副本,在數據副本上執行寫操作。

詳細解析:
bgsave開始時,主進程通過調用fork()函數創建子進程,子進程共享主進程的內存數據,子進程是異步的,對主進程幾乎沒有阻塞。
有一個進程,Redis的主進程,要去實現對redis的讀寫操作,要在內存中去操作。但是在linux系統中,所有的進程都沒有辦法直接操作物理內存,操作系統給每一個進程都分配了一個虛擬內存,主進程只能操作虛擬內存。操作系統會維護虛擬內存和物理內存之間的映射關系表,這個表被稱為頁表。主進程操作虛擬內存,虛擬內存基于頁表的映射,關聯到物理內存真正的存儲數據的位置,這樣就可以對物理內存進行讀和寫操作。
子進程會拷貝主進程中的頁表,也就是映射關系,子進程在操作自己的虛擬內存的時候,也會關聯到物理內存。那這就實現了子進程和主進程的內存空間共享。
子進程讀取到物理內存中的數據,就可以將數據寫入磁盤中,生成新的RDB文件,替換舊的RDB文件。
子進程在寫新的RDB文件時,主進程可能接收到寫請求,會產生沖突。為避免這個沖突,fork采用了copy-on-write技術,寫時復制。也就是將物理內存中的數據變為只讀,不可以寫。如果主進程接收到寫請求,主進程將物理內存中的數據拷貝一份,得到數據副本,在副本上進行寫操作。
頁表:記錄虛擬地址和物理地址的映射關系。

(2)AOF
①定義

AOF全稱為Append Only File(追加文件)。Redis處理的每一個寫命令(修改數據的命令,如set)都會存儲在AOF文件,可以看作是命令日志文件。
在這里插入圖片描述

②詳細解析AOF

AOF默認是關閉的,需要修改redis.conf配置文件來開啟AOF
在這里插入圖片描述
AOF的命令記錄的頻率也可以通過redis.conf文件來配
在這里插入圖片描述
一般在項目中,我們都會采用everysec。

因為是記錄命令,AOF文件會比RDB文件大的多。而且AOF會記錄對同一個key的多次寫操作,但只有最后一次寫操作才有意義。通過執行bgrewriteaof命令,可以讓AOF文件執行重寫功能,用最少的命令達到相同的效果。
在這里插入圖片描述

(3)RDB和AOF的對比

RDB和AOF各有自己的優缺點,如果對數據安全性要求較高,在實際開發中往往會結合兩者來使用。
在這里插入圖片描述
在這里插入圖片描述

10.數據過期策略

(1)數據過期定義

Redis對數據設置數據的有效時間,數據過期以后,就需要將數據從內存中刪除掉。可以按照不同的規則進行刪除,這種刪除規則就被稱之為數據的刪除策略(數據過期策略)。有兩種策略,惰性刪除和定期刪除。
Redis的過期刪除策略是惰性刪除+定期刪除兩種策略配合使用

(2)惰性刪除
①定義

惰性刪除:設置該key過期時間后,我們不去管它,當需要該key時,我們再檢查其是否過期,如果過期,我們就刪掉它,反之就返回該key。
在這里插入圖片描述

②優缺點

優點:對CPU友好,只會在使用該key時才會進行過期檢查,對于很多用不到的key不用浪費時間進行過期檢查。
缺點:對內存不友好,如果一個key已經過期,但是一直沒有使用,那么該key就會一直存在內存中,內存永遠不會釋放。

(3)定期刪除
①定義

定期刪除:每隔一段時間,我們就會對一些key進行檢查,刪除里面過期的key(從一定數量的數據庫中取出一定數量的隨機key進行檢查,并刪除其中的過期key)。不會存在已經過期的key還沒有被刪除的可能。

②清理模式

1)SLOW模式:是定時任務,執行頻率默認為10hz(每秒執行10次,每個執行周期是100毫秒),每次不超過25毫秒【時間這么短是因為在清理過程中,要盡可能少的去影響主進程操作】,可以通過修改配置文件redis.conf的hz選項來調整這個次數。
2)FAST模式:執行頻率不固定,但兩次間隔不低于2毫秒,每次耗時不超過1毫秒。【盡可能少的去影響主進程操作】

③優缺點

優點:可以通過限制刪除操作執行的時長和頻率來減少刪除操作對CPU的影響。另外定期刪除,也能有效釋放過期key占用的內存。
缺點:難以確定刪除操作執行的時長和頻率。
在這里插入圖片描述

11.數據淘汰策略

加入緩存過多,內存是有限的,內存被占滿了怎么辦?
問的就是數據淘汰策略。

(1)數據淘汰策略定義

當Redis中的內存不夠用時,此時在向Redis中添加新的key,那么Redis就會按照某一種規則將內存中的數據刪除掉,這種數據的刪除規則被稱之為內存的淘汰策略。

(2)八種淘汰策略
  1. noeviction: 不淘汰任何key,但是內存滿時不允許寫入新數據,默認就是這種策略。
    在這里插入圖片描述

  2. volatile-ttl: 對設置了TTL的key,比較key的剩余TTL值,TTL越小越先被淘汰

  3. allkeys-random:對全體key ,隨機進行淘汰。

  4. volatile-random:對設置了TTL的key ,隨機進行淘汰。

  5. allkeys-lru: 對全體key,基于LRU算法進行淘汰
    LRU:
    在這里插入圖片描述
    LFU:
    在這里插入圖片描述

在這里插入圖片描述

  1. volatile-lru: 對設置了TTL的key,基于LRU算法進行淘汰
  2. allkeys-lfu: 對全體key,基于LFU算法進行淘汰
  3. volatile-lfu: 對設置了TTL的key,基于LFU算法進行淘汰

(3)淘汰策略使用建議

1)優先使用allkeys-lru策略,充分利用LRU算法的優勢,把最近最常訪問的數據留在緩存中。如果業務有明顯的冷熱數據區分,建議使用。
2)如果業務中數據訪問頻率差別不大,沒有明顯冷熱數據區分,建議使用allkeys-random,隨機選擇淘汰。
3)如果業務中有置頂的需求,可以使用volatile-lru策略,同時置頂數據不設置過期時間,這些數據就一直不被刪除,會淘汰其他設置過期時間的數據。
4)如果業務中有短時高頻訪問的數據,可以使用allkeys-lfu或volatile-flu策略。

在這里插入圖片描述
在這里插入圖片描述

(二)分布式鎖

Redis分布式鎖,是如何實現的。
需要結合項目中的業務進行回答,通常情況下,分布式鎖使用的場景:集群情況下的定時任務、搶單、冪等性場景。

1.搶券場景

在這里插入圖片描述
在這里插入圖片描述

正常情況下:
線程1執行完之后,線程2開始執行,查詢優惠券,不會出現問題。
在這里插入圖片描述
沖突情況:
假設此時優惠券庫存=1,線程1查詢到優惠券庫存=1,而此時線程2也查詢到優惠券庫存=1,接著線程1扣減庫存,庫存=0,線程1執行結束。線程2也扣減庫存,那么此時,庫存=-1,出現了超賣現象。
在這里插入圖片描述
解決方案:
加鎖。但是這種解決方案只適合單體項目,并且只開啟一臺服務。
在這里插入圖片描述
在這里插入圖片描述

但是我們的項目為了支撐更多的并發請求,往往將服務做成集群部署,同一份代碼,部署在多個tomcat中.
當用戶請求的時候,使用nginx做反向代理,負載均衡到各個請求去訪問各個服務。那么此時就不適合加鎖了。
在這里插入圖片描述
在這里插入圖片描述
此時使用的是本地的鎖,只能解決同一個jvm下線程的互斥,解決不了多個jvm下線程的互斥。所以在集群的情況下,就不能使用本地的鎖來解決,需要使用外部的鎖來解決,也就是分布式鎖。
在這里插入圖片描述
使用了分布式鎖以后,針對集群部署的項目,在服務器8080下,線程1加鎖之后,分布式鎖中就會記錄服務器8080的線程1持有鎖。那么服務器8081的線程1試圖獲取鎖會失敗,別的線程都會獲取鎖失敗,只有等8080的線程1釋放鎖以后才可以獲取到鎖。

2.分布式鎖的原理

(1)定義

Redis實現分布式鎖主要利用Redis的setnx命令。setnx是 SET if not exists(如果不存在,則SET)的簡寫。
在這里插入圖片描述
其中EX表示key的過期時間,在一條命令中設置可以保證原子性,不設置EX可能會導致死鎖的問題。
在這里插入圖片描述

(2)redis分布式鎖控制鎖的有效時長

Redis實現分布式鎖如何合理地控制鎖的有效時長?

方案一:根據業務執行時間預估。但是可能出現網絡不穩定服務宕機的情況,也會自動釋放鎖,導致業務的原子性無法得到滿足,業務不能很好地被執行。【該方案有欠缺】
方案二:給鎖續期。單獨開一個線程監控業務完成情況,如果業務不夠時間完成,則延長加鎖時間。【也有欠缺,很浪費】

最好的解決方案:
redisson實現的分布式鎖

(3)redisson實現的分布式鎖執行流程

線程1獲取鎖,加鎖成功之后可以直接操作Redis執行業務,同時加鎖成功之后會增加一個watch dog看門狗進行監聽【每隔(releaseTime/3)的時間做一次續期】,看門狗會不斷地去監聽持有鎖的線程,releaseTime就是鎖的過期時間(默認30秒),也就是每隔10s,看門狗將鎖的過期時間重新設置為30s。當業務執行完成以后,手動地釋放鎖,并且告訴看門狗不需要再進行監聽了。
線程2也嘗試獲取鎖,看是否加鎖成功,但是線程1正在持有鎖。在線程2中設置了一個while循環,不斷嘗試獲取鎖。如果線程1在很短的時間內釋放了鎖,那線程2可以獲得加鎖。在這個while循環設置了一個閾值,如果線程2嘗試獲取鎖達到這個閾值,那么線程2會獲取鎖失敗。
一般情況下,業務執行非常快,所以線程2不需要等待線程1很久。加入了這個while循環等待機制,在高并發的情況下,可以很大程度上增加分布式鎖的使用性能。【這就是redisson的重試機制】
加鎖、設置過期時間等操作都是基于lua腳本完成。
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

(4)Redisson實現的分布式鎖—可重入

在這里插入圖片描述
add1方法調用add2方法,其中add2方法是可以獲取鎖成功的,redisson實現的分布式鎖是可以重入的。add1和add2方法都是同一個線程,每個線程在執行的時候,都有一個唯一的線程ID作為標識,加鎖的時候根據這個線程ID來判斷,如果是同一個線程,那么可以獲取鎖成功。如果不是一個線程,則獲取不成功。

可重入的好處:當業務比較復雜的時候,鎖的粒度比較細的時候,就可以用到重入。可以避免多個鎖之間產生死鎖的問題。

重入的實現:在存儲鎖數據的時候,采用hash結構,利用hash結構記錄線程id和重入次數。這個key根據自己的業務進行命名,也就是鎖的命名,field存儲的是持有鎖線程的唯一標識(線程ID),value存儲的是當前線程重入的次數。

上面的代碼中,加鎖之后,存入到field中,將線程標識,然后add1中加鎖,value=1,之后調用add2,先去field查看線程ID是否一致,一致的話,add2加鎖成功,value=2。add2執行完業務之后,釋放鎖value-1=1。之后,add1執行完業務之后,釋放鎖value-1=0;
在這里插入圖片描述
在這里插入圖片描述

(5)Redisson實現的分布式鎖—主從一致性

Redis的主從集群架構,有主節點和從節點,主節點主要負責寫操作(更新操作),從節點負責對外的讀操作,當主節點發生了寫操作之后,就要將數據同步到從節點,因為要保證主從數據的同步。目前有java應用創建了一個分布式鎖,因為是寫操作,先找到主節點,將數據寫入到主節點中,正常的話就是主節點同步到從節點,但是還沒來得及同步數據,主節點宕機了。Redis提供的哨兵模式會在兩個從節點中,選擇一個從節點充當主節點,那么新的線程來了以后,會直接請求新的主節點,嘗試獲取鎖,會加鎖成功。那么這兩個線程會同時持有同一把鎖,那么此時就喪失了鎖的互斥性,可能會出現臟數據的問題。
在這里插入圖片描述

解決方案:
redisson提供了RedLock紅鎖:不能只在一個redis實例上創建鎖,應該是在多個redis實例上創建鎖(n/2+1),避免在一個redis實例上加鎖。(n代表的是redis節點的數量)
下面有3個節點,那么3/2+1=2.5,至少要創建大于等于2個鎖。
紅鎖也是有缺陷的,在實際項目中很少使用,主要是加了紅鎖之后,實現起來很復雜,尤其是在高并發的情況下,性能變得很差,因為需要提供多個獨立的redis節點,運維繁瑣。不是很支持用這個。
Redis集群的思想是AP思想,優先保證高可用性,可以做到最終一致。如果要做到強一致性,則考慮使用zookeeper的CP思想

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

(三)計數器

(四)保存token

(五)消息隊列

(六)延遲隊列

二、其他面試題

(一)集群

Redis集群有哪些方案?知道嘛
三種:主從復制、哨兵模式、分片集群。D
在這里插入圖片描述

1.主從復制

(1)定義

主從復制:單節點Redis的并發能力是有上限的,要進一步提高Redis的并發能力,就需要搭建主從集群,實現讀寫分離。
主節點復制寫操作,從節點復制讀操作,因為Redis一般是讀多寫少,多個從節點,增加了并發能力。主節點需要將數據同步給從節點。
在這里插入圖片描述

(2)主從數據同步原理
①主從全量同步

執行流程:

  1. 從節點執行replicaof命令,建立連接。
  2. 接著從節點向主節點請求數據同步。
  3. 主節點接收到之后,會判斷該從節點是否是第一次同步。
  4. 如果該從節點是第一次和主節點建立連接,那么將返回主節點的數據版本信息給從節點。
  5. 從節點保持版本信息,主從版本保持一致。
  6. 接著主節點會執行bgsave,生成RDB文件。
  7. RDB文件生成之后,主節點向從節點發送RDB文件。
  8. 從節點接收到RDB文件后,會清空本地數據,加載RDB文件。
  9. 那么主節點在生成RDB文件時,可能接收到其他更改請求,那么就會導致主從數據不一致的問題。為了解決這個問題,主節點會記錄RDB期間的所有命令,生成一個日志文件repl_baklog。
  10. 主節點 再將這個日志文件發送給從節點,發送repl_baklog中的命令給從節點。
  11. 從節點接收到該日志文件之后,會執行接收到的命令,那么就可以實現主從數據的完全同步。

問題解決:
1)主節點是怎么判斷從節點是否是第一次同步呢?
從節點發起連接請求時,將自己的replid發送給主節點,主節點通過判斷該從節點的replid和自己的replid是否一致,如果不一樣,說明這個從節點是第一次進行連接。
如果一致,表明該從節點之前已經建立過連接。那么主節點就不會再生成RDB文件,直接執行repl-baklog的命令。
2)不是第一次建立連接(第二次、第三次同步),那么該執行多少這個日志文件中的命令呢?
根據offset,從節點發送自己的offset給主節點,主節點判斷從節點的offset和自己是否一致,不一致的話,將不一致的部分發送給同節點進行同步。

在這里插入圖片描述
在這里插入圖片描述

②主從增量同步(salve重啟或后期數據變化)

從節點重啟之后,向主節點發起同步請求,附帶有replid和offset兩個值,主節點master判斷請求replid是否一致,如果是第一次,則返回主節點replid和offset給從節點。如果不一致,不是第一次連接,回復continue,主節點去repl_baklog中獲取offset后的數據,發送offset后的命令給從節點,從節點執行命令。
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

主從復制保證不了redis的高可用,因為一旦主節點宕機之后,就無法執行寫操作。在這里插入圖片描述

2.哨兵模式

(1)哨兵的作用

Redis提供了哨兵機制來實現主從集群的自動故障恢復。哨兵也是redis節點,也由多個節點組成了集群,一般情況下至少要部署三臺哨兵。

  1. 監控:哨兵會不斷檢查主節點和從節點是否按預期工作,監控集群狀態。
  2. 自動故障恢復:如果主節點故障,哨兵會將一個從節點提升為主節點。當故障實例恢復后,也以新的主節點為主。
  3. 通知:哨兵充當Redis客戶端的服務發現來源,當集群發生故障轉移時,哨兵會將最新信息推送給Redis的客戶端。例如,主節點宕機,更換新的主節點之后,哨兵會將新的主節點告訴給Redis的客戶端,Redis的客戶端會自動連接上新的主節點進行工作。

極大可能保持了Redis的高可用性。
在這里插入圖片描述

(2)服務狀態監控

哨兵(Sentinel)基于心跳機制監測服務狀態,每隔1秒向集群的每個實例發送ping命令:
在這里插入圖片描述
在這里插入圖片描述

其中指定數量quorum也是可以設置的,最好超過哨兵實例數量的一半。
哨兵選主規則:
在這里插入圖片描述

(3)Redis集群(哨兵模式)腦裂

如果主節點和哨兵處于不同的網絡分區,那哨兵只能去監測從節點,無法監測到主節點,那么哨兵就會在從節點當中根據選主規則,選擇新的主節點。但是原本的主節點還存在,只是網絡出現問題,客戶端還可以正常連接。那這樣子,就會出現兩個master主節點,就像大腦分裂了一樣。這個就是腦裂。
在這里插入圖片描述

腦裂帶來的問題:
由于原本的主節點還存在,客戶端仍然在向原來的主節點寫入數據,但是其他節點無法同步數據,因為網絡異常。
如果網絡恢復之后,哨兵會將原來的主節點強制降為從節點,依附新的主節點,那么這個從節點就會從新的主節點同步數據,就會將自身原本的數據清空,那腦裂之前,客戶端寫入的數據就丟失了。腦裂帶來數據丟失。
解決方案:
主節點必須要有最少一個從節點,才可以接收客戶端的數據,否則直接拒絕請求。
redis中有兩個配置參數:
min-replicas-to-write 1 表示最少的salve節點為1個
min-replicas-max-lag 5 表示數據復制和同步的延遲不能超過5秒
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

3.分片集群

主從和哨兵可以解決高可用、高并發讀的問題【主從復制解決高并發讀的問題,哨兵模式解決高可用問題】。但是依然有兩個問題沒有解決:
海量數據存儲問題
高并發寫的問題。(采用分片集群解決)

(1)分片集群特征

使用分片集群可以解決上述問題,分片集群特征:
1.集群中有多個master,每個master保存不同數據
2.每個master都可以有多個slave節點
3.master之間通過ping監測彼此健康狀態。每個master互相之間起到哨兵作用。
4.客戶端請求可以訪問集群任意節點,最終都會被轉發到正確節點
在這里插入圖片描述

(2)數據讀寫

Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 個哈希槽,每個 key通過 CRC16 校驗后對 16384 取模來決定放置哪個槽,集群的每個節點負責一部分 hash 槽。
可以設置key的有效部分,按照一定的規則,key選擇存儲到哪一個節點中。有相同的業務數據,都想進入到redis的同一個節點下,就可以設置相同的有效部分來存儲。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

(二)事務

(三)Redis為什么這么快

在這里插入圖片描述
在這里插入圖片描述

1.用戶空間和內核空間

Linux系統中一個進程使用的內存情況劃分兩部分:內核空間、用戶空間。
用戶空間只能執行受限的命令(Ring3),而且不能直接調用系統資源,必須通過內核提供的接口來訪問。
內核空間可以執行特權命令(Ring0),調用一切系統資源。
在這里插入圖片描述
在這里插入圖片描述
需要想辦法減少等待時間,以及減少用戶空間和內核空間之間數據的拷貝。

2.阻塞IO

阻塞IO就是兩個階段都必須阻塞等待。比較耗時,性能不高。
在這里插入圖片描述

3.非阻塞IO

在這里插入圖片描述

4.IO多路復用(Redis底層使用的就是這個)

IO多路復用:是利用單個線程來同時監聽多個Socket ,并在某個Socket可讀、可寫時得到通知,從而避免無效的等待,充分利用CPU資源。
這個Socket指的是客戶端的連接。
用戶進程先調用select函數,可以監聽一個Socket集合,里面包含了多個Sockets

在這里插入圖片描述
IO多路復用是利用單個線程來同時監聽多個Socket ,并在某個Socket可讀、可寫時得到通知,從而避免無效的等待,充分利用CPU資源。不過監聽Socket的方式、通知的方式又有多種實現,常見的有:
select
poll
epoll。
在這里插入圖片描述

5.Redis網絡模型

單線程的
在這里插入圖片描述
IO多路復用監聽每個客戶端的連接,每個連接可能處理不同的事件,IO多路復用只是針對已經就緒的連接,將這些事件進行派發。Redis中提供了多個事件處理器,這些事件處理器分別用于實現不同的網絡通信請求。比如連接應答處理器,可以處理客戶端請求的應答;命令回復處理器,處理客戶端響應的;命令請求處理器,接收客戶端的參數,接收請求數據,將數據轉為Redis命令,選擇并執行命令,把結果寫入緩沖隊列,放入緩沖區。這個是單線程的。
影響性能的永遠是IO。也就是網路的讀寫,為了解決這個問題,引入了多線程。命令回復處理器也就是往外寫,也加入了多線程。加入了多線程以后,大大提高了Redis對客戶端的速度,主要是減少了網絡IO導致的性能變慢的影響。
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

1.Redis的數據持久化策略有哪些?
2.什么是緩存穿透,怎么解決?
3.什么是布隆過濾器?
4.什么是緩存擊穿,怎么解決?
5.什么是緩存雪崩,怎么解決?
6.Redis雙寫問題是什么?
7.Redis分布式鎖如何實現?
8.Redis實現分布式鎖如何合理的控制鎖的有效時長?
9.Reids的數據過期策略有哪些?
10.Redis的數據淘汰策略有哪些?
11.Redis集群有哪些方案,知道嗎?
12.什么是Redis主從同步?
13.你們使用Redis是單點還是集群?哪種集群?
14.Redis分片集群中數據是怎么存儲和讀取的?
15.Redis集群腦裂是什么?
16.怎么保證Redis的高并發高可用?
17.你們用過Redis的事務嗎?事務的命令有哪些?
18.Redis是單線程,但是為什么還那么快?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/73621.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/73621.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/73621.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Boot使用線程池創建多線程

在 Spring Boot 2 中,可以使用 Autowired 注入 線程池(ThreadPoolTaskExecutor 或 ExecutorService),從而管理線程的創建和執行。以下是使用 Autowired 方式注入線程池的完整示例。 1. 通過 Autowired 注入 ThreadPoolTaskExecuto…

9、交付手段-強化肌肉記憶(隨身工具箱)

一、交付工具箱 當臨時遇到各類交付棘手問題時,大腦里記住交付工具的使用場景,有利于快速決策,將這些工具轉為肌肉記憶,能夠快速靈活處理交付中的各類問題,蛻變為交付之星 1、復雜項目:WBS分解、日站會、…

【概念】Node.js,Express.js MongoDB Mongoose Express-Validator Async Handler

1. Node.js 定義:Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行時環境,允許你在服務器端運行 JavaScript 代碼。作用:它使得開發者可以使用 JavaScript 編寫服務器端代碼,從而實現前后端使用同一種語言。比喻&#xff1a…

【GPT入門】第22課 langchain LCEL介紹

【GPT入門】第22課 langchain LCEL介紹 1. LCEL介紹與特點2. 原生API與LCEL的對比2. 簡單demo 1. LCEL介紹與特點 LCEL 即 LangChain Expression Language,是 LangChain 推出的一種聲明式語言,用于簡化和優化在 LangChain 框架內構建復雜鏈和應用的過程…

數據結構——單鏈表list

前言:大家好😍,本文主要介紹數據結構——單鏈表 目錄 一、單鏈表 二、使用步驟 1.結構體定義 2.初始化 3.插入 3.1 頭插 3.2 尾插 3.3 按位置插 四.刪除 4.1頭刪 4.2 尾刪 4.3 按位置刪 4.4按值刪 五 統計有效值個數 六 銷毀…

堆排序:力扣215.數組中的第K個大元素

一、問題描述 在一個整數數組 nums 中,需要找出第 k 個最大的元素。這里要注意,我們要找的是數組排序后的第 k 個最大元素,而不是第 k 個不同的元素。例如,對于數組 [3,2,1,5,6,4],當 k 2 時,第 2 個最大…

C語言(25)

一.數據在內存中的存儲 1.整數在內存中的存儲 整數在內存中以二進制的形式儲存,分別為原碼,補碼,反碼 有符號的整數,在上述三種形式都有符號位和數值位兩個部分,符號位為0是正數,1是負數,最高…

鴻蒙開發-一多開發之媒體查詢功能

在HarmonyOS中,使用ArkTS語法實現響應式布局的媒體查詢是一個強大的功能,它允許開發者根據不同的設備特征(如屏幕尺寸、屏幕方向等)動態地調整UI布局和樣式。以下是一個使用媒體查詢實現響應式布局的實例: 1. 導入必要…

Docker運行hello-world鏡像失敗或超時:Unable to find image ‘hello-world:latest‘ locally Trying to pull reposi

Docker運行hello-world鏡像失敗或超時,報錯:Unable to find image ‘hello-world:latest’ locally Trying to pull repository docker.io/library/hello-world … /usr/bin/docker-current: missing signature key. See ‘/usr/bin/docker-current run …

MySQL連接較慢原因分析及解決措施

文章目錄 整體說明一、問題現象二、問題分析2.1、DNS反向解析問題2.2、網絡問題2.3、SSL/TLS協商問題2.4、自動補全的延遲 三、問題解決 摘要: MySQL連接較慢原因分析及解決措施 關鍵詞: MySQL、連接緩慢、客戶端、參數設置 整體說明 在使用MySQL的時候…

doris:安全概覽

oris 提供以下機制管理數據安全: 身份認證:Doris 支持用戶名/密碼與 LDAP 認證方式。 內置認證:Doris 內置了用戶名/密碼的認證方式,可以自定義密碼策略; LDAP 認證:Doris 可以通過 LDAP 服務集中管理用戶…

C++之文字修仙小游戲

1 效果 1.1 截圖 游戲運行: 存檔: 1.2 游玩警告 注意!不要修改裝備概率,裝備的概率都是湊好的數字。如果想要速升,修改靈石數量 2 代碼 2.1 代碼大綱 1. 游戲框架與初始化 控制臺操作:通過 gotoxy() …

Docker安裝部署RabbitMQ

Docker安裝部署RabbitMQ 本文介紹了如何在Linux(CentOS 7)系統環境下的Docker上安裝部署RabbitMQ的詳細過程。 目錄 Docker安裝部署RabbitMQ一、環境準備1.Linux環境2.Docker3.停止并移除現有的 RabbitMQ 鏡像和容器 二、安裝部署RabbitMQ1.拉取 RabbitM…

【MyBatis Plus 邏輯刪除詳解】

文章目錄 MyBatis Plus 邏輯刪除詳解前言什么是邏輯刪除?MyBatis Plus 中的邏輯刪除1. 添加邏輯刪除字段2. 實體類的配置3. 配置 MyBatis Plus4. 使用邏輯刪除5. 查詢邏輯刪除的記錄 MyBatis Plus 邏輯刪除詳解 前言 MyBatis Plus 是一個強大的持久化框架&#xf…

線性代數(1)用 excel 計算雞兔同籠

線性代數excel計算雞兔同籠 案例:雞兔同籠問題的三種解法(遞進式教學)一、問題描述二、方程式解法(基礎版)步驟解析 三、線性代數解法(進階版)1. 方程組轉化為矩陣形式2. 矩陣求解(逆…

Flask中使用WTForms處理表單驗證

在 Flask 中,WTForms 是一個用于 處理表單驗證 的庫,可以與 Flask 結合,提供表單驗證、數據清理、錯誤提示等功能。 1. 安裝 Flask-WTF 首先安裝 Flask-WTF: pip install Flask-WTFFlask-WTF 是 WTForms 的 Flask 擴展&#xff…

24.策略模式實現日志

日志的介紹 計算機中的日志是記錄系統和軟件運行中發送事件的文件,主要作用是監控運行狀態、記錄異常信息,幫助快速定位問題并支持程序員進行問題修復。它是系統維護、故障排查和安全管理的重要工具。 日志格式以下幾個指標是必須得有的: ?…

【網絡】簡單的 Web 服務器架構解析,包含多個服務和反向代理的配置,及非反向代理配置

這張圖片描述了一個簡單的 Web 服務器架構,包含多個服務和反向代理的配置。以下是對每個部分的詳細解釋,幫助你理解其中的技術內容: 1. Web Server: ifn666.com 這是你的主域名(ifn666.com),所有服務都通過…

???????大語言模型安全風險分析及相關解決方案

大語言模型的安全風險可以從多個維度進行分類。 從輸入輸出的角度來看,存在提示注入、不安全輸出處理、惡意內容生成和幻覺錯誤等風險; 從數據層面來看,訓練數據中毒、敏感信息泄露和模型反演攻擊是主要威脅; 模型自身則面臨拒絕服務和盜竊的風險; 供應鏈和插件的不安全引…

貪心算法——c#

貪心算法通俗解釋 貪心算法是一種"每一步都選擇當前最優解"的算法策略。它不關心全局是否最優,而是通過局部最優的累積來逼近最終解。優點是簡單高效,缺點是可能無法得到全局最優解。 一句話秒懂 自動售貨機找零錢:用最少數量的…