Redis cluster學習 & Redis常識 & sort操作


Redis中的5種數據類型String、Hash、List、Set、Sorted Set。

Redis源碼總代碼一萬多行。

這篇文章有一些Redis “常識” http://www.searchdatabase.com.cn/showcontent_70423.htm

key可以是任意類型,最后都存成byte[];作者建議用 : 分隔表名,用.作為單詞間的連接。(據我所知,redis只有庫沒有表)

 

針對KEY的操作:

命令 sort(按某個key從小到大排序,desc則是從大到小):

參考 http://www.cnblogs.com/linjiqin/archive/2013/06/14/3135921.html

10.117.146.16:8379> lpush price 30 1.5 10 8
(integer) 4
10.117.146.16:8379> sort price
1) "1.5"
2) "8"
3) "10"
4) "30"
10.117.146.16:8379> sort price desc
1) "30"
2) "10"
3) "8"
4) "1.5"

1.還可以使用alpha修飾符對字符串進行排序

2.使用limit修飾符限制返回結果

3.使用外部key進行排序

4.get有一個額外的參數規則,那就是可以用#獲取被排序鍵的值。

5.通過將一個不存在的鍵作為參數傳給 by 選項, 可以讓 sort 跳過排序操作,直接返回結果。

6.這種用法在單獨使用時,沒什么實際用處。不過,通過將這種用法和get選項配合,就可以在不排序的情況下,獲取多個外部鍵,相當於執行一個整合的獲取操作(類似於 sql數據庫的join關鍵字)。

保存排序結果

10.117.146.16:8379> sort price store ordered_price
(integer) 4
10.117.146.16:8379> lrange ordered_price 0 -1
1) "1.5"
2) "8"
3) "10"
4) "30"

返回值:
沒有使用 store 參數,返回列表形式的排序結果。
使用 store 參數,返回排序結果的元素數量。

其他命令還有:KEYS顯示所有的key,支持通配符 "KEYS a*" , "keys a?c",但不建議在生產環境大數據量下使用。

SORT,對集合按數字或字母順序排序后返回,或者存到另一個List,還可以關聯到外部Key等。因為會耗用CPU,有時會安排到slave上執行。

EXPIRE/EXPREAT/PERSIST/TTL/,關於Key超時的操作,默認以秒為單位,也有p字頭的以毫秒為單位的版本。 
其他命令: EXISTS,DEL,RENAME/RENAMENX(僅當new key不存在時),MOVE/MIGRATE(實例內從此db到彼db/從此實例到彼實例),
RANDOMKEY,TYPE/Object(Key的類型/對象編碼類型,空置時間),DUMP/RESTORE(value值的持久化)

 

2.2 String   

最普通的key-value,除了支持最基本的get/set, Redis也很喜歡添加一些簡便的指令,在服務端做起來是舉手之勞,客戶端便方便很多。
incr/decr/incrby/incrbyfloat, 如果key還不存在時創建key並設原值為0。

setEx/pSetEx, Set + Expire 的簡便寫法,p字頭以毫秒為單位。

setNx, key不存在時才put進去。

getset, 設置新值,返回舊值。

mget/mset/msetex, 一次get/set多個key。

getbit/setbit/bitop/bitcount bitmap玩法,比如統計今天的訪問用戶,每個用戶有一個offset,今天進來的話就把那個位為1。 
append/setrange/getrange,只對特定的數據格式比如字段定長的有用,json格式就沒用。

 

2.3 Hash

2.4 List   

Redis里可以當雙向鏈表來用,還提供blocking版本的pop函數,可以當Message Queue來用。

不過List並沒有JMS的ack機制,如果消費者把job給Pop走了又沒處理完就死機了怎么辦? 
解決方法之一是加多一個sorted set,以分發時間為score,用戶把job做完了之后要去消掉它。    除了List標准的雙向POP/PUSH外,還支持對隊列內容的直接操作,比如LREM/LSET/LINSERT/LINDEX。    另外經常用LTRIM限制List的大小,比如只保留最新的20條消息。LRANGE不同於POP直接彈走元素,只是返回列表內一段下標的元素。LLEN獲取列表的長度。

 


2.5 Set   

Set就是Set,還提供一些交集,並集,差集的集合操作。   

 

2.6 Sorted Set   

有序集,元素放入集合的時候要同時提供該元素的分數。
ZRANGE/ZREVRANGE 按排名的上下限返回元素,正數與倒數。

ZRANGEBYSCORE/ZREVRANGEBYSCORE 按分數的上下限返回元素,正數與倒數。

ZREMRANGEBYRANK/ZREMRANGEBYSCORE 按排名/按分數刪除元素。

ZCOUNT 統計分數上下限之間的元素個數。

ZRANK/ZREVRANK 顯示某個元素的正倒序的排名。

ZSCORE/ZINCRBY 顯示元素的分數/增加元素的分數。

ZADD/ZREM/ZCARD/ZINTERSTORE/ZUNIONSTORE 集合操作與SET相同,少了個差集的操作。

 

2.7 事務   

用Multi/Exec/Discard實現, 隔離級別是這邊事務一天不提交,那邊另一個事務還是看到舊的值。

還有個Watch指令,起到CAS的效果,如果事務提交時,Key的值已被別的事務改變,事務會被打斷。


2.8 Lua Script   

Redis2.6內置的Lua Script支持,可以在Redis的Server端一次過運行大量邏輯。
整個Script默認是在一個事務里的。 Script里涉及的所有Key盡量用變量,從外面傳入,使Redis一開始就知道你要改變哪些key。

EVAL每次傳輸一整段Script比較費帶寬,可以先用SCRIPT LOAD載入script,返回哈希值。然后用EVALHASH執行。

內置的LUA庫里還很貼心的帶了CJSON,可以處理JSON字符串。

 


3. 性能

速度太快了,用光了帶寬也測不出極限。

如果是本地socket直連,incr可以去到很嚇人的幾十萬TPS。

普通的get/set操作,經過了LAN,延時也只有1毫秒左右,可以放心使用,不用像調用REST接口和訪問數據庫那樣,多一次外部訪問都心痛。

自帶的redis-benchmark默認只是基於一個很小的數據集進行測試,
但可調整命令行參數如 redis-benchmark -r 10000000 -n 10000000 -d 128 -t SET,GET 就可以默認開50條線程,
SET 6M條左右(random)key是21字節長,value是128字節長的數據進去, 再GET出來。 如果要一次發送多條指令,PipeLining模式能讓性能更快,因為它在設計上正視了網絡往返的時間。 更快的是Lua Script模式,還可以包含邏輯,直接在服務端又get又set的 (見2.
8 Lua Script)。 單線程單CPU架構,但作者認為CPU不是瓶頸,內存與網絡帶寬才是。

 

發現執行緩慢的命令,可配置執行超過多少時間的指令算是緩慢指令(默認10毫秒,不含IO時間),可以用slowlog get 指令查看(默認只保留最后的128條)。單線程的模型下,某個請求占掉10毫秒是件大事情。


4. 容量   

最大內存: 一定要設置最大內存,否則物理內存用爆了就會大量使用Swap,寫RDB文件時的速度慢得你想死。

多留一倍內存是最安全的。重寫AOF文件和RDB文件的進程(即使不做持久化,復制到Slave的時候也要寫RDB)會fork出一條新進程來,
采用了操作系統的Copy
-On-Write策略(如果父進程的內存沒被修改,子進程與父進程共享Page。如果父進程的Page被修改, 會復制一份改動前的內容給新進程), 留意Console打出來的報告,如"RDB: 1215 MB of memory used by copy-on-write"。在系統極度繁忙時,
如果父進程的所有Page在子進程寫RDB過程中都被修改過了,就需要兩倍內存。 按照Redis啟動時的提醒,設置 vm.overcommit_memory
= 1 ,使得fork()一條10G的進程時,因為COW策略而不一定需要有10G的free memory. 當最大內存到達時,按照配置的Policy進行處理, 默認policy為volatile-lru, 對設置了expire time的key進行LRU清除(不是按實際expire time)。
如果沒有數據設置了expire time或者policy為noeviction,則直接報錯,但此時系統仍支持get之類的讀操作。
另外還有幾種policy,比如volatile-ttl按最接近expire time的,allkeys-lru對所有key都做LRU。 原來2.0版的VM(將Value放到磁盤,Key仍然放在內存),2.4版后又不支持了。

 


內存占用:

測試表明,stirng類型需要90字節的額外代價,就是說key1個字節,value一個字節時,還是需要占用92字節的長度,
而上述的benchmark的記錄就占用了239個字節。 用make 32bit可以編譯出32位的版本,每個指針占用的內存更小,但只支持最大4GB內存。

 


Sharding:

Jedis支持在客戶端做分區,局限是不能動態re-sharding, 有分區的master倒了,必須用slave頂上。要增加分區的話,呃.....

Redis-Cluster是今年工作重點,支持automatic re-sharding, 采用和Hazelcast類似的算法,總共有N個分區,每台Server負責若干個分區。
客戶端先hash出key 屬於哪個分區,然后發給負責這個分區的Server。Re-sharding時,會將某些分區的數據移到新的Server上,
然后各Server周知分區<->Server映射的變化,因為分區數量有限,所以通訊量不大。 在遷移過程中,原server對於已經遷移走的數據的get請求,
會回一個臨時轉向的應答。

 

5. 高可用性   

5.1 持久化

RDB文件: 整個內存的壓縮過的Snapshot,RDB的數據結構, 可以配置寫Snapshot的復合觸發條件,默認是60秒內改了1萬次或300秒內改了10次或900秒內改了1次。

RDB在寫入過程中,會連內存一起Fork出一個新進程,遍歷新進程內存中的數據寫RDB。 先寫到臨時文件再Rename,這樣外部程序對RDB文件的備份和傳輸過程是安全的。而且即使寫新快照的過程中Server被強制關掉了,舊的RDB文件還在。

可配置是否進行壓縮,方法是是字符串的LZF算法 和String形式的數字變回int形式存儲。

 

 

AOF文件:

append only的操作日志,等於mysql的binlog,記錄所有有效的寫操作,格式是明文的Redis協議的純文本文件。

一般配置成每秒調用一次fsync將數據寫到磁盤,壞處是操作系統非正常關機時,可能會丟1秒的數據。 如果設為fsync always,性能只剩幾百TPS,不用考慮。 如果使用了AOF,重啟時只會從AOF文件載入數據,不會管RDB文件。

AOF文件過大時,會fork出一條新進程來將文件重寫(也是先寫臨時文件再rename), 遍歷新進程的內存中數據,每條記錄有一條的Set語句。默認配置是當AOF文件大小是上次rewrite后的大小的一倍時觸發

Redis協議的內容,如set mykey hello, 將持久化成*3 $3 set $5 mykey $5 hello, 第一個數字代表這條語句有多少元,其他的數字代表后面字符串的長度。這樣的設計,使得即使在寫文件過程中突然關機導致文件不完整,也能自我修復,執行redis-check-aof即可。    

RDB不會實時寫入數據,而且如果同時使用兩者,但服務器重啟只會找AOF文件。那要不要只使用AOF呢?作者建議不要,因為RDB更適合用於備份數據庫,快速重啟,而且不會有AOF可能潛在的bug,留着作為一個萬一的手段。


讀寫性能:

AOF重寫和RDB寫入都是在fork出進程后,遍歷新進程內存順序寫的,既不影響主進程,順序寫的速度也比隨機寫快,在普通PC服務器上把剛才的1.5G數據寫成一個200M的RDB文件大約8秒, 啟動時載入一個1.4G的AOF文件大約13秒。

2.4版以后,lpush可以一次push多個值了,使得AOF重寫時可以將舊版本中的多個lpush指令合成一個。 有人建議設置no-appendfsync-on-rewrite 為 yes,aof rewrite時就不執行fsync了,先都存在內存里,減少IO資源爭用。 當然這樣會丟數據。 Fork一個使用了大量內存的進程也要時間,大約10ms per GB的樣子,各種系統的對比。

 

其他:

正確關閉服務器:redis-cli shutdown 或者 kill,都會graceful shutdown,保證寫RDB文件以及將AOF文件fsync到磁盤,不會丟失數據。

如果是Ctrl+C,或者kill -9 就會丟失數據。

執行指令bgsave 可觸發rdb存盤,bgrewriteaof可觸發aof重寫。

 


5.2 Master-Slave復制

可以在配置文件、命令行參數、以及執行SLAVEOF指令的來設置。 當前版本,一旦執行SlaveOF, slave會清掉自己的所有數據,執行一次全同步:Master要bgsave出自己的一個RDB文件,發給Slave。然后再增量同步: Master作為一個普通的client連入slave,將所有寫操作轉發給slave,沒有特殊的同步協議。

作者在2.8版本中將支持PSYNC部分同步 測試表明同步延時非常小。

有建議只在Slave上寫RDB和AOF文件,但這樣master啟動時就需要從slave copy文件,fail-over腳本也更復雜。只要有足夠內存,master平時IO也不高的話,還是簡化架構更好。

 

5.3 Fail-Over   

5.3.1 概述   

Redis-sentinel是2.6版開始加入的另一組獨立運行的節點, 提供自動Fail Over的支持。


每秒鍾對所有master,slave和其他sentinel執行ping,redis-server節點要應答+PONG或-LOADING或-MASTERDOWN.

如果某一台Sentinel沒有在30秒內(可配置得短一些哦)收到上述正確應答,它就會認為master處於sdown狀態(主觀Down) 
它向其他sentinel詢問是否也認為master倒了(SENTINEL
is-master-down-by-addr ), 如果quonum台(默認是2)sentinels在5秒鍾內都這樣認為,
就會認為master真是odown了(客觀Down)。 此時會選出一台sentinel作為Leader執行fail
-over, Leader會從slave中選出一個提升為master(執行slaveof none),這台slave必須狀態正常,
而且INFO顯示它與master的復制連接並沒有斷開太久。然后讓其他slave指向它(執行slaveof new master)。

 


5.3.2 master/slave 及 其他sentinels的發現   

master地址在sentinel的配置文件里, sentinel會每10秒一次向master發送INFO,知道master的slave有哪些。

如果master已經變為slave,sentiel會分析INFO的應答指向新的master。

 

 

所以當sentiel重啟時,它的配置文件里master的地址並沒變,那它仍然會去找old master,然后被redirect到新的master。

但如果old master還沒起來,或者old master沒把自己變成slave,悲劇就可能發生。   另外,sentinel會在master上建一個pub/sub channel,通告各種信息,比如+sdown/-sdown, 而且sentinel們也是通過接收pub/sub channel上的+sentinel的信息發現彼此,因為每台sentinel每5秒會發送一次__sentinel__:hello,宣告自己的存在。

自定義腳本和Client

5.3.3 自定義腳本 
sentinel在failover時還會執行配置文件里指定的用戶reconfig腳本,讓master變為slave並指向新的master。
腳本在如下時機被調用:
1. slave開始提升成master,
2.所有slave都已指向新master,
3.各種原因提升被終止。 腳本的將會在命令行按順序傳入如下參數: 腳本返回0是正常,如果返回1會被重新執行,如果返回2或以上不會。
如果超過60秒沒返回會被強制終止。   

另一種notify腳本在收到任何pub/sub信息時都會調用,讓你去通知O&M系統。   

5.3.4 Client集成   
client中執行語句SENTINEL get-master-addr-by-name mymaster 可獲得當前master的地址。
但是Jedis還沒集成sentinel,只有一個熱心網友提交了pull request   

淘寶的Tedis driver,使用了完全不同的思路,不基於Sentinel,而是多寫隨機讀,學術名詞是ReadOne-WriteAll-tx(see NoSQL數據庫的分布式算法),
一開始就同步寫入到所有節點,讀的話隨便讀一個還活着的節點就行了。(但節點死掉重新起來后怎么重新同步?什么時候可以重新作為一個可選的master?)  
Redis作者也在博客里抱怨怎么沒有人做Dynamo-style 的client。

監控技巧見: http://blog.nosqlfan.com/html/4166.html

SlowLog 檢查慢操作(見2.性能)

配置sentinel的notify.sh腳本對所有事件告警或自己用PING/INFO監控節點狀態(見5.3.3)

MONITOR可以顯示Server收到的所有指令,可以用於debug。 Redis live, 基於Python的DashBoard,使用INFO和MONITOR獲得系統情況和指令統計分析。 
Instagram的Redis Faina,基於Python,使用MONITOR對指令進行統計分析. Redis
-rdb-tools 基於Python,可以分析RDB文件,
比如每條Key對應value所占的大小,還可以將RDB dump成文本文件然后進行比較,還可以將RDB輸出成JSON格式。
Redis作者自己寫的Redis Sampler,基於Ruby,統計數據分布情況。

 


維護

用redis-check-aof/redis-check-dump 修復爛掉的文件。

在系統閑時調用 bgrewriteaof 重寫AOF文件。

其他   

過期數據清除 ,過期數據的清除從來不容易,Redis使用了一種相對務實的做法 當client主動get的時候會先進行判斷。

如果clien永遠都不再get那條key呢? 它會在后台,每秒10次的執行如下操作: 隨機選取100個key校驗是否過期,如果有25個以上的key過期了,
立刻隨機選取下100個key,可見如果數據不被主動get,它什么時候最終被清理掉是未知的。 上述主動清理只會在master進行,slave們會收到從master發過來的DEL指令,master的AOF文件也會插入一條DEL。

 

Java Driver   

各個Driver好像只有Jedis比較活躍。   

不需要Spring Data Redis的封裝,因為Jedis已足夠簡單,所以它沒有像對MongoDB java driver的封裝那樣能簡化代碼,
所謂屏蔽各種底層driver的差異並不太吸引人,因為我就沒打算選其他幾種driver。    Jedis基於Apache Commons Pool做的連接池,默認最大連接數只有8,一般需要重新設置。

 

 

下面這一篇講Redis Cluster的原理還比較清晰 http://www.cnblogs.com/foxmailed/p/3630875.html

  Redis Cluster 是Redis的集群實現,內置數據自動分片機制,集群內部將所有的key映射到16384個Slot中,
集群中的每個Redis Instance負責其中的一部分的Slot的讀寫。
集群客戶端連接集群中任一Redis Instance即可發送命令,當Redis Instance收到自己不負責的Slot的請求時,
會將負責請求Key所在Slot的Redis Instance地址返回給客戶端,客戶端收到后自動將原請求重新發往這個地址,對外部透明。
一個Key到底屬於哪個Slot由crc16(key) % 16384 決定。 關於負載均衡,集群的Redis Instance之間可以遷移數據,以Slot為單位,但不是自動的,需要外部命令觸發。 關於集群成員管理,集群的節點(Redis Instance)和節點之間兩兩定期交換集群內節點信息並且更新,從發送節點的角度看,這些信息包括:
集群內有哪些節點,IP和PORT是什么,節點名字是什么,節點的狀態(比如OK,PFAIL,FAIL,后面詳述)是什么,包括節點角色(master 或者 slave)等。 關於可用性,集群由N組主從Redis Instance組成。主可以沒有從,但是沒有從
意味着主宕機后主負責的Slot讀寫服務不可用。一個主可以有多個從,主宕機時,某個從會被提升為主,具體哪個從被提升為主,協議類似於Raft,參見這里
如何檢測主宕機?Redis Cluster采用quorum
+心跳的機制。從節點的角度看,節點會定期給其他所有的節點發送Ping,
cluster-node-timeout(可配置,秒級)時間內沒有收到對方的回復,則單方面認為對端節點宕機,將該節點標為PFAIL狀態。
通過節點之間交換信息收集到quorum個節點都認為這個節點為PFAIL,則將該節點標記為FAIL,
並且將其發送給其他所有節點,其他所有節點收到后立即認為該節點宕機。
從這里可以看出,主宕機后,至少cluster-node-timeout時間內該主所負責的Slot的讀寫服務不可用。

 

這里有一些使用Redis遇到的坑和經驗 http://www.360doc.com/content/16/0425/23/16915_553797555.shtml

疑似 Cluster 腦裂?(這個名稱好可怕。。)
腦裂在所謂的分布式系統中很常見,大家也不陌生,做為DBA最怕的就是Mysql keepalived 腦裂,造成主庫雙寫。難道 Redis Cluster中也會有腦裂么?
凌晨5點接到電話,發現應用看到數據不一致,偶爾是無數據,偶爾有數據,很像讀到了臟數據。
登上 Redis, Cluster Nodes, Cluster Config,確實發現不同 Redis 實例配置了不同的Cluster Nodes。
想起了昨天有對該集群遷移,下掉了幾個實例,但是在 PHP 配置端沒有推送配置,導致 PHP 可能讀到了舊實例數據,馬上重新推送一遍配置,問題解決。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM