1. 程式人生 > 資料庫 >redis面試總結

redis面試總結

單執行緒的redis為什麼這麼快

  1.所有的資料都在記憶體中,所有的運算都是記憶體級別的運算。   2.單執行緒避免了多執行緒的切換性 能損耗問題。   3.Redis的IO多路複用:利用epoll來實現IO多路複用,將連線資訊和事件放到佇列中,依次放到 檔案事件分派器,事件分派器將事件分發給事件處理器。     (C10K問題:一個TCP連線,就需要分配一個程序。假如有C10K,就需要建立1W個程序,可想而知單機是無法承受的。解決:同一個執行緒/程序同時處理多個連線,即I/O多路複用,NIO)     epoll方式:該方式可以說是C10K問題的killer,他不去輪詢監聽所有檔案控制代碼是否已經就緒。epoll只對發生變化的檔案控制代碼感興趣。其工作機制是,使用"事件"的就緒通知方式,通過epoll_ctl註冊檔案描述符fd,一旦該fd就緒,核心就會採用類似callback的回撥機制來啟用該fd, epoll_wait便可以收到通知, 並通知應用程式。而且epoll使用一個檔案描述符管理多個描述符,將使用者程序的檔案描述符的事件存放到核心的一個事件表中, 這樣資料只需要從核心快取空間拷貝一次到使用者程序地址空間。而且epoll是通過核心與使用者空間共享記憶體方式來實現事件就緒訊息傳遞的,其效率非常高。   4. 支援豐富資料型別,支援string,list,set,sorted set,hash   5.支援事務,操作都是原子性,所謂的原子性就是對資料的更改要麼全部執行,要麼全部不執行   6. 豐富的特性:可用於快取,訊息,按key設定過期時間,過期後將會自動刪除如何解決redis的併發競爭key問題 Redis持久化
  RDB快照(snapshot): Redis 將記憶體資料庫快照儲存在名字為 dump.rdb 的二進位制檔案中。   AOF(append-only file):快照功能並不是非常耐久(durable): 如果 Redis 因為某些原因而造成故障停機, 那麼伺服器將丟失 最近寫入、且仍未儲存到快照中的那些資料。從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方 式: AOF 持久化,將修改的每一條指令記錄進檔案appendonly.aof中   推薦(並且也是預設)的措施為每秒 fsync 一次, 這種 fsync 策略可以兼顧速度和安全性。   redis4.0開始支援RDB和AOF混合持久化。 Redis資料恢復
:   aof檔案開頭是rdb的格式, 先載入 rdb內容再載入剩餘的 aof。   aof檔案開頭不是rdb的格式,直接以aof格式載入整個檔案。 可以直接覆蓋原來檔案的原因是:redis記憶體中存在的資料,肯定是當前的所有資料,因此,不需要將aof檔案或者rdb檔案中的資料在進行讀取,重寫,然後再覆蓋的行為。而是直接將當前記憶體的所有資料直接進行寫檔案的動作,覆蓋之前的即可。
  • Redis將AOF重寫程式放到子程序(後臺)裡執行。這樣處理的最大好處是:  
    • 子程序進行AOF重寫期間,主程序可以繼續處理命令請求;
    • 子程序帶有主程序的資料副本,使用子程序而不是執行緒,可以避免在鎖的情況下,保證資料的安全性。
  • 子程序在進行AOF重寫期間,伺服器處理新的命令可能對現有的資料進行修改,導致當前資料庫和重寫後的AOF檔案中的資料不一致。

  為了解決這種資料不一致的問題,Redis增加了一個AOF重寫快取,這個快取在fork出子程序之後開始啟用,Redis伺服器主程序在執行完寫命令之後,會同時將這個寫命令追加到AOF緩衝區和AOF重寫緩衝區。

  • 即子程序在執行AOF重寫時,主程序需要執行以下三個工作:  
    • 執行client發來的命令請求;
    • 將寫命令追加到現有的AOF檔案中;
    • 將寫命令追加到AOF重寫快取中。

  完成AOF重寫之後

    • 當子程序完成對AOF檔案重寫之後,向父程序傳送一個完成訊號,父程序會呼叫一個訊號處理函式,完成以下工作:

      •   將AOF重寫快取中的內容全部寫入到新的AOF檔案中;新的AOF檔案中儲存當前資料庫最新的狀態;
      •   對新的AOF檔案進行改名,原子的覆蓋原有的AOF檔案;完成新舊兩個AOF檔案的替換。
  • 在整個AOF後臺重寫過程中,“主程序寫入命令到AOF快取”和“對新的AOF檔案改名,覆蓋舊檔案。會造成主程序阻塞。

  AOF重寫是為了解決AOF檔案體積膨脹的問題,重寫過程基本上不影響Redis主程序處理命令請求;

  AOF重寫實際上是針對資料庫的當前狀態來進行的,重寫過程中不會讀寫、也不適用原來的AOF檔案;

  AOF可以由使用者手動觸發,也可以由伺服器自動觸發。

 Redis過期策略:

  Keys的過期時間使用Unix時間戳儲存;

  Redis keys過期有兩種方式:被動和主動方式;

  當一些客戶端嘗試訪問它時,key會被發現並主動的過期。同時,定時隨機測試設定keys的過期時間。所有這些過期的keys將會從金鑰空間刪除。

    具體就是Redis每秒10次做的事情:

    1. 測試隨機的20個keys進行相關過期檢測。
    2. 刪除所有已經過期的keys。
    3. 如果有多於25%的keys過期,重複步奏1.

   在複製AOF檔案時如何處理過期

    為了獲得正確的行為而不犧牲一致性,當一個key過期,DEL將會隨著AOF文字一起合成到所有附加的slaves。在master例項中,這種方法是集中的,並且不存在一致性錯誤的機會。然而,當slaves連線到master時,不會獨立過期keys(會等到master執行DEL命令),他們任然會在資料集裡面存在,所以當slave當選為master時淘汰keys會獨立執行,然後成為master。

Redis事務

  事務可以一次執行多個命令, 並且帶有以下兩個重要的保證:

    • 事務是一個單獨的隔離操作:事務中所有命令都會序列化、按順序地執行。在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。

    • 事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

  Redis 在事務失敗時不進行回滾,而是繼續執行餘下的命令:

    • Redis 命令只會因為錯誤的語法而失敗(並且這些問題不能在入隊時發現),或是命令用在了錯誤型別的鍵上面:這也就是說,失敗的命令是由程式設計錯誤造成的,這些錯誤應該在開發的過程中被發現,不應該出現在生產環境中。
    • 因為不需要對回滾進行支援,所以 Redis 的內部可以保持簡單且快速。
Redis快取穿透和快取雪崩,快取擊穿:   快取穿透:查詢一個數據,發現redis記憶體資料庫沒有,即快取沒有命中,於是向持久層資料庫查詢。發現也沒有,於是本次查詢失敗。當用戶很多,快取都沒有命中,都去請求了持久層資料庫。會給持久層資料庫造成很大的壓力,即出現了快取穿透。   解決:1)布隆過濾器       布隆過濾器是一種資料結構,就是引入了k(k>1)個相互獨立的雜湊函式,保證在給定的空間、誤判率下,完成元素判重的過程。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。       Bloom-Filter演算法的核心思想就是利用多個不同的Hash函式來解決“衝突”。       Hash存在一個衝突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少衝突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那麼該元素肯定不在集合中。只有在所有的Hash函式告訴我們該元素在集合中時,才能確定該元素存在於集合中。這便是Bloom-Filter的基本思想。     2)快取空物件       當儲存層不命中後,返回的空物件也將其快取起來,同時設定一個過期時間,之後再訪問這個資料將會從快取中獲取,保護了後端資料來源;   快取擊穿:指一個key非常熱點,大併發集中對這個key進行訪問,當這個key在失效的瞬間,仍然持續的大併發訪問就穿破快取,轉而直接請求資料庫。   解決方案:     1.在訪問key之前,採用SETNX(set if not exists,只有不存在的時候才設定,可以利用它來實現鎖的效果,控制只有一個請求去更新快取,其它的請求視情況要麼等待,要麼使用過期的快取)來設定另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key。     2.在請求一次資料庫後,建立一個短期的快取,在後續請求中請求快取。       建立一個佇列,當快取中訪問不到對應資料的時候,只允許一個請求區訪問資料庫,其餘請求全部等待,當訪問資料庫返回,儲存到記憶體之後,在統一進行返回。   快取雪崩:由於原有快取失效,新快取未到期間(例如:我們設定快取時採用了相同的過期時間,在同一時刻出現大面積的快取過期),所有原本應該訪問快取的請求都去查詢資料庫了,而對資料庫CPU和記憶體造成巨大壓力,嚴重的會造成資料庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。   解決辦法:     (1)限流降級:通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。     (2)將快取失效時間分散開。     (3)redis高可用:既然redis有可能掛掉,那就多增設幾臺redis,這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建的叢集。   Redis叢集原理分析   Redis Cluster 將所有資料劃分為 16384 個 slots(槽位),每個節點負責其中一部分槽位。槽位的資訊儲存於每個節點中。   當Cluster 的客戶端連線叢集時,會得到一份叢集的槽位配置資訊並將其快取在客戶端本地。當客戶端要查詢某個 key 時,可以直接定位到目標節點。為防止槽位的資訊在客戶端與伺服器不一致,Cluster 預設會對 key 值使用 crc16 演算法進行 hash 得到一個整數值,然後用這個整數值對 16384 進行取模來得到具體槽位。   HASH_SLOT = CRC16(key) mod 16384     跳轉重定位:當客戶端向一個錯誤的節點發出了指令,該節點會發現指令的 key 所在的槽位並不歸自己管理,它會向客戶端傳送一個特殊的跳轉指令攜帶目標操作的節點地址,告訴客戶端去連這個節點去獲取資料。客戶端收到指令後除了跳轉到正確的節點上去操作,還會同步更新糾正本地的槽位對映表快取,後續所有 key 將使用新的槽位對映表。   Redis叢集節點間的通訊機制   redis cluster節點間採取gossip協議進行通訊 
  • 維護叢集的元資料有兩種方式:集中式和gossip 
    集中式:        優點在於元資料的更新和讀取,時效性非常好,一旦元資料出現變更立即就會更新到集中式的儲存中,其他節點讀取的時候立即就可以立即感知到;不足在於所有的元資料的更新壓力全部集中在一個地方,可能導致元資料的儲存壓力。      gossip:        gossip協議包含多種訊息,包括ping,pong,meet,fail等等。        ping:每個節點都會頻繁給其他節點發送ping,其中包含自己的狀態還有自己維護的叢集元資料,互相通過ping交換元資料;        pong: 返回ping和meet,包含自己的狀態和其他資訊,也可以用於資訊廣播和更新;        fail: 某個節點判斷另一個節點fail之後,就傳送fail給其他節點,通知其他節點,指定的節點宕機了。       meet:某個節點發送meet給新加入的節點,讓新節點加入叢集中,然後新節點就會開始與其他節點進行通訊,不需要傳送形成網路的所需的所有CLUSTER MEET命令。傳送CLUSTER MEET訊息以便每個節點能夠達到其他每個節點只需通過一條已知的節點鏈就夠了。由於在心跳包中會交換gossip資訊,將會建立節點間缺失的連結。   gossip協議的優點在於元資料的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新,有一定的延時,降低了壓力;缺點在於元資料更新有延時可能導致叢集的一些操作會有一些滯後。
  • 10000埠 
每個節點都有一個專門用於節點間通訊的埠,就是自己提供服務的埠號+10000,比如7001,那麼用於節點間通訊的就是17001埠。 每個節點每隔一段時間都會往另外幾個節點發送ping訊息,同時其他幾點接收到ping訊息之後返回pong訊息。   網路抖動:機房網路突然之間部分連線變得不可訪問,然後很快又恢復正常。 為解決這種問題,Redis Cluster 提供了一種選項cluster-node-timeout,表示當某個節點持續 timeout 的時間失聯時,才可以認定該節點出現故障,需要進行主從切換。如果沒有這個選項,網路抖動會導致主從頻繁切換 (資料的重新複製)。   Redis叢集選舉原理分析 當slave發現自己的master變為FAIL狀態時,便嘗試進行Failover,以期成為新的master。由於掛掉的master可能會有多個slave,從而存在多個slave競爭成為master節點的過程, 其過程如下: 1.slave發現自己的master變為FAIL 2.將自己記錄的叢集currentEpoch加1,並廣播FAILOVER_AUTH_REQUEST 資訊 3.其他節點收到該資訊,只有master響應,判斷請求者的合法性,併發送FAILOVER_AUTH_ACK,對每一個epoch只發送一次ack 4.嘗試failover的slave收集master返回的FAILOVER_AUTH_ACK 5.slave收到超過半數master的ack後變成新Master(這裡解釋了叢集為什麼至少需要三個主節點,如果只有兩個,當其中一個掛了,只剩一個主節點是不能選舉成功的) 6.slave廣播Pong訊息通知其他叢集節點。   從節點並不是在主節點一進入 FAIL 狀態就馬上嘗試發起選舉,而是有一定延遲,一定的延遲確保我們等待FAIL狀態在叢集中傳播,slave如果立即嘗試選舉,其它masters或許尚未意識到FAIL狀態,可能會拒絕投票 •延遲計算公式:  DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms •SLAVE_RANK表示此slave已經從master複製資料的總量的rank。Rank越小代表已複製的資料越新。這種方式下,持有最新資料的slave將會首先發起選舉(理論上)。   叢集是否完整才能對外提供服務 當redis.conf的配置cluster-require-full-coverage為no時,表示當負責一個插槽的主庫下線且沒有相應的從庫進行故障恢復時,叢集仍然可用,如果為yes則叢集不可用。   Redis叢集為什麼至少需要三個master節點,並且推薦節點數為奇數? 因為新master的選舉需要大於半數的叢集master節點同意才能選舉成功,如果只有兩個master節點,當其中一個掛了,是達不到選舉新master的條件的。 奇數個master節點可以在滿足選舉該條件的基礎上節省一個節點,比如三個master節點和四個master節點的叢集相比,大家如果都掛了一個master節點都能選舉新master節點,如果都掛了兩個master節點都沒法選舉新master節點了,所以奇數的master節點更多的是從節省機器資源角度出發說的。   Redis叢集對批量操作命令的支援 對於類似mset,mget這樣的多個key的原生批量操作命令,redis叢集只支援所有key落在同一slot的情況,如果有多個key一定要用mset命令在redis叢集上操作,則可以在key的前面加上{XX},這樣引數資料分片hash計算的只會是大括號裡的值,這樣能確保不同的key能落到統一slot裡去,示例如下: mset {user1}:name zhuge {user1}:age 18 假設name和age計算的hash slot值不一樣,但是這條命令在叢集下執行,redis只會用大括號裡的 user1 做hash slot計算,所以算出來的slot值肯定相同,最後都能落在同一slot。   哨兵leader選舉流程 當一個master伺服器被某sentinel視為客觀下線狀態後,該sentinel會與其他sentinel協商選出sentinel的leader進行故障轉移工作。每個發現master伺服器進入客觀下線的sentinel都可以要求其他sentinel選自己為sentinel的leader,選舉是先到先得。同時每個sentinel每次選舉都會自增配置紀元(選舉週期),每個紀元中只會選擇一個sentinel的leader。如果所有超過一半的sentinel選舉某sentinel作為leader。之後該sentinel進行故障轉移操作,從存活的slave中選舉出新的master,這個選舉過程跟叢集的master選舉很類似。 哨兵叢集只有一個哨兵節點,redis的主從也能正常執行以及選舉master,如果master掛了,那唯一的那個哨兵節點就是哨兵leader了,可以正常選舉新master。 不過為了高可用一般都推薦至少部署三個哨兵節點。為什麼推薦奇數個哨兵節點原理跟叢集奇數個master節點類似。