Redis開發與運維

Redis概述

Redis是基於鍵值對的NoSQL數據庫,值能夠是由string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位圖)、HyberLogLog、GEO等多種數據結構和算法組成,能夠知足多種應用場景。html

特性

主要特性有:速度快,數據所有放在內存中、基於C語言實現(距離操做系統更近,執行速度相對更快)、採用單線程架構預防了多線程可能產生的競爭問題;基於鍵值對結構,全稱是remote dictionary server;豐富的功能,鍵過時功能能夠實現緩存功能、發佈訂閱功能能夠用來實現消息系統,支持Lua腳本能夠創造出新的redis命令,提供了簡單的事務功能可在必定程度上保證事務特性,提供了流水線(Pipeline)功能支持批量操做減小了網絡開銷;簡單穩定,代碼行數相對少,使用單線程模型,是的服務端處理模型和客戶端開發變得簡單,且redis不依賴於操做系統中的類庫,本身實現了事務處理的功能;客戶端語言多,幾乎涵蓋了主流的編程語言;持久化,支持兩種持久化方式,保證了數據的可持久化特性;主從複製,複製功能是分佈式redis的基礎;高可用和分佈式,提供了高可用實現redis sentinel保證節點的故障發現和故障自動轉移功能,分佈式實現redis cluster是redis的真正分佈式實現,提供了高可用、讀寫和容量的可擴展性。java

使用場景

緩存,鍵過時機制和內存淘汰策略;排行榜系統,redis提供了列表和有序集合數據結構;計數器應用,自然支持高性能的計數器功能;社交網絡,redis提供的數據結構比較容易實現贊/踩、粉絲、推送等功能;消息隊列系統,提供了發佈訂閱和阻塞隊列的功能,能夠知足通常消息隊列的功能。node

API理解和使用

命令手冊

數據結構和編碼

                                   

每種數據結構都有本身底層的內部編碼實現,並且是多種實現,這樣Redis會在合適的場景選擇合適的內部編碼。這樣作的目的在於,能夠改進內部編碼而對外的數據結構和命令沒有影響,這樣一旦開發開發出優秀的內部編碼,無需改動外部數據結構和命令。第二,多種內部編碼實現能夠在不一樣場景下發揮各自的優點。例如ziplist比較節省內存,可是在列表元素比較多的狀況下,性能會有所降低,這時候Redis會根據配置選項將列表類型的內部實現轉換爲linkedlist。mysql

單線程架構

redis使用了單線程架構I/O多路複用模型來實現高性能的內存數據庫服務。單線程能夠簡化數據結構和算法的實現,避免了線程切換和竟態產生的消耗,對於服務端來講,鎖和線程切換一般是性能殺手。可是單線程對於每一個命令的執行時間是有要求的(若是某個命令執行時間過長,就會形成其餘命令的阻塞,對於redis這種高性能服務來講是致命的,因此redis是面向快速執行場景的數據庫)。redis

爲何單線程還這麼快?

爲何redis使用單線程模型還會達到每秒萬級的處理能力,大體分爲以下三點:算法

(1)redis是基於內存來存儲的,然而內存的讀取/響應市場大約爲100納秒,這一點也就是redis能打到每秒萬級的重要基礎;sql

(2)非阻塞I/O,redis使用epoll做爲I/O多路複用技術的實現,再加上redis的自身的事件處理模型將epoll中的鏈接、讀寫、關閉都轉換爲事件不在網絡I/O上浪費時間;數據庫

(3)單線程避免了線程切換和竟態產生的消耗編程

字符串

鍵都是字符串類型,值實際能夠是字符串(簡單字符串、複雜字符串Json、Xml等)、數字(整數、浮點)、甚至是二進制(圖像、音頻、視頻),可是值最大不能超過512MB。json

set命令選項

  • NX :鍵必須不存在,才能夠設置成功,用於添加。NX 爲 "Not eXists"的縮寫

  • XX :與XX相反,鍵必須存在,才能夠設置成功,用於更新

字符串類型的內部編碼有3種,int是8個字節的長整型;embstr是小於等於39個字節的字符串;raw是大於39個字節的字符串,Redis會根據當前值的類型和長度決定使用內部編碼實現。

哈希

哈希對比關係型數據庫是稀疏的數據結構,每一個鍵能夠有不一樣的field,在Redis中,哈希類型是指鍵值自己又是一個鍵值對結構,形如:value={{field1,value1},{field2,value2},{fieldN,valueN}}

                                

                                                       圖-hash結構示意圖

哈希類型的內部編碼有兩種,ziplist(壓縮列表),當哈希類型元素個數小於hash-max-ziplist-entries配置(默認512個),同時全部值都小於hash-max-ziplist-value配置(默認64個字節)時,Redis會使用ziplist做爲哈希的內部實現,ziplist使用更加緊湊的結構實現多個元素的連續存儲,因此在節省內存方面比hashtable更加優秀。hashtable(哈希表),當哈希類型沒法知足ziplist的條件時,Redis會使用hashtable做爲哈希的內部實現。由於此時ziplist的讀寫效率會降低,而hashtable的讀寫時間複雜度爲O(1)

列表

隊列存儲多個有序的可重複字符串,能夠充當棧和隊列的角色。

列表類型的內部編碼有兩種,ziplist(壓縮列表),當哈希類型元素個數小於hash-max-ziplist-entries配置(默認512個),同時全部值都小於hash-max-ziplist-value配置(默認64個字節)時,Redis會使用ziplist做爲哈希的內部實現,linkedlist(鏈表),當列表類型沒法知足ziplist的條件時,Redis會使用linkedlist做爲列表的內部實現。3.2版本以後提供quickList內部編碼 ,是zipList 和linkedList的混合體,它將linkedList按段切分,每一段使用zipList來緊湊存儲,多個zipList之間使用雙向指針串接起來,結合兩者優點。

集合

集合類型的內部編碼有兩種,intset(整數集合),當集合中的元素都是整數且元素個數小於set-max-intset-entries配置(默認512個)時,Redis會選用intset來做爲集合內部實現,從而減小內存的使用。hashtable(哈希表),當集合類型沒法知足intset的條件時,Redis會使用hashtable做爲集合的內部實現。

有序集合

有序集合元素不能重複,給每一個元素設置一個分數score做爲排序依據。

                                       表-列表、集合、有序集合區別

有序集合類型的內部編碼有兩種,ziplist(壓縮列表),當有序集合的元素個數小於zset-max-ziplist-entries配置(默認128個),同時每一個元素的值小於zset-max-ziplist-value配置(默認64個字節)時,Redis會用ziplist來做爲有序集合的內部實現,ziplist能夠有效減小內存使用skiplist(跳躍表),當ziplist條件不知足時,有序集合會使用skiplist做爲內部實現,由於此時zip的讀寫效率會降低。

鍵管理

單個鍵管理,type、del、object、exists、expire,鍵重命名rename key newkey、隨機返回一個鍵randomkey、鍵過時(除了expire、ttl命令外,Redis還提供了expireat、pexpire、pexpireat、pttl、persist等一系列命令)、遷移鍵move(不推薦)、dump+restore、migrate(推薦使用,原子操做、支持多鍵,是Redis Cluster實現水平擴容的重要工具

遍歷鍵,因爲單線程架構keys在大量鍵時容易形成Redis阻塞,推薦使用漸進式遍歷scan(也會出現統計漏掉或者重複的極端狀況,不能保證完整遍歷),屢次執行,除了scan之外,Redis提供了面向哈希類型、集合類型、有序集合的掃描遍歷命令,解決諸如hgetall、smembers、zrange可能產生阻塞問題,對應的命令分別是hscan、sscan、zscan,它們的用法和scan基本相似。

數據庫管理,切換數據庫select,清除數據庫flushdb/flushall命令

小功能大用處

慢查詢分析

redis命令執行分爲4個步驟,發送命令,命令排隊,執行命令,返回結果,這裏的慢查詢只統計執行命令的時間(不含命令排隊和網絡傳輸時間因此沒有慢查詢並不表明客戶端沒有超時時間)。

預設閥值如何設置(線上建議調大慢查詢列表,根據Redis併發量進行調整),慢查詢的記錄在哪裏(慢查詢日誌是一種先進先出的隊列,能夠將它持久化到其餘存儲中(mysql),而後使用可視化工具進行查看)。

Redis Shell

redis-cli

redis-server

redis-benchmark作基準性能測試,它提供了不少選項幫助開發和運行人員測試Redis的性能。

Pipeline

Redis命令真正執行的時間一般在微秒級別,因此纔會有Redis性能瓶頸是網絡這樣的說法。爲了解決這一的問題,Pipeline流水線機制就出現了,它能將一組Redis命令進行組裝,經過一次RTT傳輸給Redis,再將這組命令的結果按照順序返回給客戶端,可是它不是原子性的。雖然Pipeline好用,可是每次組裝還須要節制,不然一次組裝的數據量過大,一方面會增長客戶端的等待時間,另外一方面會形成必定的網絡阻塞。

事物與Lua

Redis提供了簡單的事務功能,將一組須要放在一塊兒執行的命令放到multi和exec兩個命令之間,分別表明事務的開始和結束,它們之間的命令是原子順序執行的。Redis事務不支持回滾功能,開發人員須要本身修復這類問題。

命令 說明
mutli 表明事物開始
exec 表明事物結束
discard 命令表示中止事物。
watch 監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。

                                                    表-redis事務命令

redis事務命令出現錯誤,處理機制不盡相同:

  • 語法錯誤則整個事物沒法執行
  • 非語法錯誤則會部分執行,而且不支持回滾, 只會將錯誤包含在事務的結果中, 這不會引發事務中斷或整個失敗,不會影響已執行事務命令的結果,也不會影響後面要執行的事務命令

Lua腳本

Lua語言是C語言開發的腳本語言,普遍用於遊戲領域。Redis就能夠經過eval命令或evalsha命令(利用腳本SHA1校驗和做爲參數執行對應腳本,避免每次發送腳本的開銷,實現腳本常駐服務端,使得腳本功能獲得複用執行Lua腳本,Lua腳本在Redis中的執行是原子性執行的,執行過程當中不會插入其餘命令;能夠利用Lua腳本定製命令,並將這些命令常駐內存實現複用的效果;Lua腳本能夠將多條命令一次性打包,有效減小網絡開銷;

Bitmaps

實現對位的操做,自己不是數據結構,其實是能夠對位進行操做的字符串。能夠把bitmaps想象成以位爲單位的數組,數組的每一個單元只能存儲0和1,數組的下標在Bitmaps中叫作偏移量。Bitmaps基於最小的單位bit進行存儲,因此很是省空間;設置時候時間複雜度O(1)、讀取時候時間複雜度O(n),操做是很是快的;二進制數據的存儲,進行相關計算的時候很是快。redis中bit映射被限制在512MB以內,因此最大是2^32位。建議每一個key的位數都控制下,由於讀取時候時間複雜度O(n),越大的串讀的時間花銷越多。

HyberLogLog

並不是一種新的數據結構,而是一種基數算法,能夠利用極小的內存空間完成獨立總數的統計。使用場景,只爲了計算獨立總數,不須要獲取單條數據,能夠容忍必定的偏差率,畢竟其在內存的佔用量上有很大的優點。

發佈訂閱

redis提供了簡單的發佈訂閱功能,勝在足夠簡單,不支持消息的持久化存儲(意味着新開啓的客戶端沒法接受該頻道以前的消息)和消息傳輸保障機制。

 

                                    

                                                     圖-redis發佈訂閱示意圖

GEO

提供地理信息定位的功能,支持存儲地理位置信息用來實現諸如附近位置、搖一搖這類依賴於地理位置信息的功能。其實現主要包含了如下兩項技術:使用geohash保存地理位置的座標,使用有序集合(zset)保存地理位置的集合。

深刻分析參考:佔小狼的博客-Redis的3個高級數據結構

客戶端

Redis是用單線程來處理多個客戶端的訪問。

客戶端通訊協議

幾乎全部的主流編程語言都有Redis的客戶端,不考慮Redis很是流行的緣由,若是站在技術的角度看緣由還有兩個:客戶端與服務端之間的通訊協議是在TCP 協議之上構建的,客戶端和服務器經過TCP鏈接來進行數據交互,服務器默認的端口號爲6379。客戶端和服務器發送的命令或數據一概以rn(CRLF)結尾,Redis制定了RESP(REdis Serialization Protocol,Redis序列化協議)實現客戶端與服務端的正常交互,這種協議簡單高效,既可以被機器解析,又容易被人類識別。

Java客戶端Jedis

Java有不少優秀的Redis客戶端,其中使用較普遍的是Jedis。

下圖直連方式每次都會新建TCP鏈接,使用後再斷開鏈接,對於頻繁訪問Redis的場景顯然不是高效的使用方式,同時資源沒法控制,極端狀況下會出現鏈接泄露,生產環境考慮使用鏈接池的方式對Jedis鏈接進行管理。

                                    

                                                           圖-Jedis直連Redis

Jedis自己沒有提供序列化工具,也就是說開發者須要本身引入序列化工具,Jedis支持Pipeline特性,支持Lua腳本的執行。

客戶端管理

Redis提供了客戶端相關API對其狀態進行監控和管理。

client list命令能列出與Redis服務端相連的全部客戶端鏈接信息,client setName用於給客戶端設置名字,client kill(client kill ip:port)命令用於殺掉指定IP地址和端口的客戶端,client pause命令用於阻塞客戶端timeout毫秒數(在此期間客戶端鏈接將被阻塞),monitor命令用於監控Redis正在執行的命令(一旦Redis的併發量過大monitor客戶端的輸出緩衝會暴漲,可能瞬間會佔用大量內存)。

客戶端相關配置,Redis提供了maxclients參數來限制最大客戶端鏈接數,timeout(單位爲秒)參數來限制鏈接的最大空閒時間,tcp-keepalive檢測TCP鏈接活性的週期,tcp-backlog,TCP三次握手後,會將接受的鏈接放入隊列中,tcpbacklog就是隊列的大小。

客戶端統計片斷info clients命令,info stats命令。

客戶端常見異常

沒法從鏈接池獲取到鏈接

客戶端讀寫超時

客戶端鏈接超時

客戶端緩衝區異常

Lua腳本正在執行

Redis正在加載持久化文件

Redis使用的內存超過maxmemory配置

客戶端鏈接數過大

客戶端案例分析

客戶端執行monitor命令使得緩衝區暴增形成Redis主節點內存陡增

hgetall慢查詢引發客戶端週期性超時(解決辦法)

持久化

Redis支持RDB和AOF兩種持久化機制,持久化功能有效地避免因進程退出形成的數據丟失問題,當下次重啓時利用以前持久化的文件便可實現數據恢復。

RDB

RDB持久化是把當前進程數據生成快照保存到硬盤的過程,觸發RDB持久化過程分爲手動觸發和自動觸發

手動觸發採用save命令(阻塞當前redis服務器,對於內存比較大的實例會形成長時間阻塞,已廢棄,線上環境不建議使用)bgsave命令(redis進程執行fork操做建立子進程,RDB持久化過程由子進程負責,阻塞只發生在fork階段,通常而言時間很短,是主流的觸發RDB持久化方式

流程以下:

                                    

                                                  圖-bgsave命令的運做流程

RDB文件的處理保存、壓縮、校驗。

RDB的優勢

  • RDB是一個緊湊壓縮的二進制文件,表明Redis在某個時間點上的數據快照。很是適用於備份、全量複製等場景。好比每6小時執行bgsave備份,並把RDB文件拷貝到遠程機器或者文件系統中(如hdfs),用於災難恢復,適合數據冷備和複製傳輸
  • Redis加載RDB恢復數據遠遠快於AOF的方式

RDB的缺點

  • RDB方式數據沒辦法作到實時持久化/秒級持久化。由於bgsave每次運行都要執行fork操做建立子進程,屬於重量級操做頻繁執行成本太高
  • RDB文件使用特定二進制格式保存,Redis版本演進過程當中有多個格式 的RDB版本,存在老版本Redis服務沒法兼容新版RDB格式的問題。

針對RDB不適合實時持久化的問題,Redis提供了AOF持久化方式來解決。

AOF

AOF(append only file)持久化:以獨立日誌的方式記錄每次寫命令,重啓時再從新執行AOF文件中的命令達到恢復數據的目的。AOF的主要做用是解決了數據持久化的實時性,目前已是Redis持久化的主流方式。

                                                    

                                                       圖-AOF工做流程

工做流程

  • 全部的寫入命令會追加到aof_buf(緩衝區)中

AOF爲何直接採用文本協議格式?可能的理由以下:

  • 文本協議具備很好的兼容性。
  • 開啓AOF後,全部寫入命令都包含追加操做,直接採用協議格式,避免了二次處理開銷。
  • 文本協議具備可讀性,方便直接修改和處理。

AOF爲何把命令追加到aof_buf中?
Redis使用單線程響應命令,若是每次寫AOF文件命令都直接追加到硬盤,那麼性能徹底取決於當前硬盤負 載。先寫入緩衝區aof_buf中,還有另外一個好處,Redis能夠提供多種緩衝區同步硬盤的策略,在性能和安全性方面作出平衡。

  • AOF緩衝區根據對應的策略向硬盤作同步操做;

可配置值    說明
always       命令寫入aof_buf後調用系統fsync操做同步到AOF文件,fsync完成後線程返回
everysec    命令寫入aof_buf後調用系統write操做,write完成後線程返回.fsync同步文件操做由專門線程每秒調用一次
no             命令寫入aof_buf後調用系統write操做,不對AOF文件作fsync同步,同步硬盤操做由操做系統負責,一般同步週期最長30秒

write操做會觸發延遲寫(delayed write)機制。Linux在內核提供頁緩衝區用來提升硬盤IO性能。write操做在寫入系統緩衝區後直接返回。同步硬盤操做依賴於系統調度機制,例如:緩衝區頁空間寫滿或達到特定時間週期。同步文件以前,若是此時系統故障宕機緩衝區內數據將丟失。
fsync針對單個文件操做(好比AOF文件),作強制硬盤同步,fsync將阻塞直到寫入硬盤完成後返回,保證了數據持久化。

Redis提供了多種AOF緩衝區同步文件策略,由參數appendfsync控制

  • 配置爲always時,每次寫入都要同步AOF文件,在通常的SATA硬盤 上,Redis只能支持大約幾百TPS寫入,顯然跟Redis高性能特性背道而馳,不建議配置。
  • 配置爲no,因爲操做系統每次同步AOF文件的週期不可控,並且會加大每次同步硬盤的數據量,雖然提高了性能,但數據安全性沒法保證。
  • 配置爲everysec,是建議的同步策略,也是默認配置,作到兼顧性能和數據安全性。理論上只有在系統忽然宕機的狀況下丟失1秒的數據(嚴格說來不太準確)。
  • 隨着AOF文件愈來愈大,須要按期對AOF文件進行重寫,達到壓縮的目的;

重寫後的AOF文件爲何能夠變小?有以下緣由:
1)進程內已經超時的數據再也不寫入文件。
2)舊的AOF文件含有無效命令,如del key一、hdel key二、srem keys、set a1十一、set a222等。重寫使用進程內數據直接生成,這樣新的AOF文件只保留最終數據的寫入命令。
3)多條寫命令能夠合併爲一個,如:lpush list a、lpush list b、lpush list c能夠轉化爲:lpush list a b c。爲了防止單條命令過大形成客戶端緩衝區溢出,對於list、set、hash、zset等類型操做,以64個元素爲界拆分爲多條。
AOF重寫下降了文件佔用空間,除此以外,另外一個目的是:更小的AOF文件能夠更快地被Redis加載,AOF重寫過程能夠手動觸發和自動觸發。手動觸發,直接調用bgrewriteaof命令。自動觸發,根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數肯定自動觸發時機。

  • 當Redis服務器重啓時,能夠加載AOF文件進行數據恢復。

AOF和RDB文件均可以用於服務器重啓時的數據恢復,下圖表示Redis持久化文件加載流程:

                                            

                                                              圖-Redis持久化文件加載流程

文件校驗
加載損壞的AOF文件時會拒絕啓動,AOF文件可能存在結尾不完整的狀況,好比機器忽然掉電致使AOF尾部文件命令寫入不全。Redis爲咱們提供了aof-load-truncated配置來兼容這種狀況,默認開啓。加載AOF時,當遇到此問題時會忽略並繼續啓動。

複製

在分佈式系統中爲了解決單點問題,一般會把數據複製多個副本部署到其餘機器,知足故障恢復和負載均衡等需求。Redis也是如此,它爲咱們提供了複製功能,實現了相同數據的多個Redis副本。複製功能是高可用Redis的基礎,哨兵和集羣都是在複製的基礎上實現高可用的。

配置

參與複製的Redis實例劃分爲主節點(master)和從節點(slave)。默認狀況下,Redis都是主節點。每一個從節點只能有一個主節點,而主節點能夠同時具備多個從節點。複製的數據流是單向的,只能由主節點複製到從節點。配置複製方式採用salveof命令,須要考慮到安全性傳輸延遲

拓撲

Redis的複製拓撲結構能夠支持單層或多層複制關係,根據拓撲復雜性能夠分爲如下三種:一主一從、一主多從、樹狀主從結構。

一主一從

一主一從結構用於主節點出現宕機時從節點提供故障轉移支持,當應用寫命令併發量較高且須要持久化時,能夠只在從節點上開啓AOF,這樣既保證數據安全性同時也避免了持久化對主節點的性能干擾。

一主多從

一主多從結構(又稱爲星形拓撲結構)使得應用端能夠利用多個從節點實現讀寫分離。對於讀佔比較大的場景,能夠把讀命令發送到從節點來分擔主節點壓力。同時在平常開發中若是須要執行一些比較耗時的讀命令,如:keys、sort等,能夠在其中一臺從節點上執行,防止慢查詢對主節點形成阻塞從而影響線上服務的穩定性。對於寫併發量較高的場景,多個從節點會致使主節點寫命令的屢次發送從而過分消耗網絡帶寬,同時也加劇了主節點的負載影響服務穩定性。

樹狀主從結構

樹狀主從結構(又稱爲樹狀拓撲結構)使得從節點不但能夠複製主節點數據,同時能夠做爲其餘從節點的主節點繼續向下層複製。經過引入複製中間層,能夠有效下降主節點負載和須要傳送給從節點的數據量。以下圖所示,數據寫入節點A後會同步到B和C節點,B節點再把數據同步到D和E節點,數據實現了一層一層的向下複製。當主節點須要掛載多個從節點時爲了避免對主節點的性能干擾,能夠採用樹狀主從結構下降主節點壓力

                                        

                                                             圖-樹狀主從結構

原理

                                        

                                                         圖-主從節點創建複製流程圖

Redis在2.8及以上版本使用psync命令完成主從數據同步,同步過程分爲:全量複製和部分複製。
全量複製:通常用於初次複製場景,Redis早期支持的複製功能只有全量複製,它會把主節點所有數據一次性發送給從節點,當數據量較大時,會對主從節點和網絡形成很大的開銷。
部分複製:用於處理在主從複製中因網絡閃斷等緣由形成的數據丟失場景,當從節點再次連上主節點後,若是條件容許,主節點會補發丟失數據給從節點。由於補發的數據遠遠小於全量數據,能夠有效避免全量複製的太高開銷。
部分複製是對老版複製的重大優化,有效避免了沒必要要的全量複製操做。所以當使用複製功能時,儘可能採用2.8以上版本的Redis。

主從節點在創建複製後,它們之間維護着長鏈接並彼此發送心跳命令replconf命令主要用於實時監測主從節點網絡狀態,上報自身複製偏移量,實現保證從節點的數量和延遲性功能。

                                        

                                                            圖-主從心跳檢測

異步複製,主節點不但負責數據讀寫,還負責把寫命令同步給從節點。寫命令的發送過程是異步完成,也就是說主節點自身處理完寫命令後直接返回給客戶 端,並不等待從節點複製完成。所以從節點數據集會有延遲狀況

開發與運維中注意的問題

經過複製機制,數據集能夠存在多個副本(從節點)。這些副本能夠應用於讀寫分離、故障轉移(failover)、實時備份等場景。可是在實際應用複製功能時,依然有一些坑須要跳過。

讀寫分離

當使用從節點響應讀請求時,業務端可能會遇到以下問題:複製數據延遲,讀到過時數據,從節點故障。

                                            

                                                                   圖-Redis讀寫分離示意圖

數據延遲

Redis複製數據的延遲因爲異步複製特性是沒法避免的,延遲取決於網絡帶寬和命令阻塞狀況,好比剛在主節點寫入數據後馬上在從節點上讀取可能獲取不到。須要業務場景容許短期內的數據延遲。對於沒法容忍大量延遲場景,能夠編寫外部監控程序監聽主從節點的複製偏移量,當延遲較大時觸發報警或者通知客戶端避免讀取延遲太高的從節點,實現邏輯以下圖所示。

                                                

                                                            圖-監控程序監控主從節點偏移量

這種方案的成本比較高,須要單獨修改適配Redis的客戶端類庫。若是涉及多種語言成本將會擴大。客戶端邏輯須要識別出讀寫請求並自動路由,還須要維護故障和恢復的通知。採用此方案視具體的業務而定,若是容許不一致性或對延遲不敏感的業務能夠忽略,也能夠採用Redis集羣方案作水平擴展。

讀到過時數據

當主節點存儲大量設置超時的數據時,如緩存數據,Redis內部須要維護過時數據刪除策略,刪除策略主要有兩種:惰性刪除和定時刪除

惰性刪除:主節點每次處理讀取命令時,都會檢查鍵是否超時,若是超時則執行del命令刪除鍵對象,以後del命令也會異步發送給從節點。須要注意的是爲了保證複製的一致性,從節點自身永遠不會主動刪除超時數據。

                                            

                                                圖-主節點惰性刪除過時鍵同步給從節點

定時刪除:Redis主節點在內部定時任務會循環採樣必定數量的鍵,當發現採樣的鍵過時時執行del命令,以後再同步給從節點

                                                

                                                圖-主節點定時刪除同步給從節點

若是此時數據大量超時,主節點採樣速度跟不上過時速度且主節點沒有讀取過時鍵的操做,那麼從節點將沒法收到del命令。這時在從節點上能夠讀取到已經超時的數據。Redis在3.2版本解決了這個問題,從節點讀取數據以前會檢查鍵的過時時間來決定是否返回數據,能夠升級到3.2版原本規避這個問題。

從節點故障問題

對於從節點的故障問題,須要在客戶端維護可用從節點列表,當從節點故障時馬上切換到其餘從節點或主節點上。這個過程相似上文提到的針對延遲太高的監控處理,須要開發人員改造客戶端類庫。

綜上所出,使用Redis作讀寫分離存在必定的成本。Redis自己的性能很是高,開發人員在使用額外的從節點提高讀性能以前,儘可能在主節點上作充分優化,好比解決慢查詢,持久化阻塞,合理應用數據結構等,當主節點優化空間不大時再考慮擴展。建議在作讀寫分離以前,能夠考慮使用Redis Cluster分佈式解決方案,這樣不止擴展了讀性能還能夠擴展寫性能和可支撐數據規模,而且一致性和故障轉移也能夠獲得保證,對於客戶端的維護邏輯也相對容易。

主從配置不一致

對於有些配置主從之間是能夠不一致,好比:主節點關閉AOF在從節點開啓。但對於內存相關的配置必需要一致,好比maxmemory,hash-max-ziplist-entries等參數。當配置的 maxmemory從節點小於主節點,若是複製的數據量超過從節點maxmemory 時,它會根據maxmemory-policy策略進行內存溢出控制,此時從節點數據已經丟失,但主從複製流程依然正常進行,複製偏移量也正常。修復這類問題也只能手動進行全量複製。當壓縮列表相關參數不一致時,雖然主從節點存儲的數據一致但實際內存佔用狀況差別會比較大。

規避全量複製

全量複製是一個很是消耗資源的操做,所以如何規避全量複製是須要重點關注的運維點。

第一次創建複製:因爲是第一次創建複製,從節點不包含任何主節點數據,所以必須進行全量複製才能完成數據同步。對於這種狀況全量複製沒法避免。當對數據量較大且流量較高的主節點添加從節點時,建議在低峯時進行操做,或者儘可能規避使用大數據量的Redis節點。

此外還要避免節點運行ID不匹配、複製積壓緩衝區不足等狀況形成的全量複製。

規避複製風暴

複製風暴是指大量從節點對同一主節點或者對同一臺機器的多個主節點短期內發起全量複製的過程。複製風暴對發起複製的主節點或者機器形成大量開銷,致使CPU、內存、帶寬消耗。

單主節點複製風暴

單主節點複製風暴通常發生在主節點掛載多個從節點的場景。當主節點重啓恢復後,從節點會發起全量複製流程,這時主節點就會爲從節點建立RDB快照,若是在快照建立完畢以前,有多個從節點都嘗試與主節點進行全量同步,那麼其餘從節點將共享這份RDB快照。這點Redis作了優化,有效避免了建立多個快照。可是,同時向多個從節點發送RDB快照,可能使主節點的網絡帶寬消耗嚴重,形成主節點的延遲變大,極端狀況會發生主從節點鏈接斷開,致使複製失敗。解決方案首先能夠減小主節點(master)掛載從節點(slave)的數量, 或者採用樹狀複製結構,加入中間層從節點用來保護主節點,以下圖所示:

                                               

                                               圖-採用樹狀結構下降多個從節點對主節點的消耗
從節點採用樹狀樹很是有用,網絡開銷交給位於中間層的從節點,而沒必要消耗頂層的主節點。可是這種樹狀結構也帶來了運維的複雜性,增長了手動和自動處理故障轉移的難度。

單機器複製風暴

因爲Redis的單線程架構,一般單臺機器會部署多個Redis實例。當一臺 機器(machine)上同時部署多個主節點(master)時,若是這臺機器出現故障或網絡長時間中斷,當它重啓恢復後,會有大量從節點(slave)針對這臺機器的主節點進行全量複製,會形成當前機器網絡帶寬耗盡。如何避免?應該把主節點儘可能分散在多臺機器上,避免在單臺機器上部署過多的主節點。當主節點所在機器故障後提供故障轉移機制,避免機器恢復後進行密集的全量複製。

Redis的噩夢:阻塞

Redis是典型的單線程架構,全部的讀寫操做都是在一條主線程中完成的。Redis用於高併發的場景時,這條線程就變成了它的生命線。若是出現阻塞,哪怕是很短期,對於應用來講,都是噩夢。致使阻塞問題的場景分爲內在緣由和外在緣由。

當Redis阻塞時,線上應用服務應該最早感知到,這時應用方會收到大量Redis超時異常。常規作法是在應用方加入異常統計並經過郵件/微信/短信報警,以便及時發現通知問題。開發人員須要處理如何統計異常以及觸發報警的時機。什麼時候報警根據應用的併發量決定。因爲Redis調用API會分散在項目的多個地方,每一個地方都監聽異常並加入監控代碼必然難以維護。這能夠藉助於日誌系統,使用logback或者log4j。當異常發生時,異常信息最終會被日誌系統收集到Appender,默認的Appender通常是具體的日誌文件,開發人員能夠自定義一個Appender,用於專門統計異常和觸發報警邏輯。

                                    

                                       圖-自定義Appender收集Redis異常

內在緣由

內在緣由主要包括,不合理使用API或數據結構、CPU飽和、持久化阻塞

一般Redis執行命令速度很是快,但也存在例外,如對一個包含上萬個元素的hash結構執行hgetall操做,因爲數據量比較大且命令算法複雜度是O(n),這條命令執行速度必然很慢,這個問題就是典型的不合理使用API和數據結構,在高併發場景下對於時間複雜度超過O(n)的命令應該儘可能避免在大對象上執行

慢查詢

Redis原生提供慢查詢統計功能,執行slowlog get{n}命令能夠獲取最近的n條慢查詢命令,默認對於執行超過10毫秒的命令都會記錄到一個定長隊列中,線上實例建議設置爲1毫秒便於及時發現毫秒級以上的命令。

發現慢查詢後,按照如下兩個方向去調整:

  • 修改低算法度的命令,例如hgetall改成hmget等,禁用keys、sort等命令
  • 調整大對象:縮減大對象數據或把大對象拆分爲多個小對象,防止一次命令操做過多的數據。大對象拆分過程須要視具體的業務決定,如用戶好友集合存儲在Redis中,有些熱點用戶會關注大量好友,這時能夠按時間或其餘緯度拆分到多個集合中。如何發現大對象,redis-cli --bigkeys命令,內部原理採用分段進行scan操做,把歷史掃描過的最大對象統計出來便於分析優化。

CPU飽和

單線程的Redis處理命令只能使用一個CPU。而CPU飽和是指Redis把CPU使用率跑到接近100%。使用top命令很容易識別出對應Redis進程的CPU使用率redis-cli --stat獲取當前redis使用狀況,該命令每秒輸出一行統計信息。

對於接近飽和的Redis實例,垂直層面的命令優化很難達到效果,這時就須要水平擴展來分攤OPS壓力。若是隻有幾百或者幾千OPS的Redis實例就接近CPU飽和是很不正常的,有可能使用了高算法複雜度的命令,還有一種狀況是過分的內存優化(如ziplist的過分內存使用優化

持久化阻塞

開啓了持久化功能的Redis節點,須要排查是不是持久化致使的阻塞。持久化引發主線程阻塞的操做主要有:fork阻塞、AOF刷盤阻塞、HugePage寫操做阻塞。

外在緣由

外在緣由主要包括,CPU競爭、內存交換、網絡問題等。

CPU競爭

進程競爭:Redis是典型的CPU密集型應用,不建議和其餘多核CPU密集型服務部署在一塊兒。當其餘進程過分消耗CPU時候,將嚴重影響Redis吞吐量。能夠經過top/sar等命令定位到CPU消耗的時間點和具體進程,這個問題比較容易發現,須要調整服務之間部署結構。

綁定CPU:部署Redis時候爲了充分利用多核CPU,一般一臺機器部署多個實例。常見的一種優化是把Redis進程綁定CPU上,用於下降CPU頻繁上下文切花的開銷。當Redis父進程建立子進程進行RDB/AOF重寫時,子進程在重寫時對單核CPU使用率一般在90%以上,父子進程共享CPU將會存在激烈CPU競爭,極大影響Redis的穩定性。

內存交換

內存交換對於Redis來講是很是致命的,Redis保證高性能的一個重要前提是全部的數據都在內存中。若是操做系統把Redis使用的部份內存換出到硬盤,因爲內存與硬盤讀寫速度差幾個數量級,會致使發生交換後的Redis性能急劇降低。預防內存交換的方法有:保證機器充足的可用內存;確保全部Redis實例設置最大可用內存,防止極端狀況下Redis內存不可控的增加;下降系統使用swap優先級。

網絡問題

鏈接拒絕,當出現網絡閃斷或者鏈接數溢出時,客戶端會出現沒法鏈接Redis的狀況,具體能夠分爲網絡閃斷、Redis鏈接拒絕、鏈接溢出(指操做系統或者Redis客戶端在鏈接時的問題);

網絡延遲,網絡延遲取決於客戶端到Redis服務器之間的網絡環境,主要包括它們之間的物理拓撲和帶寬佔用狀況;

網卡軟中斷,網卡軟中斷指因爲單個網卡隊列只能使用一個CPU,高併發下網卡數據交互都集中在同一個CPU,致使沒法充分利用多核CPU的狀況,通常出如今網絡高流量吞吐的場景

理解內存

高效利用Redis內存首先須要理解Redis內存消耗在哪裏如何管理內存,最後才能考慮如何優化內存,可以實現用更少的內存存儲更多的數據,從而下降成本。當Redis內存不足時,首先考慮的問題不是加機器作水平擴展,應該先嚐試作內存優化,當遇到瓶頸時,再去考慮水平擴展。即便對於集羣化方案,垂直層面優化也一樣重要,避免沒必要要的資源浪費和集羣化後的管理成本。內存優化首先要掌握Redis內存存儲特性好比字符串、壓縮編碼、整數集合等,再根據規模和所用命令需求去調整,從而達到空間和效率的最佳平衡

內存消耗

內存消耗能夠分爲進程自身消耗子進程消耗。

可使用info memory 查看內存消耗。

自身內存很是少,一個空的Redis進程消耗內存能夠忽略不計。

對象內存是Redis內存佔用最大的一塊,存儲着全部的用戶數據。每次建立鍵值對時,至少建立兩個類型對象,key對象和value對象。對象內存消耗能夠簡單理解爲 = sizeof(key) + sizeof(value)。鍵對象都是字符串,在使用時很容易忽略鍵對內存消耗的影響,應當避免使用過長的鍵。value主要包含前述的5種數據類型,其它數據類型都是在這5種數據結構之上實現的,如Bitmaps和HyperLogLog使用字符串實現,GEO使用有序集合實現等。

緩存內存主要包括客戶端緩存(全部接入到Redis服務器TCP鏈接的輸入輸出緩衝)、複製積壓緩衝區(可重用的固定大小緩衝區用於實現部分複製功能,整個主節點只有一個,全部從節點共享此緩衝區,可有效避免全量複製)、AOF緩衝區(用於在Redis重寫期間保存最近的寫入命令)

內存碎片,默認內存分配器採用jemalloc,可選還有glibc、tcmalloc。頻繁的更新操做、大量過時鍵刪除將會致使高內存碎片。主要的解決辦法有數據對齊、安全重啓進行碎片從新整理。

                                                   

                                              圖-Redis內存消耗劃分        

子進程內存消耗:AOF/RDB重寫時Redis建立的子進程消耗的內存。

內存管理

Redis主要經過設置內存上限回收策略實現內存管理。

設置內存上限

Redis使用maxmemory參數限制最大可用內存,主要目的有:用於緩存場景,當超出上限時使用LRU等刪除釋放空間;防止全部內存超過服務器物理內存。maxmemory參數限制的事Redis實際使用的內存量,因爲內存碎片的存在,實際消耗的內存可能會比maxmemory設置的更大,實際使用時要當心這部份內存溢出。得益於Redis單線程架構和內存限制機制,即便沒有采用虛擬化,不一樣的Redis進程之間能夠很好的實現CPU和內存的隔離性,以下圖所示。Redis的內存上限能夠經過config set maxmemory進行動態修改,從而達到自由伸縮內存的目的。

                                                     

                                            圖-服務器分配4個4GB的Redis進程

內存回收策略

主要體如今如下幾個方面:

  • 刪除達到過時時間的鍵對象;

因爲進程內保存大量的鍵,維護每一個鍵精準的過時刪除機制會消耗大量的CPU,對於單線程的Redis來講成本太高,所以Redis採用惰性刪除定時任務刪除機制實現過時鍵的內存回收。

惰性刪除:用於客戶端讀取帶有超時屬性的鍵時,若是已經超出鍵設置的過時時間,會執行刪除操做並返回空,這種策略是因爲節省CPU成本考慮,單獨使用這種方式存在內存泄露的問題。

定時任務刪除:內部維護定時任務,採用自適應算法,根據鍵的過時比例、使用快慢兩種速率模式回收鍵,若是超過檢查數25%的鍵過時,循環執行回收邏輯直到不足25%或者運行超時爲止,快慢模式內部刪除邏輯相同,只是執行的超時時間不一樣,慢模式爲25秒。

                                            

                                                         圖-定時任務刪除過時鍵邏輯

  • 內存使用達到maxmemory上限時觸發內存溢出控制策略。

                                   

                                                                      圖-內存溢出的控制策略

當Redis一直工做在內存溢出狀態下且設置非noeviction策略時,會頻繁觸發回收內存操做,頻繁執行內存回收成本很高,主要包括查找可回收鍵和刪除鍵的開銷,若是當前Redis有從節點,會進行同步操做,致使寫放大的問題。

                                             

                                                                 圖-寫入操做觸發內存回收操做

內存優化

redisObject對象

                                    

                                              圖-redisObject

縮減鍵值對象

下降內存使用最直接的方式就是縮減鍵和值的長度。在設計鍵時,在完整描述業務狀況下,鍵值越短越好。值對象縮減比較複雜,常見需求是把業務對象序列化成二進制數據放入Redis,首先須要在業務上精簡業務對象,其次在序列化工具使用上,應該選取更高效的序列化工具來下降字節數組的大小。格式存儲數據如json和xml做爲值對象便於調試和跨語言,可是一樣的數據對比字節數組所需的空間更大,在內存緊張的狀況下能夠採用必定壓縮算法,如Google Snappy、GZIP。

共享對象池

Redis內部維護0-9999的整數對象池以節省對象內存,須要注意共享對象池與LRU+maxmemory策略衝突。

字符串優化

Redis自身實現字符串,存在預分配機制,因此儘可能減小字符串頻繁修改操做(append、setrange),直接使用set,下降預分配帶來的內存浪費和內存碎片化。

                                        

                                                    圖-字符串結構SDS

編碼優化

Redis經過編碼實現空間和時間的平衡。如ziplist編碼採用線性連續的內存結構以節約內存,能夠做爲hash、list、zset類型的底層數據結構實現,適合小對象和長度有限的數據,內部表現爲數據緊湊排列的一塊連續內存數組。如inset編碼是集合類型編碼的一種,內部表現爲存儲有序、不重複的整數集,inset編碼適合整數集合。

                                    

                                                        表-redis內部編碼

控制鍵的數量

過多的鍵一樣會消耗內存。經過在客戶端預估鍵規模,把大量鍵分組映射到多個hash結構中下降鍵的數量(客戶端須要預估鍵的規模並設計hash分組規則,加劇了客戶端開發成本)。

                                            

                                                 圖-客戶端維護哈希分組下降鍵規模

哨兵

Redis的高可用方案。

基本概念

主從複製的問題:主節點故障後的恢復須要人工干預,故障轉移實時性和準確性沒法獲得保障,且主節點的寫能力和存儲能力受到單機的限制。即便將故障轉移流程自動化,仍然存在如下問題:判斷節點不可達的機制是否健全和準確;若是存在多個從節點,怎麼保證只有一個被晉升爲主節點;通知客戶端新的主節點機制是否足夠健壯。Redis Sentinel正是用於解決這些問題。

當主節點出現故障時,Redis Sentinel能自動完成故障發現和故障轉移,並通知應用方,從而實現真正的高可用。使用Redis Sentinel,建議使用2.8以上版本。

Redis Sentinel是一個分佈式架構,其中包含若干個Sentinel節點和Redis數據節點,每一個Sentinel節點會對數據節點和其他Sentinel節點進行監控,當它發現節點不可達時,會對節點作下線標識。若是被標識的是主節點,它還會和其餘Sentinel節點進行「協商」,當大多數Sentinel節點都認爲主節點不可達時,它們會選舉出一個Sentinel節點來完成自動故障轉移的工做,同時會將這個變化實時通知給Redis應用方。整個過程徹底是自動的,不須要人工來介入,因此這套方案頗有效地解決了Redis的高可用問題。

                                                

                                                   圖-Redis主從複製與Redis Sentinel架構的區別

整個故障轉移的處理邏輯能夠參考,能夠看出 Redis Sentinel具備如下幾個功能:

  • 監控:Sentinel節點會按期檢測Redis數據節點、其他Sentinel節點是否可達。
  • 通知:Sentinel節點會將故障轉移的結果通知給應用方。
  • 主節點故障轉移:實現從節點晉升爲主節點並維護後續正確的主從關係。
  • 配置提供者:在Redis Sentinel結構中,客戶端在初始化的時候鏈接的是Sentinel節點集合,從中獲取主節點信息。

同時看到,Redis Sentinel包含了若個Sentinel節點,這樣作也帶來了兩個好處:

  • 對於節點的故障判斷是由多個Sentinel節點共同完成,這樣能夠有效地防止誤判。
  • Sentinel節點集合是由若干個Sentinel節點組成的,這樣即便個別Sentinel 節點不可用,整個Sentinel節點集合依然是健壯的。

可是Sentinel節點自己就是獨立的Redis節點,只不過它們有一些特殊, 它們不存儲數據,只支持部分命令。

安裝和部署

Redis哨兵的部署能夠參考,實際部署時須要注意,Sentinel節點不該該部署在一臺物理「機器」上,部署至少三個且奇數個的Sentinel節點,只有一套Sentinel,仍是每一個主節點配置一套Sentinel?(一套仍是多套須要權衡)。

API

Sentinel節點是一個特殊的Redis節點,它有本身專屬的API

客戶端鏈接

Sentinel節點集合具有了監控、通知、自動故障轉移、配置提供者若干功能,也就是說實際上最瞭解主節點信息的就是Sentinel節點集合,而各個主節點能夠經過<master-name>進行標識的,因此,不管是哪一種編程語言的客戶端,若是須要正確地鏈接Redis Sentinel,必須有Sentinel節點集合和 masterName兩個參數。客戶端初始化鏈接的是Sentinel節點集合,再也不是具體的Redis節點,但Sentinel只是配置中心不是代理。

Redis Sentinel客戶端基本實現原理。

實現原理

三個定時監控任務

一套合理的監控機制是Sentinel節點斷定節點不可達的重要保證,Redis Sentinel經過三個定時監控任務完成對主節點、從節點、其他Sentinel節點的監控。

                                           

                                                圖-Sentinel節點定時執行info命令

每隔10秒,每一個Sentinel節點會向主節點和從節點發送info命令獲取最新的拓撲結構;每隔2秒,每一個Sentinel節點會向Redis數據節點的__sentinel__:hello 頻道上發送該Sentinel節點對於主節點的判斷以及當前Sentinel節點的信息(以下圖所示),同時每一個Sentinel節點也會訂閱該頻道,來了解其它Sentinel節點以及它們對主節點的判斷;每隔1秒,每一個Sentinel節點會向主節點、從節點、其他Sentinel節點發送一條ping命令作一次心跳檢測,來確認這些節點當前是否可達。

                                        

 

                                      圖-Sentinel節點發布和訂閱__sentinel__hello頻道

                                       

                                           圖-Sentinel節點向其他節點發送ping命令

主觀下線和客觀下線

上一小節介紹的第三個定時任務,每一個Sentinel節點會每隔1秒對主節 點、從節點、其餘Sentinel節點發送ping命令作心跳檢測,當這些節點超過 down-after-milliseconds沒有進行有效回覆,Sentinel節點就會對該節點作失敗斷定,這個行爲叫作主觀下線。從字面意思也能夠很容易看出主觀下線是當前Sentinel節點的一家之言,存在誤判的可能,以下圖所示。

                                       

                                                圖-Sentinel節點主觀下線檢測

當Sentinel主觀下線的節點是主節點時,該Sentinel節點會經過sentinel ismaster-down-by-addr命令向其餘Sentinel節點詢問對主節點的判斷,當超過 <quorum>個數,Sentinel節點認爲主節點確實有問題,這時該Sentinel節點會作出客觀下線的決定,這樣客觀下線的含義是比較明顯了,也就是大部分 Sentinel節點都對主節點的下線作了贊成的斷定,那麼這個斷定就是客觀的,以下圖所示。

                                        

                                             圖-Sentinel節點對主節點作客觀下線

領導者Sentinel節點選舉

假如Sentinel節點對於主節點已經作了客觀下線,那麼是否是就能夠當即進行故障轉移了?固然不是,實際上故障轉移的工做只須要一個Sentinel 節點來完成便可,因此Sentinel節點之間會作一個領導者選舉的工做,選出 一個Sentinel節點做爲領導者進行故障轉移的工做。Redis使用了Raft算法實現領導者選舉,具體能夠參考

故障轉移

領導者選舉出的Sentinel節點負責故障轉移,具體步驟以下:

  • 在從節點列表中選出一個節點做爲新的主節點,選擇方法以下:過濾:「不健康」(主觀下線、斷線)、5秒內沒有回覆過Sentinel節點ping響應、與主節點失聯超過down-after-milliseconds*10秒。選擇slave-priority(從節點優先級)最高的從節點列表,若是存在則返回,不存在則繼續。選擇複製偏移量最大的從節點(複製的最完整),若是存在則返回,不存在則繼續。選擇runid最小的從節點。

                                    

                                                   圖-選出最好的從節點

  • Sentinel領導者節點會對第一步選出來的從節點執行slaveof no one命令讓其成爲主節點。
  • Sentinel領導者節點會向剩餘的從節點發送命令,讓它們成爲新主節點的從節點,複製規則和parallel-syncs參數有關。
  • Sentinel節點集合會將原來的主節點更新爲從節點,並保持着對其關注,當其恢復後命令它去複製新的主節點。

高可用讀寫分離

從節點能夠實現主節點的故障轉移而且能夠擴展主節點的讀能力,一般模型以下圖所示,可是從節點不是高可用的,若是slave-1節點出現故障,客戶端client-1將與其失聯,其次Sentinel只會對該節點作主觀下線由於Sentinel的故障轉移是針對主節點的,因此不少時候,從節點僅僅是做爲主節點的一個熱備,不讓它參與客戶端的讀操做,就是爲了保證總體的高可用性,存在一些浪費在設計Redis Sentinel的從節點高可用時,藉助Sentinel節點的消息通知機制,實時掌握全部節點的狀態,把全部從節點看作一個資源池(以下圖所示Redis Sentinel下的讀寫分離架構圖),不管是上線仍是下線從節點,客戶端都能及時感知到(將其從資源池中添加或者刪除),這樣從節點的高可用目標就達到了。

                                

                                            圖-通常的讀寫分離模型

                                   

                                            圖-Redis Sentinel下的讀寫分離架構圖

集羣

當遇到單機內存、併發、流量等瓶頸時,能夠採用Cluster架構方案達到負載均衡的目的。

數據分佈

分佈式數據首先要解決把整個數據集按照分區規則映射到多個節點的問題。須要重點關注的事數據分區規則,常見的分區規則有哈希分區順序分區兩種。

哈希分區,離散度好、數據分佈業務無關、沒法順序訪問,表明產品有Redis Cluster、Cassandra;

順序分區,離散度易傾斜、數據分佈業務相關、可順序訪問,表明產品Bigtable、Hbase。

哈希分區

常見的哈希分區有這幾種:

  • 節點取餘分區

使用特定的數據,如Redis的鍵或用戶ID,再根據節點數量N使用公式:hash(key)%N計算出哈希值,用來決定數據映射在哪個節點,其優勢就是簡單。缺點:添加或者移除服務器時,幾乎全部緩存都要重建,還要考慮雪崩式崩潰問題。

若是使用多倍擴容,可使得遷移降到50%,這個遷移會存在一個問題,數據進行遷移後第一次是沒法從緩存中獲取到數據的,要在數據庫中去取數據,而後進行回寫,寫到新的節點,大量的回寫也會對系統性能帶來問題。

  • 一致性哈希分區

具體能夠參考

好處:在於加入和刪除節點隻影響哈希中相鄰節點,對其餘節點無影響,對於節點比較多的狀況下,影響範圍特別小。

缺點:加減節點會形成哈希環中部分數據沒法命中,須要手動處理或者過略這部分數據,所以一致性哈希經常使用於緩存場景;當使用少許節點,節點變化將大範圍影響哈希環中數據映射,所以不適合少許數據節點的分佈式方案;普通的一致性哈希分區在增減節點時須要增長一倍或減去一半節點才能保證數據和負載的均衡。

  • 虛擬槽哈希分區

虛擬槽分區巧妙使用了哈希空間,使用分散度良好的哈希函數把全部數據映射到一個固定範圍的整數集合中,整數定義爲槽(slot)。這個範圍通常遠遠大於節點數,槽是集羣內數據管理和遷移的基本單位。目的就是爲了方便數據拆分和集羣擴展。每一個節點會負責必定數量的槽。虛擬槽分區解耦了數據和節點之間的關係,簡化了節點擴容和收縮難度,節點自身維護槽的映射關係,不須要客戶端或者代理服務維護槽分區元數據,支持節點、槽、鍵之間的映射查詢,用於數據路由、在線伸縮等場景。

                                                

                                                    圖-槽集合與節點關係

                                        

                                               圖-使用哈希函數將鍵映射到槽上

集羣功能對比單機在功能上存在一些限制,批量操做、事務操做支持有限,不支持多數據庫空間、複製結構只支持一層等。

搭建集羣

三步:準備節點、節點握手(節點經過Gossip協議彼此通訊,達到感知對方過程)、分配槽。也能夠用Redis官方工具redis-trib.rb搭建集羣。

節點通訊

分佈式部署須要提供維護節點元數據信息的機制(元數據是指:節點負責哪些數據,是否出現故障等狀態信息)。常見的元數據維護方式:集中式和p2p方式。Redis集羣採用P2P的Gossip(流言)協議。Gossip協議工做原理就是節點彼此不斷通訊交換信息,通常時間後全部節點都會知道集羣完整的信息,相似流言傳播。通訊過程:集羣中的每一個節點會單獨開啓一個TCP通道,用於節點之間彼此通訊,通訊端口都在基礎端口加10000;每一個節點在週期內選擇幾個節點發送ping消息;收到ping消息的節點經過pong消息做爲響應。

有些節點可能知道所有也有可能知道部分節點,只要這些節點彼此能夠正常通訊,當節點出故障、新節點加入、主從角色變化、槽信息變動等事件發送經過不斷ping/pong消息通訊,通過一段時間後全部節點都會知道集羣所有節點的最新狀態,達到集羣狀態同步。

Gossip協議主要職責就是信息交換。經常使用的消息可分爲ping、pong、meet消息、fail消息等。meet,通知新節點加入,以後在集羣中進行週期性的ping、pong消息交換(用於檢測節點是否在線和交換彼此狀態信息)。fail消息,當節點預判集羣內另外一個節點下線時,會向集羣內廣播一個fail消息。

                                        

                                                        圖-不一樣消息通訊模式

節點選擇:由於ping/pong消息會攜帶當前節點和部分其餘節點的狀態信息,勢必加劇帶寬和計算負擔。redis集羣節點通訊採用固定頻率。通訊節點選擇過多雖然能夠作到信息及時交換可是成本太高,若是過少下降交換頻率影響故障判斷、新節點發現等需求的速度。所以Redis集羣的Gossip協議須要兼顧信息交換實時性和成本開銷。具體選擇規則以下圖所示,信息交換的成本主要體如今單位時間發送消息的節點數量和每一個消息攜帶的數據量。具體規則參考

                                    

                                              圖-選擇通訊節點的規則和消息攜帶的數據量

集羣伸縮

Redis集羣能夠實現對節點的靈活上下線控制,其中原理能夠抽象爲槽和對應數據在不一樣節點之間的靈活移動。集羣伸縮=槽和數據在節點之間的移動

擴容集羣

三步驟,準備新節點、加入集羣、遷移槽和數據。遷移過程是集羣擴容最核心的環節。槽是Redis集羣管理數據的基本單位,首先須要爲新節點制定槽的遷移計劃,肯定原有節點的哪些槽須要遷移到新節點。槽遷移計劃肯定後開始逐個把槽內數據從源節點遷移到目標節點,數據遷移過程是逐個槽進行的,每一個槽數據遷移的流程以下圖所示。

                                    

                                                   圖-新節點加入的槽遷移計劃

                                    

                                                圖-槽和數據遷移流程

收縮集羣

收縮集羣意味着縮減規模,須要從現有集羣中安全下線部分節點,安全下線節點流程以下圖所示。具體分爲下線遷移槽、忘記節點兩步。遷移下線節點中的槽和數據,方向正好和擴容遷移方向相反。集羣內的節點不停地經過Gossip消息彼此交互節點狀態,經過cluster forget {down Nodeld}實現讓集羣內全部節點忘記下線的節點。

                                        

                                                圖-節點安全下線流程

請求路由

在集羣模式下,Redis接受任何鍵相關命令時首先計算鍵對應的槽(根據鍵有效部分使用CRC16計算出散列值,再取餘16383,使每一個鍵均可以映射到0~16383槽範圍內),再根據槽找出所對應的節點,若是節點是自身,則處理鍵命令,不然回覆MOVED重定向錯誤,通知客戶端請求正確的節點。每次執行鍵命令前都要到Redis上進行重定向才能找到執行命令的節點,額外增長了IO開銷,這不是Redis集羣集羣高效的使用方式,這種客戶端又叫做Dummy(傀儡)客戶端,集羣客戶端都採用另一種實現:Smart(智能)客戶端。Smart客戶端經過在內部維護slot-node的映射關係,本地就能夠實現鍵到節點的查找,從而保證IO效率的最大化,而MOVED重定向負責協助Smart客戶端更新slot-node映射。Jedis( Smart客戶端)操做流程以下圖。

                                              

                                                          圖-MOVED重定向執行流程

                                        

                                                        圖-Jedis客戶端命令執行流程

ASK重定向,Redis集羣支持在線遷移槽和數據來完成水平伸縮,當槽對應的數據從源節點到目標節點遷移過程當中,客戶端須要作到智能識別,保證鍵命令可正常執行。

                                                

                                                        圖-slot遷移中的部分鍵場景

                                        

                                                        圖-ASK重定向流程

故障轉移

Redis集羣內節點經過ping/pong消息實現節點通訊,消息不但能夠傳播節點槽信息,還能夠傳播其它狀態如:主從狀態、節點故障等。所以故障發現也是經過消息傳播機制實現的,主要環節包括:主觀下線和客觀下線。主觀下線流程以下圖。當某個節點判斷另外一個節點主觀下線後,相應的節點狀態會跟消息在集羣內傳播。經過Gossip消息傳播,集羣內節點不斷收集到故障節點的下線報告,當半數以上持有槽的主節點都標記某個節點是主觀下線時,觸發客觀下線流程。

                                        

                                                            圖-主觀下線識別流程

 

故障恢復

故障節點變爲客觀下線後,若是下線節點是持有槽的主節點則須要在它的從節點中選出一個替換它,從而保證集羣的高可用。下線主節點的全部從節點承擔故障恢復的義務,當從節點經過內部定時任務發現自身複製的主節點進入客觀下線時,將會觸發故障恢復流程。

                                            

                                                圖-故障恢復流程

故障轉移時間

故障轉移時間跟cluster-node-timeout參數息息相關,默認15秒。配置能夠根據業務容忍度作出適當調整。

集羣運維

集羣完整性:默認狀況下當集羣16384個槽任何一個沒有指派到節點時整個集羣不可用。可是當持有槽的主節點下線時,從故障發現到自動完成轉移期間整個集羣是不可用狀態。建議修改 cluster-require-full-coverage配置爲no,當主節點故障時隻影響它負責槽的相關命令執行。

帶寬消耗:集羣內Gossip消息通訊自己會消耗帶寬,官方建議集羣最大規模在1000內。集羣內全部節點經過ping/pong消息彼此交換信息, 集羣帶寬消耗主要分爲:讀寫命令消耗+Gossip消息消耗。節點間消息通訊對帶寬的消耗主要體如今:消息發送頻率、消息數據量和節點部署的機器規模。在知足業務須要的狀況下儘可能避免大集羣,適當提升cluster-node-timeout下降消息發送頻率,若是條件容許集羣儘可能均勻部署在更多機器上避免集中部署。

Pub/Sub(發佈/訂閱)廣播問題:用於針對頻道實現消息的發佈和訂閱,在集羣模式下內部實現對全部的publish命令都會向全部節點進行廣播一次,加劇帶寬負擔。當頻繁應用pub/sub功能應該避免在大量節點的集羣內使用,負責嚴重消耗集羣內網絡帶寬。建議使用sentinel結構專門用於Pub/Sub功能,從而規避這個問題。

集羣傾斜:數據傾斜,節點和槽分配嚴重不均 、不一樣槽對應鍵數量差別過大、集合對象包含大量元素、內存相關配置不一致;請求傾斜:(常出如今熱點鍵場景)避免方式:合理設計鍵,熱點集合對象作拆分或使用hmget代替hgetall避免總體讀取,不要使用熱鍵做爲hash_tag,避免映射到同一槽,對於一致性要求不高的場景,可以使用本地緩存減小熱點調用。

集羣讀寫分離:集羣模式下的讀寫分離會遇到複製延遲、讀取過時數據,從節點故障等問題,集羣模式下讀寫分離成本比較高能夠直接擴展主節點數量提升集羣性能,通常不建議集羣模式下作讀寫分離。集羣讀寫分離有時用於特殊業務場景:利用複製的最終一致性使用多個從節點作跨機房部署下降讀命令網絡延遲,主節點故障轉移時間過長,業務端能夠把讀請求路由給從節點保證讀操做可用。

手動故障轉移:指定從節點發起轉移流程,主從角色進行轉換,從節點變爲新的主節點對外提供服務,舊的主節點變爲它從節點。

數據遷移:用於數據從單機向集羣環境遷移的場景,redis-trib.rb提供導入功能,還有社區開源的遷移工具如惟品會開發的redis-migrate-tool。

開發與運維的陷阱

處理bigkey的方案

bigkey是指key對應的value所佔的內存空間比較大,例如一個字符串類型的value能夠最大存到512MB。非字符串類型:體如今單個value值很大,通常認爲超過10kB就是bigkey;非字符串類型:體如今元素個數過多。bigkey危害主要體如今,內存空間不均勻、超時阻塞、網絡阻塞,若是bigkey是一個熱點key,那麼帶來的危害將會放大。

如何發現bigkey?被動收集,修改Redis客戶端當拋出異常時打印出全部key,方便排查bigkey問題。主動監測,scan+debug object key,結合pipeline機制或者在從節點完成,避免阻塞Redis。

如何刪除bigkey?string類型用del, 其餘類型用hscan命令獲取部分field-value,再利用hdel刪除每一個field(爲了快速可使用pipeline)。Redis4.0版本支持lazy delete free模式以無阻塞方式刪除bigkey。

熱點key尋找與解決

極端狀況下熱點key會超過Redis自己能承受的OPS,尋找熱點key對開發和運維人員很是重要。

能夠從如下幾個方面進行收集熱點key:

客戶端設置全局字典統計,須要考慮內存泄露問題、無侵入設計;

代理端實現,像Twemproxy、Codis等基於代理的Redis分佈式架構,全部客戶端的請求都是經過代理端完成的,代理是Redis客戶端和服務端的橋樑,基本過程以下圖所示。

                                            

                                                        圖-基於代理的熱點key統計

Redis服務端,monitor命令能夠監控到Redis執行的全部命令,Facebook開源的redis-faina正是採用這樣原理基於Python語言實現的,可是monitor命令在高併發條件下,會存在內存暴增和影響Redis性能的隱患,全部此種方法適合在短期內執行且只能統計單個Redis節點。

機器抓包,Redis客戶端使用TCP協議與服務端進行交互,通訊協議採用的是REST。若是站在機器的角度,能夠經過對機器上全部的Redis端口的TCP數據包進行抓取完成熱點key的統計。ELK體系下的packetbeat插件能夠對Redis、Mysql等衆多主流服務的數據包抓取、分析、報表展現,可是是以機器爲單位進行統計,集羣規模下須要後期彙總。

如何解決熱點key?

  • 拆分複雜數據結構

若是當前key類型是二級數據結構,能夠考慮將熱點key拆分爲若干個新的key分佈到不一樣Redis節點上,從而減輕壓力

  • 遷移熱點key

以Redis Cluster爲例,能夠將熱點key所在的slot單獨遷移到一個新的Redis節點上,會增長運維成本。

  • 本地緩存

將熱點key放在業務端的本地緩存中,數據更新時使用發佈訂閱機制解決業務端和Redis數據不一致的問題。

相關文章
相關標籤/搜索