天天看點

Redis面試題彙總大全

1. Sentinel啟動初始化過程

  • 1)初始化伺服器。

    因為Sentinel本質上隻是一個運作在特殊模式下的Redis伺服器,是以啟動Sentinel的第一步,就是初始化一個普通的Redis伺服器。因為Sentinel執行的工作和普通Redis伺服器執行的工作不同,是以Sentinel的初始化過程和普通Redis伺服器的初始化過程并不完全相同

  • 2)将普通Redis伺服器使用的代碼替換成Sentinel專用代碼。

    将一部分普通Redis伺服器使用的代碼替換成Sentinel專用代碼,比如說,普通Redis伺服器使用redis.h/REDIS_SERVERPORT常量的值作為伺服器端口,而Sentinel則使用sentinel.c/REDIS_SENTINEL_PORT常量的值作為伺服器端口。普通Redis伺服器使用redis.c/redisCommandTable作為伺服器的指令表,而Sentinel則使用sentinel.c/sentinelcmds作為伺服器的指令。

  • 3)初始化Sentinel狀态。

    伺服器會初始化一個sentinel.c/sentinelState結構(後面簡稱“Sentinel狀态”),這個結構儲存了伺服器中所有和Sentinel功能有關的狀态

  • 4)根據給定的配置檔案,初始化Sentinel狀态的master屬性。

    Sentinel狀态中的masters字典記錄了所有被Sentinel監視的主伺服器的相關資訊,其中:❑字典的鍵是被監視主伺服器的名字。❑而字典的值則是被監視主伺服器對應的sentinel.c/sentinelRedisInstance結構。

  • 5)建立連向主伺服器的網絡連接配接。

    初始化Sentinel的最後一步是建立連向被監視主伺服器的網絡連接配接, 對于每個被Sentinel監視的主伺服器來說,Sentinel會建立兩個連向主伺服器的異步網絡連接配接:❑一個是指令連接配接,這個連接配接專門用于向主伺服器發送指令,并接收指令回複。❑另一個是訂閱連接配接,這個連接配接專門用于訂閱主伺服器的__sentinel__:hello頻道。

    為什麼有兩個連接配接?

    在Redis目前的釋出與訂閱功能中,被發送的資訊都不會儲存在Redis伺服器裡面,如果在資訊發送時,想要接收資訊的用戶端不線上或者斷線,那麼這個用戶端就會丢失這條資訊。是以,為了不丢失__sentinel__:hello頻道的任何資訊,Sentinel必須專門用一個訂閱連接配接來接收該頻道的資訊。另一方面,除了訂閱頻道之外,Sentinel還必須向主伺服器發送指令,以此來與主伺服器進行通信,是以Sentinel還必須向主伺服器建立指令連接配接。因為Sentinel需要與多個執行個體建立多個網絡連接配接,是以Sentinel使用的是異步連接配接。

2. Sentinel擷取主伺服器資訊

Sentinel預設會以每十秒一次的頻率,通過指令連接配接向被監視的主伺服器發送INFO指令,并通過分析INFO指令的回複來擷取主伺服器的目前資訊。

通過分析主伺服器傳回的INFO指令回複,Sentinel可以擷取以下兩方面的資訊:

❑一方面是關于主伺服器本身的資訊,包括run_id域記錄的伺服器運作ID,以及role域記錄的伺服器角色;

❑另一方面是關于主伺服器屬下所有從伺服器的資訊,每個從伺服器都由一個"slave"字元串開頭的行記錄,每行的ip=域記錄了從伺服器的IP位址,而port=域則記錄了從伺服器的端口号。根據這些IP位址和端口号,Sentinel無須使用者提供從伺服器的位址資訊,就可以自動發現從伺服器。

根據run_id域和role域記錄的資訊,Sentinel将對主伺服器的執行個體結構進行更新。

至于主伺服器傳回的從伺服器資訊,則會被用于更新主伺服器執行個體結構的slaves字典,這個字典記錄了主伺服器屬下從伺服器的名單:❑字典的鍵是由Sentinel自動設定的從伺服器名字,格式為ip:port:如對于IP位址為127.0.0.1,端口号為11111的從伺服器來說,Sentinel為它設定的名字就是127.0.0.1:11111。❑至于字典的值則是從伺服器對應的執行個體結構:比如說,如果鍵是127.0.0.1:11111,那麼這個鍵的值就是IP位址為127.0.0.1,端口号為11111的從伺服器的執行個體結構。

注意主伺服器執行個體結構和從伺服器執行個體結構之間的差別:❑主伺服器執行個體結構的flags屬性的值為SRI_MASTER,而從伺服器執行個體結構的flags屬性的值為SRI_SLAVE。❑主伺服器執行個體結構的name屬性的值是使用者使用Sentinel配置檔案設定的,而從伺服器執行個體結構的name屬性的值則是Sentinel根據從伺服器的IP位址和端口号自動設定的。

3. Sentinel擷取從伺服器的資訊

當Sentinel發現主伺服器有新的從伺服器出現時,Sentinel除了會為這個新的從伺服器建立相應的執行個體結構之外,Sentinel還會建立連接配接到從伺服器的指令連接配接和訂閱連接配接。

根據INFO指令的回複,Sentinel會提取出以下資訊:❑從伺服器的運作ID run_id。❑從伺服器的角色role。❑主伺服器的IP位址master_host,以及主伺服器的端口号master_port。❑主從伺服器的連接配接狀态master_link_status。❑從伺服器的優先級slave_priority。❑從伺服器的複制偏移量slave_repl_offset。根據這些資訊,Sentinel會對從伺服器的執行個體結構進行更新。

4. Sentinel向主伺服器和從伺服器發送資訊

在預設情況下,Sentinel會以每兩秒一次的頻率,通過指令連接配接向所有被監視的主伺服器和從伺服器的__sentinel__:hello頻道發送一條資訊

5. Sentinel接收來自主伺服器和從伺服器的頻道資訊

當Sentinel與一個主伺服器或者從伺服器建立起訂閱連接配接之後,Sentinel就會通過訂閱連接配接,向伺服器發送以下指令:Sentinel對__sentinel__:hello頻道的訂閱會一直持續到Sentinel與伺服器的連接配接斷開為止。這也就是說,對于每個與Sentinel連接配接的伺服器,Sentinel既通過指令連接配接向伺服器的__sentinel__:hello頻道發送資訊,又通過訂閱連接配接從伺服器的__sentinel__:hello頻道接收資訊

對于監視同一個伺服器的多個Sentinel來說,一個Sentinel發送的資訊會被其他Sentinel接收到,這些資訊會被用于更新其他Sentinel對發送資訊Sentinel的認知,也會被用于更新其他Sentinel對被監視伺服器的認知。

6. 更新sentinels字典

Sentinel為主伺服器建立的執行個體結構中的sentinels字典儲存了除Sentinel本身之外,所有同樣監視這個主伺服器的其他Sentinel的資料:❑sentinels字典的鍵是其中一個Sentinel的名字,格式為ip:port,比如對于IP位址為127.0.0.1,端口号為26379的Sentinel來說,這個Sentinel在sentinels字典中的鍵就是"127.0.0.1:26379"。❑sentinels字典的值則是鍵所對應Sentinel的執行個體結構,比如對于鍵"127.0.0.1:26379"來說,這個鍵在sentinels字典中的值就是IP為127.0.0.1,端口号為26379的Sentinel的執行個體結構。當一個Sentinel接收到其他Sentinel發來的資訊時(我們稱呼發送資訊的Sentinel為源Sentinel,接收資訊的Sentinel為目标Sentinel),目标Sentinel會從資訊中分析并提取出以下兩方面參數:❑與Sentinel有關的參數:源Sentinel的IP位址、端口号、運作ID和配置紀元。❑與主伺服器有關的參數:源Sentinel正在監視的主伺服器的名字、IP位址、端口号和配置紀元。根據資訊中提取出的主伺服器參數,目标Sentinel會在自己的Sentinel狀态的masters字典中查找相應的主伺服器執行個體結構,然後根據提取出的Sentinel參數,檢查主伺服器執行個體結構的sentinels字典中,源Sentinel的執行個體結構是否存在:❑如果源Sentinel的執行個體結構已經存在,那麼對源Sentinel的執行個體結構進行更新。❑如果源Sentinel的執行個體結構不存在,那麼說明源Sentinel是剛剛開始監視主伺服器的新Sentinel,目标Sentinel會為源Sentinel建立一個新的執行個體結構,并将這個結構添加到sentinels字典裡面。

Sentinel 127.0.0.1:26379為主伺服器127.0.0.1:6379建立的執行個體結構,以及結構中的sentinels字典。

一個Sentinel可以通過分析接收到的頻道資訊來獲知其他Sentinel的存在,并通過發送頻道資訊來讓其他Sentinel知道自己的存在,是以使用者在使用Sentinel的時候并不需要提供各個Sentinel的位址資訊,監視同一個主伺服器的多個Sentinel可以自動發現對方。

7. 建立連向其他Sentinel的指令連接配接

當Sentinel通過頻道資訊發現一個新的Sentinel時,它不僅會為新Sentinel在sentinels字典中建立相應的執行個體結構,還會建立一個連向新Sentinel的指令連接配接,而新Sentinel也同樣會建立連向這個Sentinel的指令連接配接,最終監視同一主伺服器的多個Sentinel将形成互相連接配接的網絡:Sentinel A有連向Sentinel B的指令連接配接,而Sentinel B也有連向Sentinel A的指令連接配接。使用指令連接配接相連的各個Sentinel可以通過向其他Sentinel發送指令請求來進行資訊交換,

Sentinel在連接配接主伺服器或者從伺服器時,會同時建立指令連接配接和訂閱連接配接,但是在連接配接其他Sentinel時,卻隻會建立指令連接配接,而不建立訂閱連接配接。這是因為Sentinel需要通過接收主伺服器或者從伺服器發來的頻道資訊來發現未知的新Sentinel,是以才需要建立訂閱連接配接,而互相已知的Sentinel隻要使用指令連接配接來進行通信就足夠了。

8. 檢測主觀下線狀态

在預設情況下,Sentinel會以每秒一次的頻率向所有與它建立了指令連接配接的執行個體(包括主伺服器、從伺服器、其他Sentinel在内)發送PING指令,并通過執行個體傳回的PING指令回複來判斷執行個體是否線上。

執行個體對PING指令的回複可以分為以下兩種情況:❑有效回複:執行個體傳回+PONG、-LOADING、-MASTERDOWN三種回複的其中一種。❑無效回複:執行個體傳回除+PONG、-LOADING、-MASTERDOWN三種回複之外的其他回複,或者在指定時限内沒有傳回任何回複。Sentinel配置檔案中的down-after-milliseconds選項指定了Sentinel判斷執行個體進入主觀下線所需的時間長度:如果一個執行個體在down-after-milliseconds毫秒内,連續向Sentinel傳回無效回複,那麼Sentinel會修改這個執行個體所對應的執行個體結構,在結構的flags屬性中打開SRI_S_DOWN辨別,以此來表示這個執行個體已經進入主觀下線狀态。

9. 檢測客觀下線狀态

當Sentinel将一個主伺服器判斷為主觀下線之後,為了确認這個主伺服器是否真的下線了,它會向同樣監視這一主伺服器的其他Sentinel進行發送發送SENTINEL is-master-down-by-addr指令詢問,

當一個Sentinel(目标Sentinel)接收到另一個Sentinel(源Sentinel)發來的SENTINEL is-master-down-by指令時,目标Sentinel會分析并取出指令請求中包含的各個參數,并根據其中的主伺服器IP和端口号,檢查主伺服器是否已下線,然後向源Sentinel傳回一條包含三個參數的Multi Bulk回複作為SENTINEL is-master-down-by指令的回複:

Sentinel将統計其他Sentinel同意主伺服器已下線的數量,當這一數量達到配置指定的判斷客觀下線所需的數量時,Sentinel會将主伺服器執行個體結構flags屬性的SRI_O_DOWN辨別打開,表示主伺服器已經進入客觀下線狀态。

10. 選舉領頭Sentinel

當一個主伺服器被判斷為客觀下線時,監視這個下線主伺服器的各個Sentinel會進行協商,選舉出一個領頭Sentinel,并由領頭Sentinel對下線主伺服器執行故障轉移操作。

11. 故障轉移

在選舉産生出領頭Sentinel之後,領頭Sentinel将對已下線的主伺服器執行故障轉移操作,該操作包含以下三個步驟:

1)在已下線主伺服器屬下的所有從伺服器裡面,挑選出一個從伺服器,并将其轉換為主伺服器。

故障轉移操作第一步要做的就是在已下線主伺服器屬下的所有從伺服器中,挑選出一個狀态良好、資料完整的從伺服器,然後向這個從伺服器發送SLAVEOF no one指令,将這個從伺服器轉換為主伺服器。

新的主伺服器是怎樣挑選出來的?

領頭Sentinel會将已下線主伺服器的所有從伺服器儲存到一個清單裡面,然後按照以下規則,一項一項地對清單進行過濾:1)删除清單中所有處于下線或者斷線狀态的從伺服器,這可以保證清單中剩餘的從伺服器都是正常線上的。2)删除清單中所有最近五秒内沒有回複過領頭Sentinel的INFO指令的從伺服器,這可以保證清單中剩餘的從伺服器都是最近成功進行過通信的。3)删除所有與已下線主伺服器連接配接斷開超過down-after-milliseconds10毫秒的從伺服器:down-after-milliseconds選項指定了判斷主伺服器下線所需的時間,而删除斷開時長超過down-after-milliseconds10毫秒的從伺服器,則可以保證清單中剩餘的從伺服器都沒有過早地與主伺服器斷開連接配接,換句話說,清單中剩餘的從伺服器儲存的資料都是比較新的。之後,領頭Sentinel将根據從伺服器的優先級,對清單中剩餘的從伺服器進行排序,并選出其中優先級最高的從伺服器。如果有多個具有相同最高優先級的從伺服器,那麼領頭Sentinel将按照從伺服器的複制偏移量,對具有相同最高優先級的所有從伺服器進行排序,并選出其中偏移量最大的從伺服器(複制偏移量最大的從伺服器就是儲存着最新資料的從伺服器)。最後,如果有多個優先級最高、複制偏移量最大的從伺服器,那麼領頭Sentinel将按照運作ID對這些從伺服器進行排序,并選出其中運作ID最小的從伺服器。

2)讓已下線主伺服器屬下的所有從伺服器改為複制新的主伺服器。

當新的主伺服器出現之後,領頭Sentinel下一步要做的就是,讓已下線主伺服器屬下的所有從伺服器去複制新的主伺服器,這一動作可以通過向從伺服器發送SLAVEOF指令來實作。

3)将已下線主伺服器設定為新的主伺服器的從伺服器,當這個舊的主伺服器重新上線時,它就會成為新的主伺服器的從伺服器。

12 Redis叢集節點

一個Redis叢集通常由多個節點(node)組成,在剛開始的時候,每個節點都是互相獨立的,它們都處于一個隻包含自己的叢集當中,要組建一個真正可工作的叢集,我們必須将各個獨立的節點連接配接起來,構成一個包含多個節點的叢集,連接配接各個節點的工作可以使用CLUSTER MEET指令來完成。

收到指令的節點A将與節點B進行握手(handshake),以此來确認彼此的存在,并為将來的進一步通信打好基礎:

  • 1)節點A會為節點B建立一個clusterNode結構(每個節點都會使用一個clusterNode結構來記錄自己的狀态,并為叢集中的所有其他節點(包括主節點和從節點)都建立一個相應的clusterNode結構,以此來記錄其他節點的狀态),并将該結構添加到自己的clusterState的nodes字典裡面(每個節點都儲存着一個clusterState結構,這個結構記錄了在目前節點的視角下,叢集目前所處的狀态)。
  • 2)之後,節點A将根據CLUSTER MEET指令給定的IP位址和端口号,向節點B發送一條MEET消息(message)。
  • 3)如果一切順利,節點B将接收到節點A發送的MEET消息,節點B會為節點A建立一個clusterNode結構,并将該結構添加到自己的clusterState.nodes字典裡面。
  • 4)之後,節點B将向節點A傳回一條PONG消息。
  • 5)如果一切順利,節點A将接收到節點B傳回的PONG消息,通過這條PONG消息節點A可以知道節點B已經成功地接收到了自己發送的MEET消息。
  • 6)之後,節點A将向節點B傳回一條PING消息。
  • 7)如果一切順利,節點B将接收到節點A傳回的PING消息,通過這條PING消息節點B可以知道節點A已經成功地接收到了自己傳回的PONG消息,握手完成。

13 槽

Redis叢集通過分片的方式來儲存資料庫中的鍵值對:叢集的整個資料庫被分為16384個槽(slot), 資料庫中 的每個鍵都屬于這16384個槽的其中一個,叢集中的每個節點可以處理0個或最多16384個槽。

clusterNode結構的slots屬性和numslot屬性記錄了節點負責處理哪些槽:一個節點除了會将自己負責處理的槽記錄在clusterNode結構的slots屬性和numslots屬性之外,它還會将自己的slots數組通過消息發送給叢集中的其他節點,以此來告知其他節點自己目前負責處理哪些槽。

clusterState結構中的slots數組記錄了叢集中所有16384個槽的指派資訊:slots數組包含16384個項,每個數組項都是一個指向clusterNode結構的指針:❑如果slots[i]指針指向NULL,那麼表示槽i尚未指派給任何節點。❑如果slots[i]指針指向一個clusterNode結構,那麼表示槽i已經指派給了clusterNode結構所代表的節點。

14 在叢集中執行指令

在對資料庫中的16384個槽都進行了指派之後,叢集就會進入上線狀态,這時用戶端就可以向叢集中的節點發送資料指令了。當用戶端向節點發送與資料庫鍵有關的指令時,接收指令的節點會計算出指令要處理的資料庫鍵屬于哪個槽,并檢查這個槽是否指派給了自己:❑如果鍵所在的槽正好就指派給了目前節點,那麼節點直接執行這個指令。❑如果鍵所在的槽并沒有指派給目前節點,那麼節點會向用戶端傳回一個MOVED錯誤,指引用戶端轉向(redirect)至正确的節點,并再次發送之前想要執行的指令。

節點使用以下算法來計算給定鍵key屬于哪個槽:

其中CRC16(key)語句用于計算鍵key的CRC-16校驗和,而&16383語句則用于計算出一個介于0至16383之間的整數作為鍵key的槽号。

當節點發現鍵所在的槽并非由自己負責處理的時候,節點就會向用戶端傳回一個MOVED錯誤,指引用戶端轉向至正在負責槽的節點。

節點和單機伺服器在資料庫方面的一個差別是,節點隻能使用0号資料庫,而單機Redis伺服器則沒有這一限制。

15 重新分片

Redis叢集的重新分片操作可以将任意數量已經指派給某個節點(源節點)的槽改為指派給另一個節點(目标節點),并且相關槽所屬的鍵值對也會從源節點被移動到目标節點。重新分片操作可以線上(online)進行,在重新分片的過程中,叢集不需要下線,并且源節點和目标節點都可以繼續處理指令請求。

在進行重新分片期間,源節點向目标節點遷移一個槽的過程中,可能會出現這樣一種情況:屬于被遷移槽的一部分鍵值對儲存在源節點裡面,而另一部分鍵值對則儲存在目标節點裡面。當用戶端向源節點發送一個與資料庫鍵有關的指令,并且指令要處理的資料庫鍵恰好就屬于正在被遷移的槽時:❑源節點會先在自己的資料庫裡面查找指定的鍵,如果找到的話,就直接執行用戶端發送的指令。❑相反地,如果源節點沒能在自己的資料庫裡面找到指定的鍵,那麼這個鍵有可能已經被遷移到了目标節點,源節點将向用戶端傳回一個ASK錯誤,指引用戶端轉向正在導入槽的目标節點,并再次發送之前想要執行的指令。

ASK錯誤和MOVED錯誤都會導緻用戶端轉向,它們的差別在于:❑MOVED錯誤代表槽的負責權已經從一個節點轉移到了另一個節點:在用戶端收到關于槽i的MOVED錯誤之後,用戶端每次遇到關于槽i的指令請求時,都可以直接将指令請求發送至MOVED錯誤所指向的節點,因為該節點就是目前負責槽i的節點。❑與此相反,ASK錯誤隻是兩個節點在遷移槽的過程中使用的一種臨時措施:在用戶端收到關于槽i的ASK錯誤之後,用戶端隻會在接下來的一次指令請求中将關于槽i的指令請求發送至ASK錯誤所訓示的節點,但這種轉向不會對用戶端今後發送關于槽i的指令請求産生任何影響,用戶端仍然會将關于槽i的指令請求發送至目前負責處理槽i的節點,除非ASK錯誤再次出現。

16 消息

叢集中的各個節點通過發送和接收消息(message)來進行通信,我們稱發送消息的節點為發送者(sender),接收消息的節點為接收者(receiver)。

節點發送的消息主要有以下五種:

  • ❑MEET消息:當發送者接到用戶端發送的CLUSTER MEET指令時,發送者會向接收者發送MEET消息,請求接收者加入到發送者目前所處的叢集裡面。
  • ❑PING消息:叢集裡的每個節點預設每隔一秒鐘就會從已知節點清單中随機選出五個節點,然後對這五個節點中最長時間沒有發送過PING消息的節點發送PING消息,以此來檢測被選中的節點是否線上。除此之外,如果節點A最後一次收到節點B發送的PONG消息的時間,距離目前時間已經超過了節點A的cluster-node-timeout選項設定時長的一半,那麼節點A也會向節點B發送PING消息,這可以防止節點A因為長時間沒有随機選中節點B作為PING消息的發送對象而導緻對節點B的資訊更新滞後。
  • ❑PONG消息:當接收者收到發送者發來的MEET消息或者PING消息時,為了向發送者确認這條MEET消息或者PING消息已到達,接收者會向發送者傳回一條PONG消息。另外,一個節點也可以通過向叢集廣播自己的PONG消息來讓叢集中的其他節點立即重新整理關于這個節點的認識,例如當一次故障轉移操作成功執行之後,新的主節點會向叢集廣播一條PONG消息,以此來讓叢集中的其他節點立即知道這個節點已經變成了主節點,并且接管了已下線節點負責的槽。
  • ❑FAIL消息:當一個主節點A判斷另一個主節點B已經進入FAIL狀态時,節點A會向叢集廣播一條關于節點B的FAIL消息,所有收到這條消息的節點都會立即将節點B标記為已下線。
  • ❑PUBLISH消息:當節點接收到一個PUBLISH指令時,節點會執行這個指令,并向叢集廣播一條PUBLISH消息,所有接收到這條PUBLISH消息的節點都會執行相同的PUBLISH指令。一條消息由消息頭(header)和消息正文(data)組成

17 Redis字元串

Redis字元串底層采用SDS,結構體包含len屬性,free屬性,和一個位元組數組,好處如下:

18 Redis連結清單

Redis用一個List結構體來表示連結清單

list結構為連結清單提供了表頭指針head、表尾指針tail,以及連結清單長度計數器len,而dup、free和match成員則是用于實作多态連結清單所需的類型特定函數:❑dup函數用于複制連結清單節點所儲存的值;❑free函數用于釋放連結清單節點所儲存的值;❑match函數則用于對比連結清單節點所儲存的值和另一個輸入值是否相等。

Redis的連結清單實作的特性可以總結如下:

  • ❑雙端:連結清單節點帶有prev和next指針,擷取某個節點的前置節點和後置節點的複雜度都是O(1)。
  • ❑無環:表頭節點的prev指針和表尾節點的next指針都指向NULL,對連結清單的通路以NULL為終點。
  • ❑帶表頭指針和表尾指針:通過list結構的head指針和tail指針,程式擷取連結清單的表頭節點和表尾節點的複雜度為O(1)。
  • ❑帶連結清單長度計數器:程式使用list結構的len屬性來對list持有的連結清單節點進行計數,程式擷取連結清單中節點數量的複雜度為O(1)。
  • ❑多态:連結清單節點使用void*指針來儲存節點值,并且可以通過list結構的dup、free、match三個屬性為節點值設定類型特定函數,是以連結清單可以用于儲存各種不同類型的值。

19 Redis字典

字典在Redis中的應用相當廣泛,比如Redis的資料庫就是使用字典來作為底層實作的,對資料庫的增、删、查、改操作也是建構在對字典的操作之上的。

除了用來表示資料庫之外,字典還是哈希鍵的底層實作之一,當一個哈希鍵包含的鍵值對比較多,又或者鍵值對中的元素都是比較長的字元串時,Redis就會使用字典作為哈希鍵的底層實作。

Redis的字典使用哈希表作為底層實作,一個哈希表裡面可以有多個哈希表節點,而每個哈希表節點就儲存了字典中的一個鍵值對。

随着操作的不斷執行,哈希表儲存的鍵值對會逐漸地增多或者減少,為了讓哈希表的負載因子(load factor)維持在一個合理的範圍之内,當哈希表儲存的鍵值對數量太多或者太少時,程式需要對哈希表的大小進行相應的擴充或者收縮。擴充和收縮哈希表的工作可以通過執行rehash(重新散列)操作來完成,Redis對字典的哈希表執行rehash的步驟如下:

  • 1)為字典的ht[1]哈希表配置設定空間,這個哈希表的空間大小取決于要執行的操作,以及ht[0]目前包含的鍵值對數量(也即是ht[0].used屬性的值):❑如果執行的是擴充操作,那麼ht[1]的大小為第一個大于等于ht[0].used*2的2 n(2的n次方幂);❑如果執行的是收縮操作,那麼ht[1]的大小為第一個大于等于ht[0].used的2 n。
  • 2)将儲存在ht[0]中的所有鍵值對rehash到ht[1]上面:rehash指的是重新計算鍵的哈希值和索引值,然後将鍵值對放置到ht[1]哈希表的指定位置上。
  • 3)當ht[0]包含的所有鍵值對都遷移到了ht[1]之後(ht[0]變為空表),釋放ht[0],将ht[1]設定為ht[0],并在ht[1]新建立一個空白哈希表,為下一次rehash做準備。

    上一節說過,擴充或收縮哈希表需要将ht[0]裡面的所有鍵值對rehash到ht[1]裡面,但是,這個rehash動作并不是一次性、集中式地完成的,而是分多次、漸進式地完成的。這樣做的原因在于,如果ht[0]裡隻儲存着四個鍵值對,那麼伺服器可以在瞬間就将這些鍵值對全部rehash到ht[1];但是,如果哈希表裡儲存的鍵值對數量不是四個,而是四百萬、四千萬甚至四億個鍵值對,那麼要一次性将這些鍵值對全部rehash到ht[1]的話,龐大的計算量可能會導緻伺服器在一段時間内停止服務。是以,為了避免rehash對伺服器性能造成影響,伺服器不是一次性将ht[0]裡面的所有鍵值對全部rehash到ht[1],而是分多次、漸進式地将ht[0]裡面的鍵值對慢慢地rehash到ht[1]。以下是哈希表漸進式rehash的詳細步驟:

  • 1)為ht[1]配置設定空間,讓字典同時持有ht[0]和ht[1]兩個哈希表。
  • 2)在字典中維持一個索引計數器變量rehashidx,并将它的值設定為0,表示rehash工作正式開始。
  • 3)在rehash進行期間,每次對字典執行添加、删除、查找或者更新操作時,程式除了執行指定的操作以外,還會順帶将ht[0]哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],當rehash工作完成之後,程式将rehashidx屬性的值增一。
  • 4)随着字典操作的不斷執行,最終在某個時間點上,ht[0]的所有鍵值對都會被rehash至ht[1],這時程式将rehashidx屬性的值設為-1,表示rehash操作已完成。漸進式rehash的好處在于它采取分而治之的方式,将rehash鍵值對所需的計算工作均攤到對字典的每個添加、删除、查找和更新操作上,進而避免了集中式rehash而帶來的龐大計算量。

20 Redis舊版複制功能的實作

Redis的複制功能分為同步(sync)和指令傳播(command propagate)兩個操作:

  • ❑同步操作用于将從伺服器的資料庫狀态更新至主伺服器目前所處的資料庫狀态。
  • ❑指令傳播操作則用于在主伺服器的資料庫狀态被修改,導緻主從伺服器的資料庫狀态出現不一緻時,讓主從伺服器的資料庫重新回到一緻狀态。

21 Redis舊版複制功能的缺陷

在Redis中,從伺服器對主伺服器的複制可以分為以下兩種情況:

  • ❑初次複制:從伺服器以前沒有複制過任何主伺服器,或者從伺服器目前要複制的主伺服器和上一次複制的主伺服器不同。
  • ❑斷線後重複制:處于指令傳播階段的主從伺服器因為網絡原因而中斷了複制,但從伺服器通過自動重連接配接重新連上了主伺服器,并繼續複制主伺服器。對于初次複制來說,舊版複制功能能夠很好地完成任務,但對于斷線後重複制來說,舊版複制功能雖然也能讓主從伺服器重新回到一緻狀态,但效率卻非常低。

20 Redis新版複制功能的實作

為了解決舊版複制功能在處理斷線重複制情況時的低效問題,Redis從2.8版本開始,使用PSYNC指令代替SYNC指令來執行複制時的同步操作。

PSYNC指令具有完整重同步(full resynchronization)和部分重同步(partialresynchronization)兩種模式:❑其中完整重同步用于處理初次複制情況:完整重同步的執行步驟和SYNC指令的執行步驟基本一樣,它們都是通過讓主伺服器建立并發送RDB檔案,以及向從伺服器發送儲存在緩沖區裡面的寫指令來進行同步。❑而部分重同步則用于處理斷線後重複制情況:當從伺服器在斷線後重新連接配接主伺服器時,如果條件允許,主伺服器可以将主從伺服器連接配接斷開期間執行的寫指令發送給從伺服器,從伺服器隻要接收并執行這些寫指令,就可以将資料庫更新至主伺服器目前所處的狀态。PSYNC指令的部分重同步模式解決了舊版複制功能在處理斷線後重複制時出現的低效情況。

21 部分重同步的實作

部分重同步功能由以下三個部分構成:❑主伺服器的複制偏移量(replication offset)和從伺服器的複制偏移量。❑主伺服器的複制積壓緩沖區(replication backlog)。❑伺服器的運作ID(run ID)。

22 PSYNC指令的實作

PSYNC指令的調用方法有兩種:❑如果從伺服器以前沒有複制過任何主伺服器,或者之前執行過SLAVEOFno one指令,那麼從伺服器在開始一次新的複制時将向主伺服器發送PSYNC ? -1指令,主動請求主伺服器進行完整重同步(因為這時不可能執行部分重同步)。❑相反地,如果從伺服器已經複制過某個主伺服器,那麼從伺服器在開始一次新的複制時将向主伺服器發送PSYNC <runid> <offset>指令:其中runid是上一次複制的主伺服器的運作ID,而offset則是從伺服器目前的複制偏移量,接收到這個指令的主伺服器會通過這兩個參數來判斷應該對從伺服器執行哪種同步操作。根據情況,接收到PSYNC指令的主伺服器會向從伺服器傳回以下三種回複的其中一種:❑如果主伺服器傳回+FULLRESYNC <runid> <offset>回複,那麼表示主伺服器将與從伺服器執行完整重同步操作:其中runid是這個主伺服器的運作ID,從伺服器會将這個ID儲存起來,在下一次發送PSYNC指令時使用;而offset則是主伺服器目前的複制偏移量,從伺服器會将這個值作為自己的初始化偏移量。❑如果主伺服器傳回+CONTINUE回複,那麼表示主伺服器将與從伺服器執行部分重同步操作,從伺服器隻要等着主伺服器将自己缺少的那部分資料發送過來就可以了。❑如果主伺服器傳回-ERR回複,那麼表示主伺服器的版本低于Redis 2.8,它識别不了PSYNC指令,從伺服器将向主伺服器發送SYNC指令,并與主伺服器執行完整同步操作。

23 心跳檢測

在指令傳播階段,從伺服器預設會以每秒一次的頻率,向主伺服器發送指令:

其中replication_offset是從伺服器目前的複制偏移量。發送REPLCONF ACK指令對于主從伺服器有三個作用:❑檢測主從伺服器的網絡連接配接狀态。❑輔助實作min-slaves選項。❑檢測指令丢失。

24 Redis連接配接池

Redis也是一種資料庫,基于C/S模式,是以如果需要使用必須建立連接配接,假設Redis伺服器與用戶端分處在異地,雖然基于記憶體的Redis資料庫有着超高的性能,但是底層的網絡通信卻占用了一次資料請求的大量時間,因為每次資料互動都需要先建立連接配接,假設一次資料互動總共用時30ms,超高性能的Redis資料庫處理資料所花的時間可能不到1ms,也即是說前期的連接配接占用了29ms,連接配接池則可以實作在用戶端建立多個連結并且不釋放,當需要使用連接配接的時候通過一定的算法擷取已經建立的連接配接,使用完了以後則還給連接配接池,這就免去了資料庫連接配接所占用的時間。

25 Redis高可用

主從複制(Replication-Sentinel模式)

Redis叢集(Redis-Cluster模式)

哨兵

## 26 Redis 管道

redis是一個用戶端-伺服器(CS)模型和請求/響應協定的TCP伺服器,使用和http類似的請求響應協定。一個client可以通過一個socket連接配接發起多個請求指令。每個請求指令發出後client通常會阻塞并等待redis服務處理,redis處理完請求指令後會将結果通過響應封包傳回給client。是以,如果一個業務邏輯中需要多次發送redis操作時,每一條指令在網絡傳輸中的往返時延(計算機網絡了解一下)會遠遠大于執行時間,這也是為什麼說影響redis性能的最大難題是網絡時延。

管道(pipeline)可以一次性發送多條指令并在執行完後一次性将結果傳回,pipeline通過減少用戶端與redis的通信次數來實作降低往返延時時間,而且Pipeline 實作的原理是隊列,而隊列的原理是時先進先出,這樣就保證資料的順序性。

通過pipeline方式當有大批量的操作時候。我們可以節省很多原來浪費在網絡延遲的時間。但是,需要注意到用pipeline方式打包指令發送,redis必須在處理完所有指令前先緩存起所有指令的處理結果。打包的指令越多,緩存消耗記憶體也越多。是以并是不是打包的指令越多越好。此外,pipeline期間将“獨占”目前redis連接配接,此期間将不能進行非“管道”類型的其他操作,直到該pipeline關閉。

pipeline不是原子性的,中間可能會存在部分失敗的情況,也就是說不能保證每條指令都能執行成功,如果中間有指令出現錯誤,redis不會中斷執行,而是直接執行下一條指令,然後将所有指令的執行結果(執行成果或者執行失敗)放到清單中統一傳回,如果需要每條指令都執行成功,我們在批量執行過程中需要監控執行數量和傳回的成功數量是否一緻。

事務與pipeline的差別

事務與pipeline都是不支援復原;中間指令出現錯誤,不會影響前面已經執行成功的指令,也不會中斷後面的指令繼續執行;

事務可以實作原子性和隔離性,我的了解是,雖然pipeline中的指令是以隊列發送到redis中執行的,但如果其他redis連接配接同時發送了指令,那麼pipeline中的指令放到redis的排隊執行隊列中時可能會被拆開,即pipeline中的多個指令被其他指令插隊。

27 手動實作Redis事務復原

注意:已經執行完畢的指令對應的資料不會自動復原,需要程式員自己在代碼中實作復原。

手動進行事務復原

• 記錄操作過程中被影響的資料之前的狀态

• 單資料:string

• 多資料:hash、list、set、zset

• 設定指令恢複所有的被修改的項

• 單資料:直接set(注意周邊屬性,例如時效)

• 多資料:修改對應值或整體克隆複制

28 Redis相比memcached有哪些優勢:

• memcached所有的值均是簡單的字元串,redis作為其替代者,支援更為豐富的資料類型

• redis的速度比memcached快很多

• redis可以持久化其資料

29 Reids8種淘汰政策:

  • noeviction: 不删除政策, 達到最大記憶體限制時, 如果需要更多記憶體, 直接傳回錯誤資訊。大多數寫指令都會導緻占用更多的記憶體(有極少數會例外。
  • allkeys-lru: 所有key通用; 優先删除最近最少使用(less recently used ,LRU) 的 key。
  • volatile-lru: 隻限于設定了 expire 的部分; 優先删除最近最少使用(less recently used ,LRU) 的 key。
  • allkeys-random: 所有key通用; 随機删除一部分 key。
  • volatile-random: 隻限于設定了 expire 的部分; 随機删除一部分 key。
  • volatile-ttl: 隻限于設定了 expire 的部分; 優先删除剩餘時間(time to live,TTL) 短的key。
  • allkeys-lfu: 從資料集中挑選使用頻率最低的資料淘汰。
  • volatile-lfu: 從已設定過期時間的資料集挑選使用頻率最低的資料淘汰。

30 Redis的并發競争問題如何解決?

單程序單線程模式,采用隊列模式将并發通路變為串行通路。Redis本身沒有鎖的概念,Redis對于多個用戶端連接配接并不存在競争,利用setnx實作鎖。

31 Redis是單線程的,但Redis為什麼這麼快?

  • (1)完全基于記憶體,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似于HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1);
  • (2)資料結構簡單,對資料操作也簡單,Redis中的資料結構是專門進行設計的;
  • (3)因為是單線程,是以避免了不必要的上下文切換和競争條件,也不存在多程序或者多線程導緻的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導緻的性能消耗;
  • (4)使用多路I/O複用模型,非阻塞IO;這裡“多路”指的是多個網絡連接配接,“複用”指的是複用同一個線程
  • (5)使用底層模型不同,它們之間底層實作方式以及與用戶端之間通信的應用協定不一樣,Redis直接自己建構了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;

32 Redis記憶體模型

(1)used_memory:Redis配置設定器配置設定的記憶體總量(機關是位元組),包括使用的虛拟記憶體(即swap);Redis配置設定器後面會介紹。used_memory_human隻是顯示更友好。

(2)used_memory_rss: Redis程序占據作業系統的記憶體(機關是位元組),與top及ps指令看到的值是一緻的;除了配置設定器配置設定的記憶體之外,used_memory_rss還包括程序運作本身需要的記憶體、記憶體碎片等,但是不包括虛拟記憶體。

(3)mem_fragmentation_ratio: 記憶體碎片比率,該值是used_memory_rss / used_memory的比值。

(4)mem_allocator: Redis使用的記憶體配置設定器,在編譯時指定;可以是 libc 、jemalloc或者tcmalloc,預設是jemalloc;截圖中使用的便是預設的jemalloc。

33 Redis記憶體劃分

  • 資料

    作為資料庫,資料是最主要的部分;這部分占用的記憶體會統計used_memory中。

  • 程序本身運作需要的記憶體

    Redis主程序本身運作肯定需要占用記憶體,如代碼、常量池等等;這部分記憶體大約幾兆,在大多數生産環境中與Redis資料占用的記憶體相比可以忽略。這部分記憶體不是由jemalloc配置設定,是以不會統計在used_memory中。

  • 緩沖記憶體

    緩沖記憶體包括用戶端緩沖區、複制積壓緩沖區、AOF緩沖區等;其中,用戶端緩沖存儲用戶端連接配接的輸入輸出緩沖;複制積壓緩沖用于部分複制功能;AOF緩沖區用于在進行AOF重寫時,儲存最近的寫入指令。在了解相應功能之前,不需要知道這些緩沖的細節;這部分記憶體由jemalloc配置設定,是以會統計在used_memory中。

  • 記憶體碎片

    記憶體碎片是Redis在配置設定、回收實體記憶體過程中産生的。例如,如果對資料的更改頻繁,而且資料之間的大小相差很大,可能導緻redis釋放的空間在實體記憶體中并沒有釋放,但redis又無法有效利用,這就形成了記憶體碎片。記憶體碎片不會統計在used_memory中。

34 redis緩存被擊穿處理機制

  • 緩存穿透

    存在這樣一種場景,反複請求一個不存在的key,那麼這個情況就會穿透redis,直接請求到mysql,然後傳回資料為null,如果請求過多就會導緻伺服器響應變慢。

    解決方案:

    1.寫一個緩存器,将查詢到為空的資料存放在redis中,如果下一次仍然有這樣的請求就會在redis中傳回,但是這種方式隻能阻擋通過一個key的攻擊。

    2.寫一個布隆過濾器

  • 緩存擊穿

    指緩存中沒有但資料庫中有的資料(一般是緩存時間到期),這時由于并發使用者特别多,同時讀緩存沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力

    解決方案:

    1.設定熱點資料永不過時

    2.對同一個key添加互斥鎖

  • 緩存雪崩

    是指緩存中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機。和緩存擊穿不同的是,緩存擊穿指并發查同一條資料,緩存雪崩是不同資料都過期了,很多資料都查不到進而查資料庫。

    解決方案:

    1.緩存資料的過期時間設定随機,防止同一時間大量資料過期現象發生。

    2.如果緩存資料庫是分布式部署,将熱點資料均勻分布在不同的緩存資料庫中。

    3.設定熱點資料永遠不過期。

35 redis有哪些類型緩存

緩存是高并發場景下提高熱點資料通路性能的一個有效手段,在開發項目時會經常使用到。緩存的類型分為:本地緩存、分布式緩存和多級緩存。

  • 本地緩存:

    本地緩存就是在程序的記憶體中進行緩存,比如我們的 JVM 堆中,可以用 LRUMap 來實作,也可以使用 Ehcache 這樣的工具來實作。

    本地緩存是記憶體通路,沒有遠端互動開銷,性能最好,但是受限于單機容量,一般緩存較小且無法擴充。

  • 分布式緩存:

    分布式緩存可以很好得解決這個問題。分布式緩存一般都具有良好的水準擴充能力,對較大資料量的場景也能應付自如。缺點就是需要進行遠端請求,性能不如本地緩存。

  • 多級緩存:

    為了平衡這種情況,實際業務中一般采用多級緩存,本地緩存隻儲存通路頻率最高的部分熱點資料,其他的熱點資料放在分布式緩存中。

    在目前的一線大廠中,這也是最常用的緩存方案,單考單一的緩存方案往往難以撐住很多高并發的場景。

36 Redis 的優缺點

優點:

  • 讀寫性能優異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
  • 支援資料持久化,支援AOF和RDB兩種持久化方式。
  • 支援事務,Redis的所有操作都是原子性的,同時Redis還支援對幾個操作合并後的原子性執行。
  • 資料結構豐富,除了支援string類型的value外還支援hash、set、zset、list等資料結構。
  • 支援主從複制,主機會自動将資料同步到從機,可以進行讀寫分離。

缺點

  • 資料庫容量受到實體記憶體的限制,不能用作海量資料的高性能讀寫,是以Redis适合的場景主要局限在較小資料量的高性能操作和運算上。
  • Redis 不具備自動容錯和恢複功能,主機從機的當機都會導緻前端部分讀寫請求失敗,需要等待機器重新開機或者手動切換前端的IP才能恢複。
  • 主機當機,當機前有部分資料未能及時同步到從機,切換IP後還會引入資料不一緻的問題,降低了系統的可用性。
  • Redis 較難支援線上擴容,在叢集容量達到上限時線上擴容會變得很複雜。為避免這一問題,運維人員在系統上線時必須確定有足夠的空間,這對資源造成了很大的浪費。

37 為什麼要用 Redis /為什麼要用緩存

主要從“高性能”和“高并發”這兩點來看待這個問題。

高性能:

假如使用者第一次通路資料庫中的某些資料。這個過程會比較慢,因為是從硬碟上讀取的。将該使用者通路的資料存在數緩存中,這樣下一次再通路這些資料的時候就可以直接從緩存中擷取了。操作緩存就是直接操作記憶體,是以速度相當快。如果資料庫中的對應資料改變的之後,同步改變緩存中相應的資料即可!

Redis面試題彙總大全

高并發:

直接操作緩存能夠承受的請求是遠遠大于直接通路資料庫的,是以我們可以考慮把資料庫中的部分資料轉移到緩存中去,這樣使用者的一部分請求會直接到緩存這裡而不用經過資料庫。

Redis面試題彙總大全

為什麼要用 Redis 而不用 map/guava 做緩存?

緩存分為本地緩存和分布式緩存。以 Java 為例,使用自帶的 map 或者 guava 實作的是本地緩存,最主要的特點是輕量以及快速,生命周期随着 jvm 的銷毀而結束,并且在多執行個體的情況下,每個執行個體都需要各自儲存一份緩存,緩存不具有一緻性。

使用 redis 或 memcached 之類的稱為分布式緩存,在多執行個體的情況下,各執行個體共用一份緩存資料,緩存具有一緻性。缺點是需要保持 redis 或 memcached服務的高可用,整個程式架構上較為複雜。

Redis持久化

  • AOF檔案比RDB更新頻率高,優先使用AOF還原資料。
  • AOF比RDB更安全也更大
  • RDB性能比AOF好
  • 如果兩個都配了優先加載AOF

如何選擇合适的持久化方式

如果是單點的

一般來說, 如果想達到足以媲美PostgreSQL的資料安全性,你應該同時使用兩種持久化功能。在這種情況下,當 Redis 重新開機的時候會優先載入AOF檔案來恢複原始的資料,因為通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集完整。

如果你非常關心你的資料, 但仍然可以承受數分鐘以内的資料丢失,那麼你可以隻使用RDB持久化。

有很多使用者都隻使用AOF持久化,但并不推薦這種方式,因為定時生成RDB快照(snapshot)非常便于進行資料庫備份, 并且 RDB 恢複資料集的速度也要比AOF恢複的速度要快,除此之外,使用RDB還可以避免AOF程式的bug。

如果你隻希望你的資料在伺服器運作的時候存在,你也可以不使用任何持久化方式。

主從架構

持久化為Redis提供了異常情況下的資料恢複機制,但開啟持久化是有代價的,哪一種持久化都可能造成CPU卡頓,影響對用戶端請求的處理。不開啟持久化又存在風險,如果一旦誤重新開機master節點,或者試想這樣一種場景,主從切換失敗,很可能因為疏忽直接重新開機master,這時沒有開啟持久化的master會把所有slave的資料清0。是以是否開啟持久化,怎樣開啟持久化是一個難題。和運維同僚探讨了一些方案,這裡總結一下供大家參考:

1、極端情況下可以容忍全量資料丢失,那麼建議master關閉持久化,slave關閉持久化;

2、極端情況下不能容忍全量資料丢失,但可以容忍部分資料丢失,如果記憶體資料集較小且不會增長建議master開啟rdb,slave開啟rdb;如果資料集很大,或不确定資料集增長趨勢,建議master關閉持久化,slave開啟rdb

開啟rdb需要cpu和磁盤性能保障。如果master關閉持久化,slave開啟rdb需要保證slave的rdb不會被master誤重新開機所覆寫,這裡提供幾種方案:

  • 重新開機腳本包一層指令先網絡請求加載備機備份目錄下的rdb檔案後再執行start,可以防止誤重新開機,但備機調整部署可能需要調整腳本,主機打開持久化也需要調整腳本
  • 定時将rdb檔案通過網絡io傳給master節點(檔案大比較耗時,檔案增長需要考慮定時腳本執行間隔,否則會造成持續的網絡io),而且也會有一定資料損失
  • 定時備份Slave的rdb到備份目錄,不做任何其他操作,誤重新開機時人工拷貝rdb到master節點(會有一定資料損失)

3、最大限度需要資料無損,建議master開啟aof,slave開啟aof

開啟aof需要cpu和磁盤性能保障。開啟aof建議fsync同步刷盤使用everysec,自定義腳本在應用空閑時定時做bgrewrite,bgrewrite期間增量資料做緩沖。

目前大部分業務都允許部分資料丢失,為使Redis性能最大化,關閉了Master持久化,slave開啟rdb,為防止誤重新開機對rdb做了5分鐘一次備份,保留最近1小時的備份檔案,必要時人工copy到master資料目錄下恢複資料。後續硬體性能提升後,看情況再調整持久化機制

Redis的過期鍵的删除政策

我們都知道,Redis是key-value資料庫,我們可以設定Redis中緩存的key的過期時間。Redis的過期政策就是指當Redis中緩存的key過期了,Redis如何處理。

過期政策通常有以下三種:

  • 定時過期:每個設定過期時間的key都需要建立一個定時器,到過期時間就會立即清除。該政策可以立即清除過期的資料,對記憶體很友好;但是會占用大量的CPU資源去處理過期的資料,進而影響緩存的響應時間和吞吐量。
  • 惰性過期:隻有當通路一個key時,才會判斷該key是否已過期,過期則清除。該政策可以最大化地節省CPU資源,卻對記憶體非常不友好。極端情況可能出現大量的過期key沒有再次被通路,進而不會被清除,占用大量記憶體。
  • 定期過期:每隔一定的時間,會掃描一定數量的資料庫的expires字典中一定數量的key,并清除其中已過期的key。該政策是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果。

    (expires字典會儲存所有設定了過期時間的key的過期時間資料,其中,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis叢集中儲存的所有鍵。)

    Redis中同時使用了惰性過期和定期過期兩種過期政策。

Redis key的過期時間和永久有效分别怎麼設定?

EXPIRE和PERSIST指令。

MySQL裡有2000w資料,redis中隻存20w的資料,如何保證redis中的資料都是熱點資料

redis記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰政策。

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

如果達到設定的上限,Redis的寫指令會傳回錯誤資訊(但是讀指令還可以正常傳回)。或者你可以配置記憶體淘汰機制,當Redis達到記憶體上限時會沖刷掉舊的内容。

Redis如何做記憶體優化?

可以好好利用Hash,list,sorted set,zset等集合類型資料,因為通常情況下很多小的Key-Value可以用更緊湊的方式存放到一起。盡可能使用散清單(hash),散清單(是說散清單裡面存儲的數少)使用的記憶體非常小,是以你應該盡可能的将你的資料模型抽象到一個散清單裡面。比如你的web系統中有一個使用者對象,不要為這個使用者的名稱,姓氏,郵箱,密碼設定單獨的key,而是應該把這個使用者的所有資訊存儲到一張散清單裡面

事務管理(ACID)概述

  • 原子性(Atomicity)

    原子性是指事務是一個不可分割的工作機關,事務中的操作要麼都發生,要麼都不發生。

  • 一緻性(Consistency)

    事務前後資料的完整性必須保持一緻。

  • 隔離性(Isolation)

    多個事務并發執行時,一個事務的執行不應影響其他事務的執行

  • 持久性(Durability)

    持久性是指一個事務一旦被送出,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響

Redis的事務總是具有ACID中的一緻性和隔離性,其他特性是不支援的。當伺服器運作在AOF持久化模式下,并且appendfsync選項的值為always時,事務也具有耐久性。

Redis事務支援隔離性嗎

Redis 是單程序程式,并且它保證在執行事務時,不會對事務進行中斷,事務可以運作直到執行完所有事務隊列中的指令為止。是以,Redis 的事務是總是帶有隔離性的。

Redis事務保證原子性嗎,支援復原嗎

Redis中,單條指令是原子性執行的,但事務不保證原子性,且沒有復原。事務中任意指令執行失敗,其餘的指令仍會被執行。

Redis事務其他實作

基于Lua腳本,Redis可以保證腳本内的指令一次性、按順序地執行。

也不提供事務運作錯誤的復原,執行過程中如果部分指令運作錯誤,剩下的指令還是會繼續運作完。

基于中間标記變量,通過另外的标記變量來辨別事務是否執行完成,讀取資料時先讀取該标記變量判斷是否事務執行完成。但這樣會需要額外寫代碼實作,比較繁瑣

Redis常見性能問題和解決方案?

  • Master最好不要做任何持久化工作,包括RDB快照和AOF日志備份,特别是不要啟用RDB快照做持久化。Master調用BGREWRITEAOF重寫AOF檔案,AOF在重寫的時候會占大量的CPU和記憶體資源,導緻服務load過高,出現短暫服務暫停現象。如果資料比較關鍵,某個Slave開啟AOF備份資料,政策為每秒同步一次。
  • 為了主從複制的速度和連接配接的穩定性,Slave和Master最好在同一個區域網路内。
  • 盡量避免在壓力較大的主庫上增加從庫
  • 為了Master的穩定性,主從複制不要用圖狀結構,用單向連結清單結構更穩定,即主從關系為:Master<–Slave1<–Slave2<–Slave3…,這樣的結構也友善解決單點故障問題,實作Slave對Master的替換,也即,如果Master挂了,可以立馬啟用Slave1做Master,其他不變。