本文由 DevOpsDays 本文由簡書做者kelgon供稿,高效運維社區致力於陪伴您的職業生涯,與您一塊兒愉快的成長。git
做者:kelgon
連接:http://www.jianshu.com/p/2f14bc570563
來源:簡書github
本文將從Redis的基本特性入手,經過講述Redis的數據結構和主要命令對Redis的基本能力進行直觀介紹。以後在性能調優等方面進行更深刻的介紹和指導。
Redis是一個開源的,基於內存的結構化數據存儲媒介,能夠做爲數據庫、緩存服務或消息服務使用。redis
Redis支持多種數據結構,包括字符串、哈希表、鏈表、集合、有序集合、位圖、Hyperloglogs等。算法
Redis具有LRU淘汰、事務實現、以及不一樣級別的硬盤持久化等能力,而且支持副本集和經過Redis Sentinel實現的高可用方案,同時還支持經過Redis Cluster實現的數據自動分片能力。數據庫
Redis的主要功能都基於單線程模型實現,也就是說Redis使用一個線程來服務全部的客戶端請求,同時Redis採用了非阻塞式IO,並精細地優化各類命令的算法時間複雜度,這些信息意味着:數組
Redis是線程安全的(由於只有一個線程),其全部操做都是原子的,不會因併發產生數據異常緩存
Redis的速度很是快(由於使用非阻塞式IO,且大部分命令的算法時間複雜度都是O(1))安全
使用高耗時的Redis命令是很危險的,會佔用惟一的一個線程的大量處理時間,致使全部的請求都被拖慢。(例如時間複雜度爲O(N)的KEYS命令,嚴格禁止在生產環境中使用)性能優化
本節中將介紹Redis支持的主要數據結構,以及相關的經常使用Redis命令。本節只對Redis命令進行扼要的介紹,且只列出了較經常使用的命令。若是想要了解完整的Redis命令集,或瞭解某個命令的詳細使用方法,請參考官方文檔:https://redis.io/commands網絡
Redis採用Key-Value型的基本數據結構,任何二進制序列均可以做爲Redis的Key使用(例如普通的字符串或一張JPEG圖片)
關於Key的一些注意事項:
不要使用過長的Key。例如使用一個1024字節的key就不是一個好主意,不只會消耗更多的內存,還會致使查找的效率下降
Key短到缺失了可讀性也是很差的,例如」u1000flw」比起」user:1000:followers」來講,節省了寥寥的存儲空間,卻引起了可讀性和可維護性上的麻煩
最好使用統一的規範來設計Key,好比」object-type:id:attr」,以這一規範設計出的Key多是」user:1000」或」comment:1234:reply-to」
Redis容許的最大Key長度是512MB(對Value的長度限制也是512MB)
String是Redis的基礎數據類型,Redis沒有Int、Float、Boolean等數據類型的概念,全部的基本類型在Redis中都以String體現。
與String相關的經常使用命令:
SET:爲一個key設置value,能夠配合EX/PX參數指定key的有效期,經過NX/XX參數針對key是否存在的狀況進行區別操做,時間複雜度O(1)
GET:獲取某個key對應的value,時間複雜度O(1)
GETSET:爲一個key設置value,並返回該key的原value,時間複雜度O(1)
MSET:爲多個key設置value,時間複雜度O(N)
MSETNX:同MSET,若是指定的key中有任意一個已存在,則不進行任何操做,時間複雜度O(N)
MGET:獲取多個key對應的value,時間複雜度O(N)
上文提到過,Redis的基本數據類型只有String,但Redis能夠把String做爲整型或浮點型數字來使用,主要體如今INCR、DECR類的命令上:
INCR:將key對應的value值自增1,並返回自增後的值。只對能夠轉換爲整型的String數據起做用。時間複雜度O(1)
INCRBY:將key對應的value值自增指定的整型數值,並返回自增後的值。只對能夠轉換爲整型的String數據起做用。時間複雜度O(1)
DECR/DECRBY:同INCR/INCRBY,自增改成自減。
INCR/DECR系列命令要求操做的value類型爲String,並能夠轉換爲64位帶符號的整型數字,不然會返回錯誤。
也就是說,進行INCR/DECR系列命令的value,必須在[-2^63 ~ 2^63 - 1]範圍內。
前文提到過,Redis採用單線程模型,自然是線程安全的,這使得INCR/DECR命令能夠很是便利的實現高併發場景下的精確控制。
例1:庫存控制
在高併發場景下實現庫存餘量的精準校驗,確保不出現超賣的狀況。
設置庫存總量:
SET inv:remain "100"
庫存扣減+餘量校驗:
DECR inv:remain
當DECR命令返回值大於等於0時,說明庫存餘量校驗經過,若是返回小於0的值,則說明庫存已耗盡。
假設同時有300個併發請求進行庫存扣減,Redis可以確保這300個請求分別獲得99到-200的返回值,每一個請求獲得的返回值都是惟一的,絕對不會找出現兩個請求獲得同樣的返回值的狀況。
例2:自增序列生成
實現相似於RDBMS的Sequence功能,生成一系列惟一的序列號
設置序列起始值:
SET sequence "10000"
獲取一個序列值:
INCR sequence
直接將返回值做爲序列使用便可。
獲取一批(如100個)序列值:
INCRBY sequence 100
假設返回值爲N,那麼[N - 99 ~ N]的數值都是可用的序列值。
當多個客戶端同時向Redis申請自增序列時,Redis可以確保每一個客戶端獲得的序列值或序列範圍都是全局惟一的,絕對不會出現不一樣客戶端獲得了重複的序列值的狀況。
Redis的List是鏈表型的數據結構,可使用LPUSH/RPUSH/LPOP/RPOP等命令在List的兩端執行插入元素和彈出元素的操做。雖然List也支持在特定index上插入和讀取元素的功能,但其時間複雜度較高(O(N)),應當心使用。
與List相關的經常使用命令:
LPUSH:向指定List的左側(即頭部)插入1個或多個元素,返回插入後的List長度。時間複雜度O(N),N爲插入元素的數量
RPUSH:同LPUSH,向指定List的右側(即尾部)插入1或多個元素
LPOP:從指定List的左側(即頭部)移除一個元素並返回,時間複雜度O(1)
RPOP:同LPOP,從指定List的右側(即尾部)移除1個元素並返回
LPUSHX/RPUSHX:與LPUSH/RPUSH相似,區別在於,LPUSHX/RPUSHX操做的key若是不存在,則不會進行任何操做
LLEN:返回指定List的長度,時間複雜度O(1)
LRANGE:返回指定List中指定範圍的元素(雙端包含,即LRANGE key 0 10會返回11個元素),時間複雜度O(N)。應儘量控制一次獲取的元素數量,一次獲取過大範圍的List元素會致使延遲,同時對長度不可預知的List,避免使用LRANGE key 0 -1這樣的完整遍歷操做。
應謹慎使用的List相關命令:
LINDEX:返回指定List指定index上的元素,若是index越界,返回nil。index數值是迴環的,即-1表明List最後一個位置,-2表明List倒數第二個位置。時間複雜度O(N)
LSET:將指定List指定index上的元素設置爲value,若是index越界則返回錯誤,時間複雜度O(N),若是操做的是頭/尾部的元素,則時間複雜度爲O(1)
LINSERT:向指定List中指定元素以前/以後插入一個新元素,並返回操做後的List長度。若是指定的元素不存在,返回-1。若是指定key不存在,不會進行任何操做,時間複雜度O(N)
因爲Redis的List是鏈表結構的,上述的三個命令的算法效率較低,須要對List進行遍歷,命令的耗時沒法預估,在List長度大的狀況下耗時會明顯增長,應謹慎使用。
換句話說,Redis的List實際是設計來用於實現隊列,而不是用於實現相似ArrayList這樣的列表的。若是你不是想要實現一個雙端出入的隊列,那麼請儘可能不要使用Redis的List數據結構。
爲了更好支持隊列的特性,Redis還提供了一系列阻塞式的操做命令,如BLPOP/BRPOP等,可以實現相似於BlockingQueue的能力,即在List爲空時,阻塞該鏈接,直到List中有對象能夠出隊時再返回。針對阻塞類的命令,此處不作詳細探討,請參考官方文檔(https://redis.io/topics/data-types-intro) 中」Blocking operations on lists」一節。
Hash即哈希表,Redis的Hash和傳統的哈希表同樣,是一種field-value型的數據結構,能夠理解成將HashMap搬入Redis。
Hash很是適合用於表現對象類型的數據,用Hash中的field對應對象的field便可。
Hash的優勢包括:
能夠實現二元查找,如」查找ID爲1000的用戶的年齡」
比起將整個對象序列化後做爲String存儲的方法,Hash可以有效地減小網絡傳輸的消耗
當使用Hash維護一個集合時,提供了比List效率高得多的隨機訪問命令
與Hash相關的經常使用命令:
HSET:將key對應的Hash中的field設置爲value。若是該Hash不存在,會自動建立一個。時間複雜度O(1)
HGET:返回指定Hash中field字段的值,時間複雜度O(1)
HMSET/HMGET:同HSET和HGET,能夠批量操做同一個key下的多個field,時間複雜度:O(N),N爲一次操做的field數量
HSETNX:同HSET,但如field已經存在,HSETNX不會進行任何操做,時間複雜度O(1)
HEXISTS:判斷指定Hash中field是否存在,存在返回1,不存在返回0,時間複雜度O(1)
HDEL:刪除指定Hash中的field(1個或多個),時間複雜度:O(N),N爲操做的field數量
HINCRBY:同INCRBY命令,對指定Hash中的一個field進行INCRBY,時間複雜度O(1)
應謹慎使用的Hash相關命令:
HGETALL:返回指定Hash中全部的field-value對。返回結果爲數組,數組中field和value交替出現。時間複雜度O(N)
HKEYS/HVALS:返回指定Hash中全部的field/value,時間複雜度O(N)
上述三個命令都會對Hash進行完整遍歷,Hash中的field數量與命令的耗時線性相關,對於尺寸不可預知的Hash,應嚴格避免使用上面三個命令,而改成使用HSCAN命令進行遊標式的遍歷,具體請見 https://redis.io/commands/scan
Redis Set是無序的,不可重複的String集合。
與Set相關的經常使用命令:
SADD:向指定Set中添加1個或多個member,若是指定Set不存在,會自動建立一個。時間複雜度O(N),N爲添加的member個數
SREM:從指定Set中移除1個或多個member,時間複雜度O(N),N爲移除的member個數
SRANDMEMBER:從指定Set中隨機返回1個或多個member,時間複雜度O(N),N爲返回的member個數
SPOP:從指定Set中隨機移除並返回count個member,時間複雜度O(N),N爲移除的member個數
SCARD:返回指定Set中的member個數,時間複雜度O(1)
SISMEMBER:判斷指定的value是否存在於指定Set中,時間複雜度O(1)
SMOVE:將指定member從一個Set移至另外一個Set
慎用的Set相關命令:
SMEMBERS:返回指定Hash中全部的member,時間複雜度O(N)
SUNION/SUNIONSTORE:計算多個Set的並集並返回/存儲至另外一個Set中,時間複雜度O(N),N爲參與計算的全部集合的總member數
SINTER/SINTERSTORE:計算多個Set的交集並返回/存儲至另外一個Set中,時間複雜度O(N),N爲參與計算的全部集合的總member數
SDIFF/SDIFFSTORE:計算1個Set與1或多個Set的差集並返回/存儲至另外一個Set中,時間複雜度O(N),N爲參與計算的全部集合的總member數。
上述幾個命令涉及的計算量大,應謹慎使用,特別是在參與計算的Set尺寸不可知的狀況下,應嚴格避免使用。能夠考慮經過SSCAN命令遍歷獲取相關Set的所有member(具體請見 https://redis.io/commands/scan ),若是須要作並集/交集/差集計算,能夠在客戶端進行,或在不服務實時查詢請求的Slave上進行。
Redis Sorted Set是有序的、不可重複的String集合。Sorted Set中的每一個元素都須要指派一個分數(score),Sorted Set會根據score對元素進行升序排序。若是多個member擁有相同的score,則以字典序進行升序排序。
Sorted Set很是適合用於實現排名。
Sorted Set的主要命令:
ZADD:向指定Sorted Set中添加1個或多個member,時間複雜度O(Mlog(N)),M爲添加的member數量,N爲Sorted Set中的member數量
ZREM:從指定Sorted Set中刪除1個或多個member,時間複雜度O(Mlog(N)),M爲刪除的member數量,N爲Sorted Set中的member數量
ZCOUNT:返回指定Sorted Set中指定score範圍內的member數量,時間複雜度:O(log(N))
ZCARD:返回指定Sorted Set中的member數量,時間複雜度O(1)
ZSCORE:返回指定Sorted Set中指定member的score,時間複雜度O(1)
ZRANK/ZREVRANK:返回指定member在Sorted Set中的排名,ZRANK返回按升序排序的排名,ZREVRANK則返回按降序排序的排名。時間複雜度O(log(N))
ZINCRBY:同INCRBY,對指定Sorted Set中的指定member的score進行自增,時間複雜度O(log(N))
慎用的Sorted Set相關命令:
ZRANGE/ZREVRANGE:返回指定Sorted Set中指定排名範圍內的全部member,ZRANGE爲按score升序排序,ZREVRANGE爲按score降序排序,時間複雜度O(log(N)+M),M爲本次返回的member數
ZRANGEBYSCORE/ZREVRANGEBYSCORE:返回指定Sorted Set中指定score範圍內的全部member,返回結果以升序/降序排序,min和max能夠指定爲-inf和+inf,表明返回全部的member。時間複雜度O(log(N)+M)
ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除Sorted Set中指定排名範圍/指定score範圍內的全部member。時間複雜度O(log(N)+M)
上述幾個命令,應儘可能避免傳遞[0 -1]或[-inf +inf]這樣的參數,來對Sorted Set作一次性的完整遍歷,特別是在Sorted Set的尺寸不可預知的狀況下。能夠經過ZSCAN命令來進行遊標式的遍歷(具體請見 https://redis.io/commands/scan ),或經過LIMIT參數來限制返回member的數量(適用於ZRANGEBYSCORE和ZREVRANGEBYSCORE命令),以實現遊標式的遍歷。
Redis的這兩種數據結構相較以前的並不經常使用,在本文中只作簡要介紹,如想要詳細瞭解這兩種數據結構與其相關的命令,請參考官方文檔https://redis.io/topics/data-types-intro 中的相關章節
Bitmap在Redis中不是一種實際的數據類型,而是一種將String做爲Bitmap使用的方法。能夠理解爲將String轉換爲bit數組。使用Bitmap來存儲true/false類型的簡單數據極爲節省空間。
HyperLogLogs是一種主要用於數量統計的數據結構,它和Set相似,維護一個不可重複的String集合,可是HyperLogLogs並不維護具體的member內容,只維護member的個數。也就是說,HyperLogLogs只能用於計算一個集合中不重複的元素數量,因此它比Set要節省不少內存空間。
其餘經常使用命令
EXISTS:判斷指定的key是否存在,返回1表明存在,0表明不存在,時間複雜度O(1)
DEL:刪除指定的key及其對應的value,時間複雜度O(N),N爲刪除的key數量
EXPIRE/PEXPIRE:爲一個key設置有效期,單位爲秒或毫秒,時間複雜度O(1)
TTL/PTTL:返回一個key剩餘的有效時間,單位爲秒或毫秒,時間複雜度O(1)
RENAME/RENAMENX:將key重命名爲newkey。使用RENAME時,若是newkey已經存在,其值會被覆蓋;使用RENAMENX時,若是newkey已經存在,則不會進行任何操做,時間複雜度O(1)
TYPE:返回指定key的類型,string, list, set, zset, hash。時間複雜度O(1)
CONFIG GET:得到Redis某配置項的當前值,可使用*通配符,時間複雜度O(1)
CONFIG SET:爲Redis某個配置項設置新值,時間複雜度O(1)
CONFIG REWRITE:讓Redis從新加載redis.conf中的配置
儘管Redis是一個很是快速的內存數據存儲媒介,也並不表明Redis不會產生性能問題。
前文中提到過,Redis採用單線程模型,全部的命令都是由一個線程串行執行的,因此當某個命令執行耗時較長時,會拖慢其後的全部命令,這使得Redis對每一個任務的執行效率更加敏感。
針對Redis的性能優化,主要從下面幾個層面入手:
最初的也是最重要的,確保沒有讓Redis執行耗時長的命令
使用pipelining將連續執行的命令組合執行
操做系統的Transparent huge pages功能必須關閉:
echo never > /sys/kernel/mm/transparent_hugepage/enabled若是在虛擬機中運行Redis,可能自然就有虛擬機環境帶來的固有延遲。能夠經過./redis-cli —intrinsic-latency 100命令查看固有延遲。同時若是對Redis的性能有較高要求的話,應儘量在物理機上直接部署Redis。
檢查數據持久化策略
考慮引入讀寫分離機制
Redis絕大多數讀寫命令的時間複雜度都在O(1)到O(N)之間,在文本和官方文檔中均對每一個命令的時間複雜度有說明。
一般來講,O(1)的命令是安全的,O(N)命令在使用時須要注意,若是N的數量級不可預知,則應避免使用。例如對一個field數未知的Hash數據執行HGETALL/HKEYS/HVALS命令,一般來講這些命令執行的很快,但若是這個Hash中的field數量極多,耗時就會成倍增加。
又如使用SUNION對兩個Set執行Union操做,或使用SORT對List/Set執行排序操做等時,都應該嚴加註意。
避免在使用這些O(N)命令時發生問題主要有幾個辦法:
不要把List當作列表使用,僅當作隊列來使用
經過機制嚴格控制Hash、Set、Sorted Set的大小
可能的話,將排序、並集、交集等操做放在客戶端執行
絕對禁止使用KEYS命令
避免一次性遍歷集合類型的全部成員,而應使用SCAN類的命令進行分批的,遊標式的遍歷
Redis提供了SCAN命令,能夠對Redis中存儲的全部key進行遊標式的遍歷,避免使用KEYS命令帶來的性能問題。同時還有SSCAN/HSCAN/ZSCAN等命令,分別用於對Set/Hash/Sorted Set中的元素進行遊標式遍歷。SCAN類命令的使用請參考官方文檔:https://redis.io/commands/scan
Redis提供了Slow Log功能,能夠自動記錄耗時較長的命令。相關的配置參數有兩個:
slowlog-log-slower-than xxxms #執行時間慢於xxx毫秒的命令計入Slow Log slowlog-max-len xxx #Slow Log的長度,即最大紀錄多少條Slow Log
使用SLOWLOG GET [number]命令,能夠輸出最近進入Slow Log的number條命令。
使用SLOWLOG RESET命令,能夠重置Slow Log
儘量使用長鏈接或鏈接池,避免頻繁建立銷燬鏈接
客戶端進行的批量數據操做,應使用Pipeline特性在一次交互中完成。具體請參照本文的Pipelining章節
Redis的數據持久化工做自己就會帶來延遲,須要根據數據的安全級別和性能要求制定合理的持久化策略:
AOF + fsync always的設置雖然可以絕對確保數據安全,但每一個操做都會觸發一次fsync,會對Redis的性能有比較明顯的影響
AOF + fsync every second是比較好的折中方案,每秒fsync一次
AOF + fsync never會提供AOF持久化方案下的最優性能
使用RDB持久化一般會提供比使用AOF更高的性能,但須要注意RDB的策略配置每一次RDB快照和AOF Rewrite都須要Redis主進程進行fork操做。fork操做自己可能會產生較高的耗時,與CPU和Redis佔用的內存大小有關。根據具體的狀況合理配置RDB快照和AOF Rewrite時機,避免過於頻繁的fork帶來的延遲
Redis在fork子進程時須要將內存分頁表拷貝至子進程,以佔用了24GB內存的Redis實例爲例,共須要拷貝24GB / 4kB * 8 = 48MB的數據。在使用單Xeon 2.27Ghz的物理機上,這一fork操做耗時216ms。
能夠經過INFO命令返回的latest_fork_usec字段查看上一次fork操做的耗時(微秒)
當Linux將Redis所用的內存分頁移至swap空間時,將會阻塞Redis進程,致使Redis出現不正常的延遲。Swap一般在物理內存不足或一些進程在進行大量I/O操做時發生,應儘量避免上述兩種狀況的出現。
/proc//smaps文件中會保存進程的swap記錄,經過查看這個文件,可以判斷Redis的延遲是否由Swap產生。若是這個文件中記錄了較大的Swap size,則說明延遲頗有多是Swap形成的。
當同一秒內有大量key過時時,也會引起Redis的延遲。在使用時應儘可能將key的失效時間錯開。
Redis的主從複製能力能夠實現一主多從的多節點架構,在這一架構下,主節點接收全部寫請求,並將數據同步給多個從節點。
在這一基礎上,咱們可讓從節點提供對實時性要求不高的讀請求服務,以減少主節點的壓力。
尤爲是針對一些使用了長耗時命令的統計類任務,徹底能夠指定在一個或多個從節點上執行,避免這些長耗時命令影響其餘請求的響應。
關於讀寫分離的具體說明,請參見後續章節
Redis支持一主多從的主從複製架構。一個Master實例負責處理全部的寫請求,Master將寫操做同步至全部Slave。
藉助Redis的主從複製,能夠實現讀寫分離和高可用:
實時性要求不是特別高的讀請求,能夠在Slave上完成,提高效率。特別是一些週期性執行的統計任務,這些任務可能須要執行一些長耗時的Redis命令,能夠專門規劃出1個或幾個Slave用於服務這些統計任務
藉助Redis Sentinel能夠實現高可用,當Master crash後,Redis Sentinel可以自動將一個Slave晉升爲Master,繼續提供服務
啓用主從複製很是簡單,只須要配置多個Redis實例,在做爲Slave的Redis實例中配置:
slaveof 192.168.1.1 6379 #指定Master的IP和端口
當Slave啓動後,會從Master進行一次冷啓動數據同步,由Master觸發BGSAVE生成RDB文件推送給Slave進行導入,導入完成後Master再將增量數據經過Redis Protocol同步給Slave。以後主從之間的數據便一直以Redis Protocol進行同步
Redis的主從複製功能自己只是作數據同步,並不提供監控和自動failover能力,要經過主從複製功能來實現Redis的高可用,還須要引入一個組件:Redis Sentinel
Redis Sentinel是Redis官方開發的監控組件,能夠監控Redis實例的狀態,經過Master節點自動發現Slave節點,並在監測到Master節點失效時選舉出一個新的Master,並向全部Redis實例推送新的主從配置。
Redis Sentinel須要至少部署3個實例才能造成選舉關係。
關鍵配置:
sentinel monitor mymaster 127.0.0.1 6379 2 #Master實例的IP、端口,以及選舉須要的同意票數 sentinel down-after-milliseconds mymaster 60000 #多長時間沒有響應視爲Master失效 sentinel failover-timeout mymaster 180000 #兩次failover嘗試間的間隔時長 sentinel parallel-syncs mymaster 1 #若是有多個Slave,能夠經過此配置指定同時重新Master進行數據同步的Slave數,避免全部Slave同時進行數據同步致使查詢服務也不可用
另外須要注意的是,Redis Sentinel實現的自動failover不是在同一個IP和端口上完成的,也就是說自動failover產生的新Master提供服務的IP和端口與以前的Master是不同的,因此要實現HA,還要求客戶端必須支持Sentinel,可以與Sentinel交互得到新Master的信息才行。
爲什麼要作集羣分片:
Redis中存儲的數據量大,一臺主機的物理內存已經沒法容納
Redis的寫請求併發量大,一個Redis實例以沒法承載
當上述兩個問題出現時,就必需要對Redis進行分片了。
Redis的分片方案有不少種,例如不少Redis的客戶端都自行實現了分片功能,也有向Twemproxy這樣的以代理方式實現的Redis分片方案。然而首選的方案還應該是Redis官方在3.0版本中推出的Redis Cluster分片方案。
本文不會對Redis Cluster的具體安裝和部署細節進行介紹,重點介紹Redis Cluster帶來的好處與弊端。
可以自動將數據分散在多個節點上
當訪問的key不在當前分片上時,可以自動將請求轉發至正確的分片
當集羣中部分節點失效時仍能提供服務
其中第三點是基於主從複製來實現的,Redis Cluster的每一個數據分片都採用了主從複製的結構,原理和前文所述的主從複製徹底一致,惟一的區別是省去了Redis Sentinel這一額外的組件,由Redis Cluster負責進行一個分片內部的節點監控和自動failover。
Redis Cluster中共有16384個hash slot,Redis會計算每一個key的CRC16,將結果與16384取模,來決定該key存儲在哪個hash slot中,同時須要指定Redis Cluster中每一個數據分片負責的Slot數。Slot的分配在任什麼時候間點均可以進行從新分配。
客戶端在對key進行讀寫操做時,能夠鏈接Cluster中的任意一個分片,若是操做的key不在此分片負責的Slot範圍內,Redis Cluster會自動將請求重定向到正確的分片上。
在基礎的分片原則上,Redis還支持hash tags功能,以hash tags要求的格式明明的key,將會確保進入同一個Slot中。例如:{uiv}user:1000和{uiv}user:1001擁有一樣的hash tag {uiv},會保存在同一個Slot中。
使用Redis Cluster時,pipelining、事務和LUA Script功能涉及的key必須在同一個數據分片上,不然將會返回錯誤。如要在Redis Cluster中使用上述功能,就必須經過hash tags來確保一個pipeline或一個事務中操做的全部key都位於同一個Slot中。
有一些客戶端(如Redisson)實現了集羣化的pipelining操做,能夠自動將一個pipeline裏的命令按key所在的分片進行分組,分別發到不一樣的分片上執行。可是Redis不支持跨分片的事務,事務和LUA Script仍是必須遵循全部key在一個分片上的規則要求。
在設計軟件架構時,要如何在主從複製和集羣分片兩種部署方案中取捨呢?
從各個方面看,Redis Cluster都是優於主從複製的方案
Redis Cluster可以解決單節點上數據量過大的問題
Redis Cluster可以解決單節點訪問壓力過大的問題
Redis Cluster包含了主從複製的能力
那是否是表明Redis Cluster永遠是優於主從複製的選擇呢?
並非。
軟件架構永遠不是越複雜越好,複雜的架構在帶來顯著好處的同時,必定也會帶來相應的弊端。採用Redis Cluster的弊端包括:
維護難度增長。在使用Redis Cluster時,須要維護的Redis實例數倍增,須要監控的主機數量也相應增長,數據備份/持久化的複雜度也會增長。同時在進行分片的增減操做時,還須要進行reshard操做,遠比主從模式下增長一個Slave的複雜度要高。
客戶端資源消耗增長。當客戶端使用鏈接池時,須要爲每個數據分片維護一個鏈接池,客戶端同時須要保持的鏈接數成倍增多,加大了客戶端自己和操做系統資源的消耗。
性能優化難度增長。你可能須要在多個分片上查看Slow Log和Swap日誌才能定位性能問題。
事務和LUA Script的使用成本增長。在Redis Cluster中使用事務和LUA Script特性有嚴格的限制條件,事務和Script中操做的key必須位於同一個分片上,這就使得在開發時必須對相應場景下涉及的key進行額外的規劃和規範要求。若是應用的場景中大量涉及事務和Script的使用,如何在保證這兩個功能的正常運做前提下把數據平均分到多個數據分片中就會成爲難點。
因此說,在主從複製和集羣分片兩個方案中作出選擇時,應該從應用軟件的功能特性、數據和訪問量級、將來發展規劃等方面綜合考慮,只在確實有必要引入數據分片時再使用Redis Cluster。
下面是一些建議:
須要在Redis中存儲的數據有多大?將來2年內可能發展爲多大?這些數據是否都須要長期保存?是否可使用LRU算法進行非熱點數據的淘汰?綜合考慮前面幾個因素,評估出Redis須要使用的物理內存。
用於部署Redis的主機物理內存有多大?有多少能夠分配給Redis使用?對比(1)中的內存需求評估,是否足夠用?
Redis面臨的併發寫壓力會有多大?在不使用pipelining時,Redis的寫性能能夠超過10萬次/秒(更多的benchmark能夠參考 https://redis.io/topics/benchmarks )
在使用Redis時,是否會使用到pipelining和事務功能?使用的場景多很少?
綜合上面幾點考慮,若是單臺主機的可用物理內存徹底足以支撐對Redis的容量需求,且Redis面臨的併發寫壓力距離Benchmark值還尚有距離,建議採用主從複製的架構,能夠省去不少沒必要要的麻煩。同時,若是應用中大量使用pipelining和事務,也建議儘量選擇主從複製架構,能夠減小設計和開發時的複雜度。
Redis的Java客戶端不少,官方推薦的有三種:Jedis、Redisson和lettuce。
在這裏對Jedis和Redisson進行對比介紹
Jedis:
輕量,簡潔,便於集成和改造
支持鏈接池
支持pipelining、事務、LUA Scripting、Redis Sentinel、Redis Cluster
不支持讀寫分離,須要本身實現
文檔差(真的不好,幾乎沒有……)
Redisson:
基於Netty實現,採用非阻塞IO,性能高
支持異步請求
支持鏈接池
支持pipelining、LUA Scripting、Redis Sentinel、Redis Cluster
不支持事務,官方建議以LUA Scripting代替事務
支持在Redis Cluster架構下使用pipelining
支持讀寫分離,支持讀負載均衡,在主從複製和Redis Cluster架構下均可以使用
內建Tomcat Session Manager,爲Tomcat 6/7/8提供了會話共享功能
能夠與Spring Session集成,實現基於Redis的會話共享
文檔較豐富,有中文文檔
對於Jedis和Redisson的選擇,一樣應遵循前述的原理,儘管Jedis比起Redisson有各類各樣的不足,但也應該在須要使用Redisson的高級特性時再選用Redisson,避免形成沒必要要的程序複雜度提高。
Jedis:
github:https://github.com/xetorthio/jedis
文檔:https://github.com/xetorthio/jedis/wiki
Redisson:github:https://github.com/redisson/redisson文檔:https://github.com/redisson/redisson/wiki