redis的官網簡介
????Redis 是一個開放原始碼(BSD 許可)、記憶體中的資料結構存盤,用作資料庫、快取和訊息代理,Redis 提供資料結構,例如string字串、hash散列、list串列、set集合、sorted set帶有范圍查詢的排序集、bitmaps位圖、hyperloglogs超級日志、geospatial indexes地理空間索引、 streams流,Redis 具有內置的復制、 Lua 腳本、 LRU 識訓、事務和不同級別的磁盤持久性,并通過 Redis Sentinel 提供高可用性服務,并通過 Redis Cluster 提供自動磁區,
????您可以對這些型別運行原子操作,比如附加到字串; 在散列中遞增值; 將元素推入串列; 計算集合的交集、并集和差集; 或者獲得排序集中排名最高的成員,
????為了獲得最佳性能,Redis 使用記憶體中的資料集,根據您的用例,您可以通過定期將資料集轉儲到磁盤或將每個命令附加到基于磁盤的日志中來持久化資料,如果您只需要一個功能豐富的網路化記憶體快取,那么還可以禁用持久性,
????Redis 還支持異步復制,具有非常快的非阻塞第一同步,自動重連接和部分重新同步的網路分割,
????其他功能包括:
- Transactions 交易
- Pub/Sub 發布/訂閱
- Lua scriptingLua 腳本
- Keys with a limited time-to-live 有限時間的keys
- LRU eviction of keysLRU 識訓鑰匙
- Automatic failover 自動故障轉移
?
????Redis 是用 ANSI c 撰寫的,在大多數 POSIX 系統中作業,比如 Linux、 * BSD 和 OS x,沒有外部依賴性,Linux 和 OS x 是 Redis 開發和測驗最多的兩個作業系統,我們建議使用 Linux 進行部署,Redis 也許可以在諸如 SmartOS 這樣的 solaris 衍生系統中作業,但是這種支持是最好的努力,沒有對 Windows 構建的官方支持,
?
redis的快
????redis為什么能風靡全球,首要原因就是因為它快,在網路發展如此迅速的時代,時間就是金錢,一個良好的快速反饋時間是第一要素,那為什么redis能這么快?
純記憶體存盤
????redis將所有資料放在記憶體中,非資料同步正常作業中,是不需要從磁盤讀取資料的,0次IO,記憶體回應時間大約為100納秒,所以理論上redis是可以達到100*1000qps的,
I/O多路復用
????I/O 多路復用(select/poll/epoll)機制中多路是指多個連接,復用是指一個執行緒多次重復使用,也就是一個執行緒處理多個 IO 流,redis采用的是epoll,在 redis 只運行單執行緒的情況下,該機制允許內核中同時存在多個監聽套接字和已連接套接字,內核會一直監聽這些套接字上的連接請求或資料請求,一旦有請求到達就會交給 redis 執行緒處理,這樣就實作了一個 redis 執行緒處理多個 IO 流的效果,看下圖:
?

- 一個 socket 客戶端與服務端連接時,會生成對應一個套接字描述符(套接字描述符是檔案描述符的一種),每一個 socket 網路連接其實都對應一個檔案描述符,
- 多個客戶端與服務端連接時,redis 使用 「I/O 多路復用程式」 將客戶端 socket 對應的 FD 注冊到監聽串列(一個佇列)中,當客服端執行 read、write 等操作命令時,I/O 多路復用程式會將命令封裝成一個事件,并系結到對應的 檔案描述符(FD ) 上,
- **「檔案事件處理器」**使用 I/O 多路復用模塊同時監控多個 FD 的讀寫情況(如下圖),當 accept、read、write等檔案事件產生時,檔案事件處理器就會回呼 FD 系結的事件處理器進行處理相關命令操作,
- 整個檔案事件處理器是在單執行緒上運行的,但是通過 I/O 多路復用模塊的引入,實作了同時對多個 FD 讀寫的監控,當其中一個 client 端達到寫或讀的狀態,檔案事件處理器就馬上執行,從而就不會出現 I/O 堵塞的問題,提高了網路通信的性能,

單執行緒
????單執行緒避免了執行緒背景關系切換以及加鎖釋放鎖帶來的消耗,對于服務端開發來說,鎖和執行緒切換通常是性能殺手,當然了,單執行緒也會有它的缺點,也是redis的噩夢:阻塞,如果執行一個命令過長,那么會造成其他命令的阻塞,對于Redis是十分致命的,所以Redis是面向快速執行場景的資料庫,
????這里需要提到的是,在redis4.0之后引入了多執行緒,像惰性洗掉,持久化、集群資料同步等操作,都是由額外的執行緒執行,而redis單執行緒是指主執行緒專注于網路 IO 和鍵值對讀寫,在redis6引入的多執行緒則是真正為了提高 I/O 的讀寫性能而引入的,它的主要實作思路是將主執行緒的 I/O 讀寫任務拆分給一組獨立的子執行緒去執行,也就是說從 socket 中讀資料和寫資料不再由主執行緒負責,而是交給了多個子執行緒,這樣就可以使多個 socket 的讀寫并行化了,這么做的原因就在于,雖然在 redis 中使用了 I/O 多路復用,但我們知道資料在內核態空間和用戶態空間之間的拷貝是無法避免的,而資料的拷貝這一步是阻塞的,并且當資料量越大時拷貝所需要的時間就越多,所以多執行緒用于分攤同步讀寫 I/O 壓力,從而提升 redis 的 qps,但是注意,redis 的命令本身依舊是由 redis 主執行緒串行執行的,只不過具體的讀寫操作交給獨立的子執行緒去執行了,而這么做的好處就是不需要為 Lua 腳本、事務的原子性而額外開發多執行緒互斥機制,這樣一來 redis 的執行緒模型實作起來就簡單多了,因為和之前一樣,所有的命令依舊是由主執行緒串行執行的,只不過具體的讀寫任務交給了子執行緒,下圖是主執行緒跟io執行緒的互動情況:

????redis6中,多執行緒機制默認是關閉的,如果想啟動的話,需要修改 redis.conf 中的兩個配置,第一個是設定io執行緒是否開啟,第二個是設定其執行緒數,
- io-thread-do-reads yes
- io-threads 3
????除了redis之外,node.js、nginx也是單執行緒,他們都是服務器高性能的典范,
?
redis的基礎資料結構
string
????它是二進制安全的,可以存盤圖片或者序列化的物件,值最大存盤為512M,底層用sds來使用,相對于c的原生字串是char[]實作的好處之一是在獲取長度時不需要遍歷資料,
????簡單使用舉例: set key value、get key等
????應用場景:共享session、分布式鎖,計數器、限流,
????內部編碼有3種,int(8位元組長整型)/embstr(小于等于39位元組字串)/raw(大于39個位元組字串)
hash
????哈希型別是指v(值)本身又是一個鍵值對(k-v)結構
????簡單使用舉例:hset key field value 、hget key field
????內部編碼:ziplist(壓縮串列) 、hashtable(哈希表)
????應用場景:快取用戶資訊等,
注意點:如果開發使用hgetall,哈希元素比較多的話,可能導致redis阻塞,可以使用hscan,而如果只是獲取部分field,建議使用hmget,
list

????串列(list)型別是用來存盤多個有序的字串,一個串列最多可以存盤2^32-1個元素,
????簡單實用舉例: lpush key value [value …] 、lrange key start end
????內部編碼:ziplist(壓縮串列)、linkedlist(鏈表)
????應用場景: 訊息佇列,文章串列,
?
set

????集合(set)型別也是用來保存多個的字串元素,但是不允許重復元素,
????簡單使用舉例:sadd key element [element …]、smembers key
????內部編碼:intset(整數集合)、hashtable(哈希表)
????注意點:smembers和lrange、hgetall都屬于比較重的命令,如果元素過多存在阻塞Redis的可能性,可以使用sscan來完成,
????應用場景: 用戶標簽,生成亂數抽獎、社交需求,
?
zset
????已排序的字串集合,同時元素不能重復,
????簡單格式舉例:zadd key score member [score member …],zrank key member
????底層內部編碼:ziplist(壓縮串列)、skiplist(跳躍表)
????應用場景:排行榜,社交需求(如用戶點贊),
?
?
bitmaps
????用一個位元位來映射某個元素的狀態,在redis中,它的底層是基于字串型別實作的,可以把bitmaps成作一個以位元位為單位的陣列,
hyperloglogs
????用來做基數統計演算法的資料結構,如統計網站的瀏覽人數,幫程式自主去重,
geospatial indexes
????用來推算地理位置的資訊,兩地之間的距離,方圓幾里的人等,
streams
????官方把它定義為:以更抽象的方式建模日志的資料結構,redis的streams主要是一個append only file的資料結構,至少在概念上它是一種在記憶體中表示的抽象資料型別,只不過它們實作了更強大的操作,以克服日志檔案本身的限制,如果你了解MQ,那么可以把streams當做MQ,如果你還了解kafka,那么甚至可以把streams當做kafka,
????另外,這個功能有點類似于redis以前的Pub/Sub,但是也有基本的不同:
- streams支持多個客戶端(消費者)等待資料(Linux環境開多個視窗執行XREAD即可模擬),并且每個客戶端得到的是完全相同的資料,
- Pub/Sub是發送忘記的方式,并且不存盤任何資料;而streams模式下,所有訊息被無限期追加在streams中,除非用于顯示執行洗掉(XDEL),
- streams的Consumer Groups也是Pub/Sub無法實作的控制方式,
redis的底層資料結構
????每次在Redis資料庫中創建一個鍵值對時,至少會創建兩個物件,一個是鍵物件,一個是值物件,而Redis中的每個物件都是由 redisObject 結構來表示:
typedef struct redisObject{
//型別
unsigned type:4;
//編碼
unsigned encoding:4;
//指向底層資料結構的指標
void *ptr;
//參考計數
int refcount;
//記錄最后一次被程式訪問的時間
unsigned lru:22;
}
????其中type屬性記錄了物件的基礎資料結構型別,也就是前面提到了五種:

????物件的 prt 指標指向物件底層的資料結構,而資料結構由 encoding 屬性來決定:

????而每種型別的物件都至少使用了兩種不同的編碼:

字串物件-string的構成
????字串是Redis最基本的資料型別,不僅所有key都是字串型別,其它幾種資料型別構成的元素也是字串,注意字串的長度不能超過512M,
????字串物件的編碼可以是int、embstr、raw,
1、int 編碼:保存的是可以用 long 型別表示的整數值,
2、embstr 編碼:保存長度小于44位元組的字串(redis3.2版本之前是39位元組,之后是44位元組),
3、raw 編碼:保存長度大于44位元組的字串(redis3.2版本之前是39位元組,之后是44位元組),
????其中raw圖示為:

????embstr圖示為:

????embstr與raw都使用redisObject和sds保存資料,區別在于,embstr的使用只分配一次記憶體空間(因此redisObject和sds是連續的),而raw需要分配兩次記憶體空間(分別為redisObject和sds分配空間),因此與raw相比,embstr的好處在于創建時少分配一次空間,洗掉時少釋放一次空間,以及物件的所有資料連在一起,尋找方便,而embstr的壞處也很明顯,如果字串的長度增加需要重新分配記憶體時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實作為只讀,
????當 int 編碼保存的值不再是整數,或大小超過了long的范圍時,自動轉化為raw,
????對于 embstr 編碼,由于 Redis 沒有對其撰寫任何的修改程式(embstr 是只讀的),在對embstr物件進行修改時,都會先轉化為raw再進行修改,因此,只要是修改embstr物件,修改后的物件一定是raw的,無論是否達到了44個位元組,
串列物件-list的構成
????list 串列,它是簡單的字串串列,按照插入順序排序,你可以添加一個元素到串列的頭部(左邊)或者尾部(右邊),它的底層實際上是個鏈表結構,
????串列物件的編碼可以是 ziplist(壓縮串列) 和 linkedlist(雙端鏈表),
????其中ziplist圖示為:

????linkedlist圖示為:

????當同時滿足下面兩個條件時,使用ziplist(壓縮串列)編碼:
1、串列保存元素個數小于512個
2、每個元素長度小于64位元組
????不能滿足這兩個條件的時候使用 linkedlist 編碼,
????上面兩個條件可以在redis.conf 組態檔中的 list-max-ziplist-value選項和 list-max-ziplist-entries 選項進行配置,
哈希物件-hash的構成
????哈希物件的鍵是一個字串型別,值是一個鍵值對集合,
????哈希物件的編碼可以是 ziplist 或者 hashtable,
????其中ziplist圖示為:

????hashtable圖示為:

????hashtable 編碼的哈希表物件底層使用字典資料結構,哈希物件中的每個鍵值對都使用一個字典鍵值對,
????在前面介紹壓縮串列時,我們介紹過壓縮串列是Redis為了節省記憶體而開發的,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,相對于字典資料結構,壓縮串列用于元素個數少、元素長度小的場景,其優勢在于集中存盤,節省空間,
????和上面串列物件使用 ziplist 編碼一樣,當同時滿足下面兩個條件時,使用ziplist(壓縮串列)編碼:
1、串列保存元素個數小于512個
2、每個元素長度小于64位元組
????不能滿足這兩個條件的時候使用 hashtable 編碼,第一個條件可以通過組態檔中的 set-max-intset-entries 進行修改,
集合物件-set的構成
????集合物件 set 是 string 型別(整數也會轉換成string型別進行存盤)的無序集合,注意集合和串列的區別:集合中的元素是無序的,因此不能通過索引來操作元素;集合中的元素不能有重復,
????集合物件的編碼可以是 intset 或者 hashtable,
????其中intset圖示為:

????hashtable圖示為:

????當集合同時滿足以下兩個條件時,使用 intset 編碼:
1、集合物件中所有元素都是整數
2、集合物件所有元素數量不超過512
????不能滿足這兩個條件的就使用 hashtable 編碼,第二個條件可以通過組態檔的 set-max-intset-entries 進行配置,
有序集合物件-zset的構成
????和上面的集合物件相比,有序集合物件是有序的,與串列使用索引下標作為排序依據不同,有序集合為每個元素設定一個分數(score)作為排序依據,
????有序集合的編碼可以是 ziplist 或者 skiplist,
????ziplist 編碼的有序集合物件使用壓縮串列作為底層實作,每個集合元素使用兩個緊挨在一起的壓縮串列節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值,并且壓縮串列內的集合元素按分值從小到大的順序進行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置,

????skiplist 編碼的有序集合物件使用 zet 結構作為底層實作,一個 zset 結構同時包含一個字典和一個跳躍表:
typedef struct zset{
//跳躍表
zskiplist *zsl;
//字典
dict *dice;
} zset;
????字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節點的 object 屬性保存元素的成員,跳躍表節點的 score 屬性保存元素的分值,
????這兩種資料結構會通過指標來共享相同元素的成員和分值,所以不會產生重復成員和分值,造成記憶體的浪費,
????說明:其實有序集合單獨使用字典或跳躍表其中一種資料結構都可以實作,但是這里使用兩種資料結構組合起來,原因是假如我們單獨使用 字典,雖然能以 O(1) 的時間復雜度查找成員的分值,但是因為字典是以無序的方式來保存集合元素,所以每次進行范圍操作的時候都要進行排序;假如我們單獨使用跳躍表來實作,雖然能執行范圍操作,但是查找操作有 O(1)的復雜度變為了O(logN),因此Redis使用了兩種資料結構來共同實作有序集合,
????當有序集合物件同時滿足以下兩個條件時,物件使用 ziplist 編碼:
1、保存的元素數量小于128;
2、保存的所有元素長度都小于64位元組,
????不能滿足上面兩個條件的使用 skiplist 編碼,以上兩個條件也可以通過Redis組態檔zset-max-ziplist-entries 選項和 zset-max-ziplist-value 進行修改,
redis的過期策略
????redis有三種過期策略,
- 定時過期:每個設定過期時間的key都需要創建一個定時器,到過期時間就會立即對key進行清除,該策略可以立即清除過期的資料,對記憶體很友好;但是會占用大量的CPU資源去處理過期的資料,從而影響快取的回應時間和吞吐量,
- 定期過期:只有當訪問一個key時,才會判斷該key是否已過期,過期則清除,該策略可以最大化地節省CPU資源,卻對記憶體非常不友好,極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,占用大量記憶體,
- 惰性過期:每隔一定的時間,會掃描一定數量的資料庫的expires字典中一定數量的key,并清除其中已過期的key,該策略是前兩者的一個折中方案,通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果,expires字典會保存所有設定了過期時間的key的過期時間資料,其中,key是指向鍵空間中的某個鍵的指標,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間,
?
????redis同時使用的是定期過期跟惰性過期,
redis的記憶體淘汰策略
????記憶體并不是無限大的,當存盤的容量達到一定限度時,redis就會采用記憶體淘汰策略來保護自己,主要有以下幾種:
- noeviction:默認策略,當記憶體不足以容納新寫入資料時,新寫入操作會報錯,
- volatile-lru:當記憶體不足以容納新寫入資料時,從設定了過期時間的key中使用LRU(最近最少使用)演算法進行淘汰,
- allkeys-lru:當記憶體不足以容納新寫入資料時,從所有key中使用LRU演算法進行淘汰,
- volatile-lfu:4.0版本新增,當記憶體不足以容納新寫入資料時,在過期的key中,使用LFU演算法進行洗掉key,
- allkeys-lfu:4.0版本新增,當記憶體不足以容納新寫入資料時,從所有key中使用LFU演算法進行淘汰,
- volatile-random:當記憶體不足以容納新寫入資料時,從設定了過期時間的key中,隨機淘汰資料,
- allkeys-random:當記憶體不足以容納新寫入資料時,從所有key中隨機淘汰資料,
- volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的key中,根據過期時間進行淘汰,越早過期的優先被淘汰,
redis的持久化機制
????redis是基于記憶體的,如果掛了資料便全部丟失,所以便需要做持久化,把資料存到磁盤中,redis有rdb跟aof兩種機制,
rdb
????redis database,是把當前記憶體中的資料集快照寫入磁盤,也就是 Snapshot 快照(資料庫中所有鍵值對資料),恢復時是將快照檔案直接讀到記憶體里,
????rdb有2種觸發機制,分別是自動觸發和手動觸發,
- 自動觸發,在 redis.conf 組態檔中的 SNAPSHOTTING 下,可配置相關策略,
- **save:**這里是用來配置觸發 Redis的 RDB 持久化條件,也就是什么時候將記憶體中的資料保存到硬碟,比如“save m n”,表示m秒內資料集存在n次修改時,自動觸發bgsave(這個命令下面會介紹,手動觸發RDB持久化的命令),當然如果你只是用Redis的快取功能,不需要持久化,那么你可以注釋掉所有的 save 行來停用保存功能,可以直接一個空字串來實作停用:save “”,
- **stop-writes-on-bgsave-error :**默認值為yes,當啟用了RDB且最后一次后臺保存資料失敗,Redis是否停止接收資料,這會讓用戶意識到資料沒有正確持久化到磁盤上,否則沒有人會注意到災難(disaster)發生了,如果Redis重啟了,那么又可以重新開始接收資料了,
- **rdbcompression ;**默認值是yes,對于存盤到磁盤中的快照,可以設定是否進行壓縮存盤,如果是的話,redis會采用LZF演算法進行壓縮,如果你不想消耗CPU來進行壓縮的話,可以設定為關閉此功能,但是存盤在磁盤上的快斬訓比較大,
- **rdbchecksum :**默認值是yes,在存盤快照后,我們還可以讓redis使用CRC64演算法來進行資料校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關閉此功能,
- **dir:**設定快照檔案的存放路徑,這個配置項一定是個目錄,而不能是檔案名,默認是和當前組態檔保存在同一目錄,
????也就是說通過在組態檔中配置的 save 方式,當實際操作滿足該配置形式時就會進行 RDB 持久化,將當前的記憶體快照保存在 dir 配置的目錄中,檔案名由配置的 dbfilename 決定,
?
- 手動觸發
- **save **該命令會阻塞當前Redis服務器,執行save命令期間,Redis不能處理其他命令,直到RDB程序完成為止,
- **bgsave **執行該命令時,Redis會在后臺異步進行快照操作,快照同時還可以回應客戶端請求,具體操作是Redis行程執行fork操作創建子行程,RDB持久化程序由子行程負責,完成后自動結束,阻塞只發生在fork階段,一般時間很短,
????基本上 Redis 內部所有的RDB操作都是采用 bgsave 命令,
?
rdb的優缺點
1、rdb是一個非常緊湊的檔案,保存了redis在某個時間點的資料集,非常適合備份,體積比aof小,因為是資料的快照,基本上就是資料的復制,不用重新讀取再寫入記憶體,
2、rdb的作業原理是父行程在保存檔案就是 fork 出一個子行程,然后這個子行程就會處理接下來的所有保存作業,父行程無須執行任何磁盤 I/O 操作,這樣可以最大化提升redis的性能,但是當資料比較大時這個fork行程可能會非常耗時,造成redis阻塞,
3、rdb在恢復大資料集時比aof慢,
4、因為是根據時間來備份的,所以會有丟失資料的風險,
?
aof
????append only file,將寫操作追加到檔案中,AOF 日志是寫后日志,“寫后”的意思是 redis 是先執行命令,把資料寫入記憶體后,然后才記日志;里面記錄的是指令執行的步驟,非常詳細,描繪出了資料的變化程序,
????在 redis.conf 組態檔中的 SNAPSHOTTING 下,可配置相關策略,
- appendonly no 是否開啟AOF機制,yes 代表開啟
- appendfilename “appendonly.aof” aof檔案名
- appendfsync xxx aof持久化策略的配置, xxx為alaways表示表示不執行同步,由作業系統自己選擇時間保證資料同步到磁盤,速度最快;xxx為everysec表示每一秒執行一次同步,可能會導致丟失這1s資料;no表示每次寫入記憶體后都執行同步,以保證資料同步到磁盤,
- no-appendfsync-on-rewrite no 是否開啟重寫(當aof檔案的大小超過所設定的閾值時,redis就會對aof檔案的內容壓縮,)
- **auto-aof-rewrite-percentage ** 100 當目前aof檔案大小超過上一次重寫的aof檔案大小的百分之多少進行重寫
- auto-aof-rewrite-min-size 64mb 設定允許重寫的最小aof檔案大小
- aof-use-rdb-preamble no 混合使用 aof和rdb的開關
aof的優缺點
1、可最大限度地保證資料的完整性,
2、重寫機制讓日志檔案更小,
3、因為記錄的是執行程序,所以檔案會比rdb大許多,
?
redis的高可用
????高可用的方案一般都是集群,redis有三種集群方式:主從模式,哨兵模式,集群模式,
?
主從模式 master-slave
????主從模式中,Redis部署了多臺機器,有主節點,負責讀寫操作,有從節點,只負責讀操作,從節點的資料來自主節點,實作原理就是主從復制機制,基本的步驟如下(藍色代表slave發給master的命令,紅色相反):
- slave發送psync2命令到master,(命令:psync2 )
- master接收到SYNC命令后,執行bgsave命令,生成RDB全量檔案,并生成緩沖區(緩沖復制先進先出佇列,默認1M)記錄從現在開始執行的所有寫命令,(命令: fullresync )
- master執行完bgsave后,向所有slave發送RDB快照檔案,
- slave收到RDB快照檔案后,清空自己的資料,然后載入、決議收到的快照,
- master快照發送完畢后,也就是全量復制結束后,會開始增量復制,向slave發送緩沖區中的寫命令,
- slave接受命令請求,并執行來自master緩沖區的寫命令,
- 后續slave只需要攜帶id跟offset(上次復制的偏移量)發給master,便可進行增量復制,如果存在就會發送continue給slave,如果不存在就會執行全量復制,(命令 continue )
?
哨兵模式 sentinel
????主從模式中,一旦主節點由于故障不能提供服務,需要人工將從節點晉升為主節點,同時還要通知應用方更新主節點地址,顯然,多數業務場景都不能接受這種故障處理方式,redis從2.8開始正式提供了Redis Sentinel(哨兵)架構來解決這個問題,
哨兵模式,由一個或多個Sentinel實體組成的Sentinel系統,它可以監視所有的redis主節點和從節點,并在被監視的主節點進入下線狀態時,自動將下線主服務器屬下的某個從節點升級為新的主節點,但是呢,一個哨兵行程對redis節點進行監控,就可能會出現問題(單點問題),因此,可以使用多個哨兵來進行監控redis節點,并且各個哨兵之間還會進行監控,下圖是整體架構:

????簡單來說,哨兵模式就三個作用:
- 發送命令,等待Redis服務器(包括主服務器和從服務器)回傳監控其運行狀態;
- 哨兵監測到主節點宕機,會自動將從節點切換成主節點,然后通過發布訂閱模式通知其他的從節點,修改組態檔,讓它們切換主機;
- 哨兵之間還會相互監控,從而達到高可用,
????哨兵的作業模式如下:
- 每個哨兵以每秒鐘一次的頻率向它所知的master,slave以及其他哨兵實體發送一個 PING命令,
- 如果一個實體(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實體會被哨兵標記為主觀下線,
- 如果一個master被標記為主觀下線,則正在監視這個master的所有哨兵要以每秒一次的頻率確認master的確進入了主觀下線狀態,
- 當有足夠數量的哨兵(大于等于組態檔指定的值)在指定的時間范圍內確認master的確進入了主觀下線狀態, 則master會被標記為客觀下線,
- 在一般情況下, 每個哨兵會以每10秒一次的頻率向它已知的所有master,slave發送 INFO 命令,當master被哨兵標記為客觀下線時,哨兵向下線的 master的所有 slave發送 INFO 命令的頻率會從 10 秒一次改為每秒一次
- 若沒有足夠數量的哨兵同意master已經下線, master的客觀下線狀態就會被移除;若master重新向哨兵的 PING 命令回傳有效回復, master的主觀下線狀態就會被移除,
????當master被判斷客觀下線以后,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,并由該領導者節點對其進行故障轉移操作,監視該主節點的所有哨兵都有可能被選為領導者,選舉使用的演算法是 Raft 演算法,Raft 演算法的基本思路是先到先得:即在一輪選舉中,哨兵 A 向 B 發送成為領導者的申請,如果 B 沒有同意過其他哨兵,則會同意 A 成為領導者,
選????舉出的領導者哨兵,開始進行故障轉移操作,該操作大體可以分為 3 個步驟:
- 在從節點中選擇新的主節點:選擇的原則是,首先過濾掉不健康的從節點,然后選擇優先級最高的從節點(由 slave-priority 指定), 如果優先級無法區分,則選擇復制偏移量最大的從節點;如果仍無法區分,則選擇 runid 最小的從節點,
- 更新主從狀態:通過 slaveof no one 命令,讓選出來的從節點成為主節點;并通過 slaveof 命令讓其他節點成為其從節點,
- 將已經下線的主節點設定為新的主節點的從節點,當它重新上線后,它會成為新的主節點的從節點,
集群模式 cluster
????哨兵模式基于主從模式,實作讀寫分離,它還可以自動切換,系統可用性更高,但是它每個節點存盤的資料是一樣的,浪費記憶體,并且不好在線擴容, 因此,cluster集群應運而生,它在redis3加入的,實作了redis的分布式存盤,對資料進行分片,也就是說每臺redis節點上存盤不同的內容,來解決在線擴容的問題,并且它也提供復制和故障轉移的功能,
????cluster集群通過Gossip協議進行通信,節點之前不斷交換資訊,交換的資訊內容包括節點出現故障、新節點加入、主從節點變更資訊、slot資訊等等,常用的Gossip訊息分為4種,分別是:ping、pong、meet、fail,
- meet訊息:通知新節點加入,訊息發送者通知接收者加入到當前集群,meet訊息通信正常完成后,接收節點會加入到集群中并進行周期性的ping、pong訊息交換,
- ping訊息:集群內交換最頻繁的訊息,集群內每個節點每秒向多個其他節點發送ping訊息,用于檢測節點是否在線和交換彼此狀態資訊,
- pong訊息:當接收到ping、meet訊息時,作為回應訊息回復給發送方確認訊息正常通信,pong訊息內部封裝了自身狀態資料,節點也可以向集群內廣播自身的pong訊息來通知整個集群對自身狀態進行更新,
- fail訊息:當節點判定集群內另一個節點下線時,會向集群內廣播一個fail訊息,其他節點接收到fail訊息之后把對應節點更新為下線狀態,
資料存盤
????cluster如何做到每個節點存盤不同資料的呢,它采用的是hash slot插槽演算法,插槽演算法把整個資料庫被分為16384個slot(槽),每個進入redis的鍵值對,根據key進行散列,分配到這16384插槽中的一個,使用的哈希映射也比較簡單,用CRC16演算法計算出一個16 位的值,再對16384取模,資料庫中的每個鍵都屬于這16384個槽的其中一個,集群中的每個節點都可以處理這16384個槽,
????集群中的每個節點負責一部分的hash槽,比如當前集群有A、B、C個節點,每個節點上的哈希槽數 =16384/3,那么就有:
- 節點A負責0~5460號哈希槽
- 節點B負責5461~10922號哈希槽
- 節點C負責10923~16383號哈希槽
????如果新增節點,那就會把其他節點的頭部部分哈希槽一起平分給新節點,如果洗掉節點的話就平分到其他節點,
設計成16384個槽點是因為考慮到節點數不太可能超過1000,并且槽點越小,其壓縮率就越高,
復制
????cluster集群引入了主從復制,一個主節點對應一個或者多個從節點,當其它主節點 ping 一個主節點 A 時,如果半數以上的主節點與 A 通信超時,那么認為主節點 A 宕機了,如果主節點宕機時,就會啟用從節點,
????當集群內節點出現故障時,通過故障轉移,以保證集群正常對外提供服務,
????redis集群通過ping/pong訊息,實作故障發現,這個環境包括主觀下線和客觀下線,
- 主觀下線: 某個節點認為另一個節點不可用,即下線狀態,這個狀態并不是最終的故障判定,只能代表一個節點的意見,可能存在誤判情況,

- 客觀下線: 指標記一個節點真正的下線,集群內多個節點都認為該節點不可用,從而達成共識的結果,如果是持有槽的主節點故障,需要為該節點進行故障轉移,
- 假如節點A標記節點B為主觀下線,一段時間后,節點A通過訊息把節點B的狀態發到其它節點,當節點C接受到訊息并決議出訊息體時,如果發現節點B的pfail狀態時,會觸發客觀下線流程;
- 當下線為主節點時,此時Redis Cluster集群為統計持有槽的主節點投票,看投票數是否達到一半,當下線報告統計數大于一半時,被標記為客觀下線狀態,
- ?故障恢復:故障發現后,如果下線節點的是主節點,則需要在它的從節點中選一個替換它,以保證集群的高可用,流程如下:
- 資格檢查:檢查從節點是否具備替換故障主節點的條件,
- 準備選舉時間:資格檢查通過后,更新觸發故障選舉時間,
- 發起選舉:到了故障選舉時間,進行選舉,
- 選舉投票:只有持有槽的主節點才有票,從節點收集到足夠的選票(大于一半),觸發替換主節點操作
- ?
mysql與redis保證雙寫一致性
延時雙刪
- 先洗掉快取
- 再更新資料庫
- 休眠一會(比如1秒),再次洗掉快取,
?
????這種方案還算可以,只有休眠那一會(比如就那1秒),可能有臟資料,一般業務也會接受的,但是如果第二次洗掉快取失敗呢?快取和資料庫的資料還是可能不一致,
?
洗掉快取重試機制
- 寫請求更新資料庫
- 緩存因為某些原因,洗掉失敗
- 把洗掉失敗的key放到訊息佇列
- 消費訊息佇列的訊息,獲取要洗掉的key
- 重試洗掉快取操作
?
讀取binlog異步洗掉快取
- 可以使用阿里的canal將binlog日志采集發送到MQ佇列里面
- 然后通過ACK機制確認處理這條更新訊息,洗掉快取,保證資料快取一致性
?
redis的分布式鎖
????這個可用redisson實作,具體可查看redisson分布式鎖詳解,
redis對于快取的三大問題的處理
快取雪崩
????快取雪崩是指在某一個時間段,快取集中過期失效,此刻無數的請求直接繞開快取,直接請求資料庫,
????造成快取雪崩的原因,有以下兩種:redis宕機、大部分資料失效,
????對于快取雪崩的解決方案有以下兩種:
- 搭建高可用的集群,防止單機的redis宕機,
- 設定不同的過期時間,防止同一時間內大量的key失效,
?
快取穿透
????快取穿透是指查詢一條資料庫和快取都沒有的一條資料,就會一直查詢資料庫,對資料庫的訪問壓力就會增大;
????解決方案:
- 快取空物件
- 布隆過濾器
?
快取擊穿
????是指一個key非常熱點,在不停的扛著大并發,大并發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大并發就穿破快取,直接請求資料庫,瞬間對資料庫的訪問壓力增大,
解決方案:
- 將資料熱點資料設定成永久的,不設定失效時間,
- 集群擴容,增加分片副本,均衡讀流量,
- 使用分布式鎖,a、當快取不命中時,在查詢資料庫前使用redis分布式鎖,使用查詢的key值作為鎖條件;b、獲取鎖的執行緒在查詢資料庫前,再查詢一次快取,這樣做是因為高并發請求獲取鎖的時候造成排隊,但第一次進來的執行緒在查詢完資料庫后會寫入快取,之后再獲得鎖的執行緒直接查詢快取就可以獲得資料;c、讀取完資料后釋放分布式鎖,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/395337.html
標籤:其他
