目錄:
- Redis五大對象
- Redis持久化
- Redis主從復制
- 慢查詢日志
- Redis Shell
- Pipeline
- 事務
- Bitmaps
- HyperLogLog
- 發布訂閱
- GEO
- 哨兵
- 集群
- 緩存設計
1、Redis五大對象:
在Redis中有五大對象,分別是String、List、Hash、Set、Sorted Set。
這五大對象都有自己獨特的編碼方式,每個編碼的實現都不一樣,有自己獨特的使用場景。
通過命令:object encoding keyName查看鍵的編碼類型。
a、String(字符串類型):
INT:可以用long型保存的整數。
EMBSTR:除了字符串、long,還可以保存double這種浮點數,但長度需要<=44。
RAW:保存長度大於44的字符串、long、double。
b、List(列表):
quicklist包含多個ziplist,是對ziplist的優化。
list-max-ziplist-size,若值是整數則表示quicklist上ziplist的節點數量,負數則表示ziplist的大小(公式:KB = -1 * 2 ^ n)。
- -3:quicklist節點的ziplist大小不能超哥16KB。
- -2:quicklist節點的ziplist大小不能超哥8KB(默認值)。
- -1:quicklist節點的ziplist大小不能超哥4KB。
因為list被設計成兩端獲取效率高,中間數據獲取效率低,若滿足這個條件可以通過list-compress-depth配置來壓縮中間數據,進一步節省內存。
- 0:特殊值,不是不壓縮(默認值)。
- 1:quicklist兩端各有1個節點不壓縮,中間的節點壓縮。
- 2:quicklist兩端各有2個節點不壓縮,中間的節點壓縮。
- 以此類推。
c、Hash(字典):
當哈希對象同時滿足一下兩個條件時,使用ziplist編碼:
- 所有鍵值元素長度小於64字節,可通過改變hash-max-ziplist-value修改默認值。
- 鍵值對數量小於512個,可通過改變hash-max-ziplst-entries修改默認值
d、Set(集合):
當集合對象同時滿足以下兩個條件時,集合對象使用intset編碼
- 集合對象保存的所有元素都是整數值。
- 鍵值對數量不超過512個,可通過修改配置文件中的set-max-inset-entries屬性改變默認值。
e、ZSet(有序集合):
當有序集合對象同時滿足以下兩個條件時,有序集合對象使用ziplist編碼:
有序集合對象保存的所有成員長度小於64字節,可通過修改配置文件中的zset-max-ziplist-value屬性改變默認值。
有序集合對象保存的所有元素數量小於128個,可通過修改配置文件中的zset-max-ziplist-entries屬性改變默認值。
2、Redis持久化
a、RDB持久化:
RDB持久化方式就是把當前進程中的數據以快照形式保存到磁盤的過程,其分為手動和自動觸發。
手動:
- save命令:save命令會阻塞主進程,若數據量較大則會影響正常的服務請求。
- bgsave命令:bgsave會fork一個子進程,會阻塞fork子進程的時間(阻塞時間極短)。
自動觸發條件:
- 根據單位時間(秒)內數據修改次數來觸發,默認配置如下:
- save 900 1
- save 300 10
- save 60 10000
- 從節點進行全量復制時。
- 執行debug reload命令時。
- 執行shutdown命令時若沒有開啟aof時執行bgsave。
RDB持久化步驟:
- 執行bgsave命令,此時父進程會判斷是否有正在執行的子進程(如RDB/AOP),如有直接返回。
- 父進程執行fork操作創建子進程,此時父進程會阻塞(可通過info stats命令查看latest_fork_usec選項查看最近一個fork操作耗時,單位微妙)。
- 父進程fork完后,bgsave會返回"Background saving started",並不再阻塞父進程。
- 子進程創建RDB文件。
RDB優缺點:
優點:
- RDB持久化的文件非常緊湊(有壓縮),它會保存某個時間點的數據,可以方便的把數據傳送遠端數據中心,適用於災難恢復。
- bgsave存儲時用子進程執行操作,最大化的優化redis的性能。
- 與AOF相比,在恢復大量數據時會快些。
缺點:
- 因為不是實時存儲數據,所以宕機時會丟失一部分數據。
- 數據量大時,fork的過程會非常耗時,如果fork阻塞,那么主線程也會受到影響。
- RDB方式的持久化的文件是特定的格式,可讀性、兼容性差(不同版本的RDB格式都不一樣)。
b、AOF持久化:
RDB與AOF兩種持久化方式只能二選一,若需要使用AOF需要通過配置文件開啟,因為默認是不開啟的。
配置:
- 是否開啟APO(默認false):appendonly yes。
- AOF文件名(默認appendonly.aof):appendfilename xxx.aof。
- 保存路徑:通過dir配置指定。
AOF持久化是通過保存Redis服務器所執行的命令來記錄數據庫的狀態,每執行一個命令就會往AOF文件后追加一個命令。
AOF采用RESP協議寫入數據:
單行字符,+開頭。
多行字符,$開頭,后跟字符串長度。
整數值,:開頭,后跟整數字符串形式。
錯誤消息,-開頭。
數組,*開頭,后跟數組長度。
AOF持久化流程:
- 首先每次追加直接操作磁盤的話,性能上肯定不高,所以Redis會先將命令追加到aof_buf中。
- 然后緩沖區再根據相應的策略將緩沖區的內容同步到磁盤。
- 並且Redis還會定期的對AOF文件進行重寫,以達到壓縮的目的。
- 服務器重啟時可以通過AOF文件恢復數據。
為什么AOF文件能夠重寫:
- 過期的數據可以不寫入。
- 無效的數據可以不寫入。
- 多條命令可以合並成一個寫入。
AOF持久化的優缺點:
優點:
- 支持秒級持久化。
- 可用於不同版本的Redis服務器,兼容性強。
- 可讀性高。
缺點:
- 文件大,恢復數據慢。
3、Redis主從復制
a、主從復制是什么:
在絕大多數中間件中都會具有主動復制這一功能,而多數也就是為了服務的高可用罷了;Redis也不例外,它提供了復制功能,實現了相同數據的多個副本。
b、搭建復制三種方式:
- 從節點配置文件中加入slaveof <masterIp> <masterPort>
- 啟動命令后加入--slaveof <masterIp> <masterPort>
- 通過命令laveof <masterIp> <masterPort>
注意:
- slaveof是異步命令,節點保存主節點信息后便返回,后續復制就從在節點異步進行。
- 由於復制模式是單向的,從節點接收寫命令的話無法讓主節點感知,這樣會造成數據不一致的情況,所以從節點需要配置成只讀模式(slave-read-only=yes)。
c、主動復制原理:
流程:
- 保存主節點信息:從節點保存主節點信息(ip、port)。
- job網絡連接主節點:從節點內部通過job來維護復制相關邏輯,當job發現新的主節點后會嘗試與該節點建立網絡連接;若無法連接job會無限重試直到連接成功,或執行slaveof no one命令取消復制。
- 發送ping給主節點:從節點會發送ping命令,檢測主動之間套接字是否可用,和主節點當前是否可接受處理命令。
- 權限驗證:如果主節點設置了requirepass則需要密碼驗證,且從節點需要有相同的配置才能通過驗證;如果驗證失敗復制終止,從節點斷開連接。
- 連接成功:發送從節點的ip、port給主節點。
- 數據同步:依據情況主從之間進行全量或部分復制。
- 命令傳播:當上述步驟完成后,也算是完成了復制的建立流程,接下來主節點會持續地把命令發送給從節點來保證主從數據的一致性。
全量復制於部分復制:
)全量:psync ? -1
一般用於初次復制的場景(雖然早期的Redis也僅支持全量復制),它會一次性的打包所有的數據給從節點,當數據量較大時會主從節點和網絡造成很大的開銷。
)部分:psync <runid> <offset>
用於復制過程中宕機或網絡閃斷的場景,主節點會補發數據給從節點。因補發數據遠小於全量數據,所以可以有效的避免全量復制的開銷。
部分復制的三要素:
- 復制偏移量:參與復制的主從節點都會維護自身的偏移量,主節點在處理完寫命令后會累加這個偏移量,從節點會每秒的上報偏移量,因此主節點就擁有了主從的偏移量。
- 溫馨提示:可以通過主節點master_repl_offset信息判斷主動節點相差的數據量,從而得知復制的健康度。
- 復制積壓緩沖區:主節點執行寫命令時不但會將數據同步給從節點,還會寫入到復制積壓緩沖區。部分復制時會根據緩沖區的偏移量來判斷從哪開始復制。
- 運行ID:若主從runid相同,則表示此主從之前同步過,進行部分復制即可;若不同則表示從節點之前同步的主節點並非此主節點,所以要進行全量復制。
psync命令流程圖:
4、慢查詢日志
慢查詢閥值設置(單位:微秒,默認10000,也就是0.1秒):slowlog-log-slower-than;0為記錄所有,小於0都不記錄。
Redis用一個列表存儲慢查詢日志,可用slowlog-max-len指定日志存儲的最大長度。
相關命令:
- 獲取慢查詢日志列表,可執行返回條數:slowlog get <n>。
- 獲取慢查詢日志列表當前長度:slowlog len。
- 清空慢查詢日志列表:slowlog reset。
5、Redis Shell
redis-cli:Redis客戶端shell。
redis-server:Redis服務端shell。
redis-benchmark:測試並發。
6、Pipeline
為了改善多個命令的RTT,Pipeline會一次性將命令打包給Redis服務器,Redis服務器再將這些命令的結果依次返回。
注意:雖然Pipeline好用,但一次傳輸的量也不能無節制,數據量過大時一方面會造成客戶端阻塞,一方面會造成一定的網絡阻塞;可以將一次大的包拆分成多個小包傳輸。
7、事務
a、事務的三個階段:
- 事務的開始:multi命令標識事務的開始。
- 事務的入隊:當客戶端為事務模式時,除了執行exec、discard、watch、multi命令之外的命令都會返回給客戶端queued狀態。
- 事務的執行:當對客戶端發送exec命令時,服務端會依次執行事務隊列的所有命令,並返回最終結果給客戶端。
b、事務中的錯誤處理:
- 入隊錯誤:入隊錯誤是指在事務隊列中出現了錯誤的命令,此時Redis將拒絕這次事務,但這在使用客戶端操作時是不存在的。
- 執行錯誤:因為有些命令只有在執行的時候才能發現(如:set key value、hget key等等),這種情況Redis不會拒絕這次事務,而是忽略錯誤命令。
c、watch命令:
watch命令是一個樂觀鎖,它可以在exec命令執行前監視一個或多個key,若這個key在執行事務期間被修改的話,那么在執行exec命令后便會拒絕這次事務。
8、Bitmaps
Bitmaps並不是一種數據結構,它的實質其實就是字符串,但它可以對字符串做位操作(轉換成二進制做位操作)。
命令:
- 獲取key指定偏移量的位數:getbit key <offset>。
- 獲取指定偏移量在哪個字節中:byte,byte = offset / 8。
- 獲取指定偏移量在byte字節的第幾位:bit = (offset % 8) + 1。
- 根據byte、bit返回位數的值(0或1)。
- 設置指定key的偏移量:setbit key <offset> <value>。
- 計算所需字節數,len = (offset / 8) + 1。
- 檢查key保存的字節數是否小於len,小於則擴展,其余部分補0。
- 同getbit key <offset>的步驟a、b。
- 將指定value設置到key中。
- 返回給客戶端原值。
- 獲取key中二進制數值為1的數量:bigcount key。
- Bitmaps邏輯運算:bitop op destkey key [key ...]。
- 交集 op = and:0組合0 >>> 0、0組合1 >>> 0、1組合1 >>> 1。
- 並集 op = or:0組合0 >>> 0、0組合1 >>> 1、1組合1 >>> 1
- 非 op = not。
- 異或 op = xor。
- 返回字符串里第一個被設置為1或0的bit位:bitpos key targetBit [start] [end]。
應用場景:適用於快速、簡單、實時統計,如獨立訪客、日活用戶等等。
9、HyperLogLog
HyperLogLog和Bitmaps非常相似,其使用一種概率算法,這種算法非常節約磁盤空間,但缺點是並不是非常精確(誤差在0.81%左右),且不能獲取單條數據,只能拿到一個統計的總數;所以僅適用於類似統計在線人數這種需求。
命令:
- 添加:pfadd key element [element ...](添加成功返回1,失敗返回0)。
- 統計數量:pfcount key [key ...](返回數量)。
- 合並:pfmerge destkey sourcekey [sourcekey ...]。
10、發布訂閱
。。。。。。
11、GEO
GEO是redis中對地理位置功能的支持;其使用geohash存儲地址位置的坐標,使用有序集合(zset)存儲地址位置的集合。
12、哨兵
顧名思義哨兵就是監視某事或某物,並在發送異常情況下及時做出反饋的兵種。在Redis中哨兵就是在主從復制架構下,主節點宕機了,系統自動故障轉移的角色。
故障轉移的過程:
- 選舉新節點:當主節點發生故障且無法正常重啟時,就需要在從節點中選出一個新的主節點,並執行slaveof no one。
- 更新應用方:待新的主節點選舉出來后,便通知應用方更新應用方的主節點信息,重啟應用方。
- 重新復制:客戶端命令其它的從節點和原來的主節點去復制這個新的主節點,slaveof host port。
故障轉移原理:
- 定期監控:每個sentinel都會監控redis節點,若發現主節點出現了故障則標記。
- 節點決策:當多個sentinel節點都認為主節點故障后,則會選舉一個領導來執行這次故障轉移。
- 執行轉移:被選舉出的那個sentinel執行故障轉移。
哨兵實現原理:
- 三個job:獲取節點信息、獲取其它sentinel節點對主節點的判斷、心跳檢測。
- 獲取節點信息:每隔10秒,sentinel節點會向主節點發送info命令獲取節點信息。
- 獲取其它sentinel節點對主節點的判斷:每隔2秒,每個sentinel節點會向__sentinel__:hello頻道上發送自己對主節點的判斷以及自己的信息,同時每個sentinel會訂閱這個頻道來獲取其它sentinel節點對主節點的判斷。
- 心跳檢測:每隔1秒,每個sentinel節點會向其它sentinel節點以及主從節點發送ping命令,來檢測其它節點是否可達。
- 主觀、客觀下線:sentinel並不會自己覺得主節點有問題就執行轉移,而是滿足主觀和客觀兩個條件才會對主節點轉移。
- 主觀(自己的判斷):sentinel通過心跳檢測,當主節點超過配置時間未響應,則會認為主節點有問題。
- 客觀(公共的判斷):sentinel會像其它sentinel節點詢問對主節點的判斷,如果超過配置數量的節點數認為主節點也有問題,則會做客觀下線處理。
- 選舉一個領導者執行故障轉移:在sentinel對主節點做出主觀、客觀下線后便會從眾多sentinel節點中選出一個領導者執行故障轉移。
13、集群
為什么要集群:主從復制架構下全量復制時若數據量太大,復制數據時可能會導致網絡阻塞,嚴重時可能會導致Redis進程阻塞從而無法對外提供服務。
解決辦法:采用集群架構,將數據分片;將全量數據拆分到不同的分區中,每個節點有一個或多個分區數據。
分區規則:
- Redis cluster中采用虛擬槽分區,所有的鍵根據哈希函數映射到編號為0 - 16383的槽內。
- 計算公式:slot = CRC16(key) & 16383。
- 每個主節點可以擁有一到多個從節點,當主節點宕機或是不可用時從節點將自動晉升為主節點。
集群與單機相比存在的一些功能限制:
- 對於批量操作的命令支持有限:僅支持分配到相同槽的批量key操作,如mget、mset。
- 對於事務支持有限:僅支持分配到相同槽的key進行事務操作。
- 大鍵值對象:因分配的最小粒度為key,所以不支持將大鍵值的對象,如hash、list映射到不同節點上。
- 數據空間:單機下可使用16個DB,而集群架構下只能使用db0。
- 復制結構:只支持一層復制結構,不支持嵌套樹狀復制結構。
14、緩存設計
a、緩存更新策略:
- 內存溢出淘汰策略:當使用內存達到配置內存上線后觸發。
- 拒絕寫入:noevication(默認策略);不刪除任何數據,拒絕所有寫入操作,並返回給客戶端OOM,此時只響應讀操作。
- 過期LRU:volatile-lru;根據LRU算法刪除設置了過期時間的鍵,如果沒有可刪除的,回退到noevication。
- 過期隨機:volatile-random;隨機刪除過期間,直到騰出足夠的空間為止。
- 全部LRU:allkeys-lru;根據LRU算法刪除所有鍵,直到騰出足夠的空間為止。
- 全部隨機:allkeys-random:隨機刪除所有鍵,直到騰出足夠的空間為止。
- ttl:根據對象ttl屬性,刪除最近將要過期的數據。如沒有回退到noevication。
- 過期刪除策略:
- 惰性刪除:Redis內部維護了一個過期字典,當客戶的獲取鍵時首先會判斷是否過期,如果過期了會先執行刪除操作然后再返回空。這種方式雖然節省CPU成本,但可能會造成內存泄漏的問題(如果一直沒有獲取這些key就會導致內存不能及時釋放)。
- 定時刪除:Redis內部維護一個job,每秒運行10次。
- 應用方更新策略:
- 讀數據:先讀緩存,后讀數據庫。
- 寫數據:先寫數據庫,后寫緩存。
- 每次更新數據都要把緩存清掉。
- 緩存設置過期時間,保持與數據庫的最終一致性。
b、緩存穿透:
- 什么是緩存穿透:緩存穿透查詢一個根本不存在的數據,導致每次請求都不會命中緩存,請求都進到DB,導致DB壓力過大而降低DB吞吐量,嚴重時可能會讓DB宕機;一般是自身業務代碼或數據出現問題,或是一個惡意攻擊、爬蟲等造成的。
- 怎么解決:
- 緩存null值,將那些不可能存在的數據也做一層緩存,如緩存值為null;這樣便不會將這些數據命中到DB層了。
- 布隆過濾器。
c、緩存雪崩:
- 什么是緩存雪崩:大量key在同一時間失效,導致大量請求同時打到DB,導致DB宕機。
- 如何解決:
- 緩存層設計成高可用了,如sentinel、cluster架構。
- 采用多級緩存,本地一級緩存,redis作為二級緩存。
- 緩存時間采用隨機值,讓key不在同一時間失效。
d、緩存擊穿:
- 什么是緩存擊穿:高並發場景下,當緩存即將失效時有大量線程來重構緩存,且重構緩存無法在短時間內完成。
- 如何解決:
- 分布式互斥鎖:僅允許一個線程重構緩存,其它線程等待重構完成后再返回數據。
- 永不過期:要么不設置過期時間,要么就另起一個線程,在緩存即將過期時更新數據。