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

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

Redis源碼總代碼一萬多行。java

這篇文章有一些Redis 「常識」 http://www.searchdatabase.com.cn/showcontent_70423.htmnode

key能夠是任意類型,最後都存成byte[];做者建議用 : 分隔表名,用.做爲單詞間的鏈接。(據我所知,redis只有庫沒有表)mysql

 

針對KEY的操做:redis

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

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

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修飾符限制返回結果json

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 可能讀到了舊實例數據,立刻從新推送一遍配置,問題解決。

相關文章
相關標籤/搜索