Redis必知必會系列

1.常用命令

  //www.cnblogs.com/huozhonghun/p/11636053.html

2.Redis是什麼

  Redis 是 C 語言開發的一個開源的(遵從 BSD 協議)高性能鍵值對(key-value)的內存數據庫,是一種 NoSQL(not-only sql,泛指非關係型數據庫)的數據庫,常用於做數據庫、緩存,消息中間件,分佈式鎖等,豐富的數據類型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets);支持數據持久化;支持主從;支持分片。

3.數據類型

  • String 是 Redis 最基本的類型,可以理解成與 Memcached一模一樣的類型,一個 Key 對應一個 Value,鍵值分別最大能存儲 512M。String類型二進制安全的,意思是Redis的String包含任何數據,比如圖片或者序列化的對象。 
  • Hash是一個鍵值(key-value)的集合。Redis 的 Hash 是一個 String 的 Key 和 Value 的映射表,Hash 特別適合存儲對象。
  • List 列表是簡單的字符串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。List 就是鏈表(雙向鏈接),可以用來當消息隊列。
  • Set 是 String 類型的無序集合。集合是通過 hashtable 實現的。Set 中的元素是沒有順序的,而且是沒有重複的。
  • Zset(Sorted Set)和 Set 一樣是 String 類型元素的集合,且不允許重複的元素。當你需要一個有序的並且不重複的集合列表,那麼可以選擇 Sorted Set 結構。和 Set 相比,Sorted Set關聯了一個 Double 類型權重的參數 Score,使得集合中的元素能夠按照 Score 進行有序排列,Redis 正是通過分數來為集合中的成員進行從小到大的排序。實現方式:Redis Sorted Set 的內部使用 HashMap 和跳躍表(skipList)來保證數據的存儲和有序,HashMap 里放的是成員到 Score 的映射。而跳躍表裡存放的是所有的成員,排序依據是 HashMap 里存的 Score,使用跳躍表的結構可以獲得比較高的查找效率,並且在實現上比較簡單。

4.緩存中間件,Memcache和Redis的區別

  • Memcache:代碼層次類似Hash,非常簡單易用;支持簡單的數據類型,但是不支持數據的持久化存儲,如果服務器宕機了,數據是無法保存的;不支持主從同步;不支持分片機制,Value 的大小只有 1MB
  • Redis:數據類型豐富;支持數據磁盤持久化存儲;支持主從;支持分片,Value 的大小可以達到 1GB

5.Redis為什麼可以這麼快

  Redis官方提供的10W+QPS,(QPS即query per second,每秒內查詢次數)。
  • 完全基於內存,絕大部分請求是純粹的內存操作,執行效率高(Redis是採用單進程單線程模型的K-V數據庫,由c語言編寫,將數據存儲到內存中,讀寫數據的時候都不會受到硬盤IO速度的限制)
  • 數據結構簡單,對數據操作也簡單(Redis不使用表,它的數據庫不會預定義或者強制要求用戶對redis存儲的不同數據進行關聯,因此性能相比關係型數據庫要高出不止一個量級,其存儲結構就是鍵值對,類似於hashMap,hashMap的優勢就是查詢,查詢的時間複雜度是O(1))
  • 採用單線程,單線程也能處理高並發請求,想多核也可啟動多實例(在面對高並發的請求的時候,首先想要的是多線程來進行處理,將IO線程和業務線程分開,業務線程使用線程池來避免頻繁創建線程和銷毀線程,即便是一次請求,阻塞了也不會影響到其它請求。Redis單線程結構是指主線程是單線程的,主線程包含IO事件的處理,以及IO對應的相關請求的業務處理,此外,主線程還負責過期鍵的處理,複製協調,集群協調等等,這些除了IO事件之外的邏輯會被封裝成周期性的任務,由主線程周期性的處理,正因為採用單線程的設計,對於客戶端的所有讀寫請求,都由一個主線程串行的處理,因此多個客戶端同時對一個鍵進行寫操作的時候,就不會有並發的問題,避免了頻繁的上下文切換和鎖競爭,使得redis執行起來效率更高。單線程是可以處理高並發的請求的,並發並不是並行,並行性意外着服務器能夠同時執行幾個事情,具有多個計算的單元,而並發性IO流,意味着能夠讓一個計算單元來處理來自多個客戶端的流請求。Redis使用單線程配合上IO多路復用,將可以大幅度的提升性能,在多核CPU流行的今天,只要一個線程,只用一個核很浪費,CPU不是制約redis的性能瓶頸,瓶頸可能是機器內存的大小或者網絡帶寬。此外,可以在多核的服務器中啟動多個實例來利用多核的特性。注意,這裡的單線程只是在處理我們的網絡請求的時候,只有一個單線程來處理,一個正式的Redis server,在運行的時候,肯定不止一個線程的,例如Redis在進行持久化的時候,會根據實際情況,以子進程子線程的方式執行)
  • 使用多路I/O復用模型,非阻塞IO(Redis是跑在單線程中的,所有的操作都是按照順序線性執行的,但是由於讀寫操作等待用戶輸入或者輸出都是阻塞的,所以IO操作在一般情況下,往往不能直接返回,就會導致某一文件的IO阻塞,進而導致整個進程無法對其它客戶端提供服務。而IO多路復用就是為了解決這個問題而出現的)

6.FD

  File Descriptor文件描述符。在操作系統中,一個打開的文件通過唯一的描述符進行引用,該描述符是打開文件的元素據到文件本身的映射,在linux中,該描述符稱為文件描述符即File Descriptor,文件描述符用一個整數來表示。

7.傳統的阻塞I/O模型

         0
  當使用read或者write的對某一個文件描述符FD進行讀寫的時候,如果當前的FD不可讀或者不可寫,整個Redis服務就不會對其它的操作做出相應,導致整個服務不可用,這也就是傳統意義上的阻塞IO模型,阻塞模型會影響其它FD對應的服務,所以在需要處理多個客戶端任務的時候,往往都不會使用阻塞模型。此時,需要一種更高效的IO模型來支持Redis的高並發處理,就是IO復用多路模型。

8.多路I/O復用模型

  最重要的函數調用就是Select系統調用。Select可以同時監控多個文件描述符的可讀,可寫情況,當其中的某些文件描述符可讀或者可寫的時候,select方法就會返回可讀以及可寫的文件描述符個數,也就是說,Selector是負責監聽我們的文件是否可讀或者可寫的,監聽的任務交給Selector之後呢,程序就可以做其它的事情了,而不被阻塞了。
      0
與此同時,也有其它的多路IO復用函數,如:epoll、kqueue,evport。它們相比select的性能是更加優秀的,都使用了內核內部的結構,能夠服務幾十萬的文件描述符。
這麼多的函數,redis採用哪個?
  • 因地制宜的,Redis需要在多個平台下運行,為了最大化的提高執行效率和性能,會根據編譯平台的不同選擇不同的IO多路復用函數作為子模塊,提高給上層統一的接口
  • 優先選擇時間複雜度為O(1)的IO多路復用函數作為底層實現
  • 一般以時間複雜度為O(n)的select作為保底(如果編譯環境沒有epoll、kqueue、evport、就會使用select作為備選方案,使用時會掃描全部的文件描述符,性能較差,時間複雜度是O(n))  
  • 基於react設計模式監聽I/O事件(Redis採用react設計模式來實現文件處理器的,文件事件處理器使用IO多路復用模塊,同時監聽多個FD,當accept、read,write等事件產生的時候,事件處理器就會回調FD綁定的事件處理器,雖然整個事件處理器是在單線程運行的,但是通過IO多路復用模塊的引用,實現了同時對多個FD讀寫的監控,提高了網絡通信模型的性能,同時來保證了整個Redis服務實現的簡單)

9.Redis內部內存管理

        0
  Redis 內部使用一個 redisObject 對象來表示所有的 key 和 value。
  • type :代表一個 value 對象具體是何種數據類型。
  • encoding :是不同數據類型在 redis 內部的存儲方式,比如:type=string 代表 value 存儲的是一個普通字符串,那麼對應的 encoding 可以是 raw 或者是 int,如果是 int 則代表實際 redis 內部是按數值型類存儲和表示這個字符串的,當然前提是這個字符串本身可以用數值表示,比如:”123″這樣的字符串。
  • vm 字段:只有打開了 Redis 的虛擬內存功能,此字段才會真正的分配內存,該功能默認是關閉狀態的。 Redis 使用 redisObject 來表示所有的 key/value 數據是比較浪費內存的,當然這些內存管理成本的付出主要也是為了給 Redis 不同數據類型提供一個統一的管理接口,實際作者也提供了多種方法幫助我們盡量節省內存使用。

10.緩存穿透

  是什麼?緩存和數據庫中都存在的數據,用戶不斷請求,造成數據庫壓力過大
  原因?
  • 業務自身代碼或數據出現問題
  • 一些惡意攻擊、爬蟲造成大量空的命中
  解決?
  • 設置布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,攔截不存在的數據
  • 從緩存取不到的數據,在數據庫中也沒有取到,這時也可以將key-value對寫為key-null,緩存有效時間可以設置短點,如30秒(設置太長會導致正常情況也沒法使用)

11.緩存擊穿

  是什麼?緩存中不存在但數據庫中存在的數據,用戶並發請求特別多,造成數據庫壓力過大
  解決?
  • 設置熱點數據永遠不過期
  • 加互斥鎖

12.緩存雪崩

  是什麼?大量緩存集中在某一個時間段失效,大量數據會直接去訪問數據庫。
  解決?沒有最佳方案,看情況而定。
  • 設置redis集群和DB集群的高可用,如果redis出現宕機情況,可以立即由別的機器頂替上來。這樣可以防止一部分的風險
  • 使用互斥鎖,在緩存失效後,通過加鎖或者隊列來控制讀和寫數據庫的線程數量。比如:對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。單機的話,可以使用synchronized或者lock來解決,如果是分佈式環境,可以是用redis的setnx命令來解決
  • 不同的key,可以設置不同的過期時間,讓緩存失效的時間點不一致,盡量達到平均分佈
  • 永遠不過期,redis中設置永久不過期,這樣就保證了,不會出現熱點問題,也就是物理上不過期
  • 可以利用ehcache等本地緩存來暫時支持,但主要還是要對源服務訪問採取限流、資源隔離(熔斷),降級措施等

13.如何從海量Key裏面查詢出某一固定前綴的Key

  先確定數據量,再從以下方式選擇

(一)使用keys pattern,查找所有符合給定模式pattern的key,keys一次性返回所有匹配的key,鍵的數量過大會使服務卡頓。

(二)使用非阻塞scan指令,但獲取的數據是隨機的,可能會獲取到重複key,無法統計。

  Scan指令:Scan cursor [MATCH pattern] [COUNT count],其中cursor是游標,MATCH pattern是需要查找的模式,COUNT count是查找的數量。
  (1)Scan指令是一個基於游標的迭代器,需要基於上一次的游標延續之前的迭代過程。意味着命令每次被調用都需要使用上一次調用返回的游標作為該次調用的游標參數,依次來延續之前的迭代過  程。    
  (2)以0作為游標開始一次新的迭代,直到命令返回遊標0完成一次遍歷。當scan指令的游標參數即cursor被置為0的時候,服務器將開始一次新的迭代,而當服務器向用戶返回值為0的游標的時候,就表示迭代完成,以0作為游標開始新一次的迭代,一直調用scan指令直到命令返回遊標0,稱這個過程為一次完整的遍歷。    
  (3)不保證每次執行都返回某個給定數量的元素,支持模糊查詢。Scan增量式迭代命令並不保證每次執行都會返回某個給定數量的元素,甚至可能返回0個元素,但只要命令返回的游標不是0,應用程序就不應該將迭代視作結束,命令返回的元素數量總是符合一定的規則的。對於一個大數據集來說,增量式迭代命令每次最多可能會返回數十個元素,而對於一個足夠小的數據集來說,可能會一次迭代返回所有的key,類似於keys指令,scan可以通過給定match參數的方式傳入要查找鍵位的模糊匹配方式,讓命令只返回和給定模式下向匹配的元素。    
  (4)一次返回的數量不可控,只能是大概率符合count參數。此外,對於增量式迭代命令是沒有辦法保證每次迭代所返回的元素數量的,我們可以使用count選項對命令的行為進行一定程度的調整,count選項的作用就是讓用戶告知迭代命令,在每次迭代中,應該從數據集里返回多少元素,使用count選項對於增量式迭代命令相當於是一種提示,大多數情況下,這種提示都是比較有效的控制返回的數量的。值得注意的是,count數量並不能嚴格的控制返回的key的數量,只能說是一個大致的約束,並非每次迭代都會返回count數量的約束,用戶可以根據自己的需求在每次迭代中隨意改變count的值,只要記得將上次迭代返回的游標用到下次迭代的游標裏面就可以了。        

14.分佈式鎖需要解決的問題

  分佈式鎖是控制分佈式系統或者不同系統之間共同訪問共享資源的一種鎖的實現,如果不同的系統或者同一個系統不同主機之間共享了某個資源的時候,往往需要互斥來防止彼此干擾,進而保證一致性。
  • 互斥性(任意時刻只能有一個客戶端獲取鎖,不能同時有兩個客戶端獲取到鎖)
  • 安全性(鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除掉)
  • 死鎖(獲取鎖的客戶端因為某些原因而宕機,而未能釋放鎖,其它客戶端再也無法獲取到該鎖,而導致的死鎖,此時需要有機制避免這種問題的發生)
  • 容錯(部分redis節點宕機,客戶端要保證可以獲取鎖和釋放鎖)

15.Redis實現分佈式鎖

  SETNX key value,如果key不存在,則創建並賦值。SETNX即set if not exist,時間複雜度是O(1),返回值:設置成功,返回1;設置失敗,返回0。

正因為SETNX的操作是原子性的,因此初期便被用在實現分佈式鎖,在執行某段代碼邏輯的時候,先嘗試使用SETNX對某個key設值,如果設值成功,則證明此時沒有別的線程在執行該段代碼,或者說佔用該獨佔資源,這個時候線程就可以順利的去執行該段代碼邏輯了,如果設值失敗,則證明此時有別的程序或者線程佔用該資源,那麼當前線程就需要等待直至設值SETNX成功,如果設值SETNX的key,這個key就會長久有效了,後續線程如何能再次獲得到鎖,此時需要給該key設值一個過期時間。

(一)使用EXPIRE key seconds設值過期時間:

  • 設值key的生存時間,當key過期的時候(生存時間為0),會被自動刪除
  • 缺點:原子性得不到滿足(如果SETNX與EXPIRE結合使用,它們分別都是原子性的,但是組合到一起卻不是原子性的)

(二)完美方案

  Redis2.6.12之後,通過set操作,將SETNX和EXPIRE柔和到一起去執行,此時就滿足了分佈式鎖的原子性需求。
命令格式:SET key value [EX seconds] [PX milliseconds] [NX][XX],操作成功完成時候,返回OK,否則返回nil。
  • EX seconds,設值鍵的過期時間為second秒。  
  • PX milliseconds,設值鍵的過期時間為millisecond毫秒。  
  • NX,只在鍵不存在的時候,才對鍵進行設值操作。效果等同於SETNX。  
  • XX,只在鍵已經存在的時候,才對鍵進行設置操作。

16.大量的key同時過期的注意事項,如何避免系統卡斷現象

  在設值key的過期時間的時候,給每個key加上隨機值就行了,使得過期時間分散一些,很大程度上避免卡頓現象的發生

17.實現異步隊列

  • 使用List作為隊列,rpush生產消息,lpop消費消息,實現先進先出的效果,缺點:沒有等待隊列里有值就直接消費,可以在應用層引入sleep機制去調用lpop重試來彌補
  • BLPOP key [key…] timeout:阻塞直到隊列有消息或者超時。BLPOP可以替代sleep做更精準的阻塞控制,缺點:只能供一個消費者消費
  • pub/sub,主題訂閱者模式,發送者(pub)發送消息,訂閱者(sub)接收消息,訂閱者可以訂閱任意數量的頻道,可以實現一對多的消費隊列,缺點:消息的發佈是無狀態的,無法保證消息是否送達達,解決這個問題,可以使用專業的消息隊列:kafka等

18.Redis持久化

  Redis提供了三種持久化的方案,將內存中的數據保存到磁盤中。

(一)RDB(快照)持久化:保存某個時間點的全量數據快照,為一個二進制文件

(1)缺點:

  • 內存數據的全量同步,數據量大會由於I/O而嚴重影響性能的。每次快照持久化都是將快照數據完整的寫入到磁盤一次,並不是增量的只同步臟數據,如果數據量大的話,並且寫操作比較多的時候b必然會引起大量的磁盤IO操作,可能會嚴重性能。
  • 可能會因為Redis掛掉而丟失從當前至最近一次快照期間的數據,由於快照方式是在一定間隔時間做一次的快照後的所有修改,如果應用要求不能丟失任何修改的話,可以採用AOF。

(2)Redis參數設置說明:

  #持久化的時間策略,可以根據自身redis的寫入情況合理配置
  save 900 1 # 表示900秒之內如果有一條是寫入指令就觸發產生一次快照。產生一次快照就可以理解為是一次備份了。
  save 300 10 # 表示300秒以內如果有10條寫入就會產生快照。如果變動數是大於0但是還沒有到10條的話,就會等到900秒過後才去做備份。
  save 60 10000 # 表示60秒內如果有一萬條寫入就進行一次備份。
  save “” # 禁用RDB配置。
  # yes表示當備份進程出錯的時候,主進程就停止接收新的寫入操作了,這樣做是為了保護持久化的數據一致性的問題。如果自己的業務有完善的監控系統,可以禁止此項配置,否則開啟。
  stop-writes-on-bgsave-error yes
  # rdb壓縮,yes表示在備份的時候需要將rdb文件進行壓縮後才保存。建議設置為no,因為redis本身是cpu密集型服務器,再開啟壓縮,會帶來更多的cpu的消耗,相比硬盤成本,cpu更值錢。
  rdbcompression yes

(3)命令生成RDB文件

  ①SAVE,阻塞Redis的服務器進程,直到RDB文件被創建完畢(很少被使用,因為save操作是在主線程中保存快照的,由於Redis是用一個主線程來處理所有的請求的,這種方式會阻塞所有的客戶端請求)
  ②BGSAVE,Fork出一個子進程來創建RDB文件,不阻塞服務器進程(記錄接收BGSAVE當時的數據庫狀態,父進程繼續處理接收到的命令,子進程完成文件的創建之後會發送信號給父進程即Redis的主進程,而於此同時,父進程處理命令的同時,通過輪詢來接收子進程的信號,不阻塞服務器進程。BGSAVE指令是使用後台方式保存RDB文件的,調用此命令後會立刻返回OK返回碼,Redis會產生一個子進程進行處理,並立刻恢復對客戶端的服務,在客戶端可以使用last save這個指令,產看操作是否成功,last save記錄了上一次成功執行save或者bgsave命令的時間)

(4)自動化觸發RDB持久化的方式

  ①根據redis.conf配置裏面的save m n規則定時觸發(用的BGSAVE)
  ②主從複製時,主節點自動觸發(從節點全量複製的時候,主節點發送RDB文件給從節點完成複製操作後,主節點就會觸發BGSAVE)
  ③執行Debug Reload
  ④執行shutdown且沒有開啟AOF持久化

(5)BGSAVE原理

0
  ①在執行了GBSAVE指令之後,首先回去檢查當前子進程有沒有正在執行的AOF/RDB子進程,有的話返回錯誤。這樣做是為了防止子進程之間的競爭,也就意味着在執行GBSAVE期間呢,客戶端發送的SAVE/GBSAVE命令會被服務器j拒絕執行,如果此時沒有發生相關子進程,則會觸發持久化
  ②觸發了持久化,就會調用redis源碼裏面的rdbSaveBackground方法執行fork系統調用。其實執行BGSAVE指令來生成RDB文件的時候,本質就是調用了操作系統的系統調用fork指令
  ③系統調用fork(),就是用來創建進程的,而Linux下fork系統調用實現了Copy-on-Write寫時複製。傳統方式下fork函數在創建子進程的時候直接把所有的資源複製給子進程,這種實現方式簡單,但是效率低下,而且複製的資源可能對子進程毫無用處,linux為了降低創建子進程的成本,改進fork實現方式,當父進程創建子進程的時候,內核只為子進程創建虛擬空間,父子兩個進程使用的是相同的物理空間,只有父子進程發生更改的時候才會為子進程分配獨立的物理空間
  ④改進的實現方式稱為寫時複製,Copy-on-Write(簡稱COW)是計算機程序設計領域的優化策略,核心思想如果有多個調用者同時要求相同資源(如內存或者磁盤上的數據存儲),它們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容的時候,系統才會真正複製一份專用副本給該調用者,而其它調用者所見到的最初的資源仍然保持不變。

(二)AOF(append-only-file)持久化,通過保存Redis服務器所執行的寫狀態來記錄數據庫的。

(1)記錄下除了查詢以外的所有變更數據庫狀態的指令(以redis協議格式來保存的)

(2)以append的形式追加保存到AOF文件中,以增量的形式(數據庫會記錄下所有變更數據庫狀態的指令,除了指定數據庫的查詢命令,其它的命令都是來自client)

(3)refis.conf配置說明

  ①aof持久化默認是關閉的,可以通過修改refis.conf配置,appendonly no修改為appendonly yes,生成的文件名稱是appendonly.aof,修改配置後需要重啟redis服務器
  ②appendfsync everysec該配置主要用來配置aof文件的寫入方式的,可以接收三個不同的參數分別是,always、everysec、no。
  • always表示一旦緩存區的內容發生變化,就總是及時的將緩存區的內容寫入到aof中
  • everysec是將緩存區的內容每隔一秒去寫入到aof中
  • no是將寫入aof的操作交由操作系統來決定
  • 一般而言,為了提高效率,操作系統會將緩存區被填滿才會開始同步數據到磁盤中。一般推薦everysec默認的方式,速度比較快,安全性比較高

(4)日誌重寫解決AOF文件大小不斷增大的問題原理

  • 首先調用fork(),創建一個子進程
  • 子進程把新的AOF寫到一個臨時文件裏面,不依賴原來的AOF文件(新的AOF的重寫是直接把當前內存的數據生成對應的命令,並不需要讀取老的AOF文件進行分析或者合併)
  • 主進程持續將新的變動同時寫到內存和原來的AOF里(這樣即使寫入失敗,也能保證數據的安全)
  • 主進程獲取子進程重寫AOF的完成信號,往新AOF同步增量變動
  • 使用新的AOF文件替換掉舊的AOF文件。

(三)AOF和RDB的混合模式

  Redis4.0之後,推出了結合AOF和RDB的混合模式,並且作為默認的方式來使用,推薦使用。(RDB作為全量備份,AOF作為增量備份,來提升備份的效率)
  • BGSAVE做鏡像全量持久化,AOF做增量持久化,因為BGSAVE會耗費較長時間,不夠實時,在停機的時候會導致大量丟失數據的問題,需要AOF配合使用,在Redis重啟的時候會使用BGSAVE持久化文件,重新構建內容,再使用AOF重放近期的操作指令,來實現完整恢復之前的狀態
  • AOF重寫機制,它其實也是先寫一份全量數據到AOF文件中,再追加增量,只不過全量數據是以redis命令格式寫入的,那麼是否可以先以RDB格式寫入全量數據,再追加增量數據呢,這樣既可以提高重新和恢復速度,也可以減少文件的大小,還同時可以保證數據的完整性,能夠結合RDB和AOF的優點,AOF和RDB的混合模式正是在這種需求下誕生的。在此種方式下,子進程在做AOF重寫的時候,會通過管道從父進程讀取增量數據並緩存下來,那麼在以RDB格式保存全量數據的時候,也會從管道讀取數據,同時不會造成管道的阻塞,也就是說,AOF文件前半段是RDB格式的全量數據,而後半段是Redis命令格式的增量數據。
    0

19.RDB和AOF文件共存情況下的恢複流程

    0

20.RDB和AOF的優缺點

  • RDB優點,RDB本質上是一個內存快照,保存了創建RDB文件那個時間點的Redis全量數據,全量數據快照,文件小,創建恢復快
  • RDB缺點,無法保存最近一次快照之後的數據
  • AOF優點,AOF本質上是一份執行日誌,保存所有被Redis更改的指令,可讀性高,適合保存增量數據,數據不易丟失
  • AOF缺點,文件體積大,恢復時間長

21.Pipeline的好處

  Pipeline和Linux的管道類似,它可以讓Redis批量執行指令。
  Redis基於請求/響應模型,單個請求處理需要一一應答。如果需要同時執行大量命令,則每條命令都需要等待上一條命令執行完畢後才能繼續執行,這中間不僅僅多了RTT(來回交互時間),還頻繁調用系統IO,發送網絡請求。為了提升效率,就可以使用Pipeline,批量執行指令,可以節省多次IO和請求響應往返的時間。但是如果指令之間存在依賴關係,則建議分批發送指令。

22.主從同步原理

  Redis一般是使用一個Master節點來進行寫操作,而若干個Slave節點進行讀操作,Master和Slave分別代表了一個個不同的RedisServer實例,另外,定期的數據備份操作也是單獨選擇一個Slave去完成,這樣可以最大程度發揮Redis的性能,為的是保證數據的弱一致性和最終一致性。我們不需要保證Master和Slave的數據是實時同步的,但是在一段時間後Master和Slave的數據是趨於同步的,這就是最終一致性。
第一次同步時,主節點做一次BGSAVE,並同時將後續修改操作記錄到內存Buffer,待完成後,將RDB文件全量同步到從節點裏面,從節點接收完成後,將RDB鏡像加載到內存中,加載完成後,再通知主節點,將期間修改的操作記錄,即增量數據同步到從節點進行存放,到某個時間點之前的全量數據同步完成後,把該時間點後的增量數據進行同步,這樣就完成了整個同步的過程。
    0

(一)全同步過程

  1. Slave發送sync命令到Master。
  2. Master啟動一個後台進程,將Redis中的數據快照保存到文件中(BGSAVE)
  3. Master將保存數據快照期間接收到的寫命令緩存起來
  4. Master完成寫文件操作後,將該文件發送給Slave
  5. 使用新的AOF文件替換掉舊的AOF文件(Slave將接收到的文件保存到磁盤中,加載文件到內存中去恢複數據快照)
  6. Slave完成數據快照恢復後,Master將這期間收集的增量寫命令發送給Slave端,進行回放

(二)增量同步過程

  1. Master接收到用戶的操作指令,執行相應的操作函數,判斷是否需要傳播到Slave
  2. 將操作記錄追加到AOF文件,首先將操作轉換成Redis內部的協議格式,並以字符串的形式存儲,並追加到AOF文件
  3. 將操作傳播到其它Slave,主要有兩步處理,首先對齊主從庫(確保是該操作的從數據庫),然後,往響應緩存寫入指令
  4. 將緩存中的數據發送給Slave

23.Redis Sentinel工作原理

  主從模式的弊端,就是不具備高可用性,當master掛掉後,redis將不能對外提供寫操作,它可以解決主從同步Master宕機後的主從切換問題。
官方提供的集群管理工具,本身是獨立運行的進程,主要功能有:
  • 監控:檢查主從服務器是否運行正常
  • 提醒:可以通過API向管理員或者其它應用程序發送故障通知
  • 自動故障遷移:主從切換(在Master宕機後,將其中一個Slave升級為Master,將其他的Slave從該節點同步數據)

   主從切換過程:PING命令時間超時》master被 Sentinel 標記為主觀下線》足夠數量的 Sentinel同意後,master標記為客觀下線》Sentinel投票選出新master,將剩餘slave指向新的master進行數據複製

24.流言協議Gossip(在雜亂無章中尋求一致)

  • 每個節點都隨機的與對方通信,最終所有節點的狀態達成一致
  • 種子節點定期隨機向其他節點發送節點列表以及需要傳播的消息
  • 不保證信息一定會傳遞給所有節點,但是最終會趨於一致

25.Redis集群原理

(一)如何從海量數據里快速找到所需

  分片:按照某種規則去劃分數據,分散存儲在多個節點上。
  通過分片實現數據分片來減輕單節點服務器的壓力。

(二)原理

  Redis集群採用無中心結構,每個節點保存數據和整個集群的狀態,每個節點都和其他所有節點連接,節點之間使用Gossip協議去傳播信息以及發現新的節點。

(三)目的

  將不同的key分散放置到不同的redis節點

(四)一致性哈希算法

  通常的做法是獲取key的hash值,根據節點數來求模,這樣的方法有弊端,當需要動態增減節點時,會造成大量的key無法被命中,所以引入了一致性哈希算法
  (1)服務器對 2^32 取模,將哈希值空間組成虛擬的圓環,整個圓環按順時針方向組織,每個節點依次為 0、1,2…2^32-1(即哈希值是一個32位無符號整形)
  (2)對數據使用同樣的 Hash 算法,並映射到相同的圓上
  (3)然後從數據映射到的位置開始順時針尋找,將數據保存到找到的第一個服務器上
  無論增減服務器,受影響的數據僅僅是此服務器到其環空間中前一台服務器(即沿着逆時針方向行走遇到的第一台服務器)之間數據,其它不會受到影響,一致性哈希算法具有較好的容錯性和可擴展性。
  一致性哈希算法在節點較少時,可能因為節點分佈不均勻,造成數據傾斜
  解決:引入虛擬節點解決數據傾斜問題,即對每個服務器節點計算多個Hash,計算位置都放置一個虛擬服務器節點

26.Redis怎麼用

  結合 Spring Boot 使用,一般有兩種方式。一種是直接通過 RedisTemplate 來使用,另一種是使用 Spring Cache 集成 Redis(也就是註解的方式)。

27.緩存和數據庫數據一致性問題

  分佈式環境下非常容易出現緩存和數據庫間數據一致性問題。
  如果項目對緩存要求強一致性,那麼就不要使用緩存。
  如果項目對緩存不要求強一致性,我們只要開闢一個異步任務去保證最終一致性即可。
  方案:
  1.延時雙刪策略(僅在休眠時間內數據存在不一致,數據庫已經更新,緩存還是舊值)
  • 先刪除緩存
  • 再寫數據庫
  • 休眠指定毫秒
  • 再次刪除緩存
  2.通過消息隊列來更新緩存
  • 實現了異步更新緩存,降低了系統的耦合性
  • 但是破壞了數據變化的時序性
  • 成本相對比較高
  3.通過binlog來同步mysql數據庫到redis中
  Mysql數據庫任何時間對數據庫的修改都會記錄在binglog中;當數據發生增刪改,創建數據庫對象都會記錄到binlog中,數據庫的複製也是基於binlog進行同步數據
  • 在mysql壓力不大情況下,延遲較低
  • 和業務完全解耦
  • 解決了時序性問題
  • 成本相對而言比較大

28.淘汰策略

  • volatile-lru(Least Recently Used):從已設置過期時間的數據集中優先對最近最少使用的數據淘汰
  • volatile-ttl:從已設置過期時間的數據集中優先剩餘時間短的數據淘汰
  • volatile-random:從已設置過期時間的數據集中隨機選擇數據淘汰
  • allkeys-lru:從所有數據集中優先對最近最少使用的數據淘汰
  • allkeys-random:從數據集隨機選擇數據淘汰
  • no-enviction:不淘汰策略,若超過最大內存,返回錯誤信息
  Redis 4.0開始 加入了 LFU(Least Frequency Use)淘汰策略,包括 volatile-lfu 和 allkeys-lfu,通過統計訪問頻率,優先對訪問頻率最少的數據淘汰。