一、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)八種淘汰策略
-
noeviction
: 不淘汰任何key,但是內存滿時不允許寫入新數據,默認就是這種策略。
-
volatile-ttl
: 對設置了TTL的key,比較key的剩余TTL值,TTL越小越先被淘汰 -
allkeys-random
:對全體key ,隨機進行淘汰。 -
volatile-random
:對設置了TTL的key ,隨機進行淘汰。 -
allkeys-lru
: 對全體key,基于LRU算法進行淘汰
LRU:
LFU:
volatile-lru
: 對設置了TTL的key,基于LRU算法進行淘汰allkeys-lfu
: 對全體key,基于LFU算法進行淘汰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)主從數據同步原理
①主從全量同步
執行流程:
- 從節點執行
replicaof
命令,建立連接。 - 接著從節點向主節點請求數據同步。
- 主節點接收到之后,會判斷該從節點是否是第一次同步。
- 如果該從節點是第一次和主節點建立連接,那么將返回主節點的數據版本信息給從節點。
- 從節點保持版本信息,主從版本保持一致。
- 接著主節點會執行bgsave,生成RDB文件。
- RDB文件生成之后,主節點向從節點發送RDB文件。
- 從節點接收到RDB文件后,會清空本地數據,加載RDB文件。
- 那么主節點在生成RDB文件時,可能接收到其他更改請求,那么就會導致主從數據不一致的問題。為了解決這個問題,主節點會記錄RDB期間的所有命令,生成一個日志文件repl_baklog。
- 主節點 再將這個日志文件發送給從節點,發送repl_baklog中的命令給從節點。
- 從節點接收到該日志文件之后,會執行接收到的命令,那么就可以實現主從數據的完全同步。
問題解決:
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節點,也由多個節點組成了集群,一般情況下至少要部署三臺哨兵。
- 監控:哨兵會不斷檢查主節點和從節點是否按預期工作,監控集群狀態。
- 自動故障恢復:如果主節點故障,哨兵會將一個從節點提升為主節點。當故障實例恢復后,也以新的主節點為主。
- 通知:哨兵充當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是單線程,但是為什么還那么快?