基于內(nèi)存的NoSQL數(shù)據(jù)庫。提供五種數(shù)據(jù)結(jié)構(gòu)的存儲洒嗤。字符串箫荡、列表、集合渔隶、有序集合羔挡、散列表。Redis 支持很多特性派撕,例如將內(nèi)存中的數(shù)據(jù)持久化到硬盤中婉弹,使用復(fù)制來擴展讀性能睬魂,使用分片來擴展寫性能终吼。
Redis 適用場景
緩存
適用 Redis 作為緩存,將熱點數(shù)據(jù)放到內(nèi)存中氯哮。
消息隊列
Redis 的 List 類型是雙向鏈表际跪,很適合用于消息隊列商佛。
計數(shù)器
Redis 這種內(nèi)存數(shù)據(jù)庫才能支持計數(shù)器的頻繁讀寫操作。
好友關(guān)系
使用 set 類型的交集很容易就可以知道兩個用戶的共同好友姆打。
數(shù)據(jù)庫
單純作為一個讀取快的數(shù)據(jù)庫
數(shù)據(jù)類型
String
我們來看Redis中是如何定義字符串的
struct sdshdr {
//字符長度良姆,記錄buf數(shù)組中已使用的字節(jié)數(shù)量
unsigned int len;
//當(dāng)前可用空間,記錄buf數(shù)組中未使用的字節(jié)數(shù)量
unsigned int free;
//具體存放字符的buf
char buf[];
};
C語言中的字符表示幔戏,并不能滿足Redis對字符串在安全性玛追、效率以及功能方面的要求,具體體現(xiàn)在一下幾點:
- 獲取字符串長度的時間復(fù)雜度
在C語言中闲延,要獲取一個字符串的長度要對字符串進(jìn)行遍歷痊剖,其時間復(fù)雜度為O(N),而SDS本身記錄了字符串的長度即len屬性垒玲,所以獲取一個字符串的長度的實踐復(fù)雜度為O(1)陆馁,特別是Redis的使用環(huán)境中存在大量、頻繁的字符串操作合愈,如果每次都調(diào)用strlen將會嚴(yán)重影響系統(tǒng)性能叮贩。 - 緩沖區(qū)溢出
在C語言中,我們要對一個字符串進(jìn)行連接操作是佛析,很容易造成緩沖區(qū)溢出益老,而redis設(shè)置free變量會在連接操作的時候自動檢查空間是否足夠,不夠空間系統(tǒng)會自動分配 -
內(nèi)存分配與釋放
(1) 空間預(yù)分配:
當(dāng)對字符串進(jìn)行拼接操作時说莫,Redis不僅分配給滿足拼接操作所必要的空間杨箭,通常還會額外分配一定量的空間供下次拼接操作使用,避免每次拼接操作進(jìn)行過多的內(nèi)存重分配储狭。
(2) 分配原則:
如果操作后的字符串長度 < 1MB 互婿,則len的長度和free的長度一樣,也就是會額外的分配一倍的空間(具體為什么這么設(shè)定還有待考究)
如果操作后的字符串長度 >= 1MB辽狈,則Redis會分配額外的1MB未使用空間
(3) 惰性空間釋放:
在對字符串進(jìn)行縮減操作時慈参,Redis不會立即回收縮減掉的部分空間,而是使用free字段記錄下來刮萌,供下次使用驮配,同時,Redis也提供了相應(yīng)的API着茸,可以在需要的時候釋放掉這些空間壮锻,以免造成內(nèi)存浪費。
> set hello world
OK
> get hello
"world"
> del hello
(integer) 1
> get hello
(nil)
List
> rpush list-key item
(integer) 1
> rpush list-key item2
(integer) 2
> rpush list-key item
(integer) 3
> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"
> lindex list-key 1
"item2"
> lpop list-key
"item"
> lrange list-key 0 -1
1) "item2"
2) "item"
set
> sadd set-key item
(integer) 1
> sadd set-key item2
(integer) 1
> sadd set-key item3
(integer) 1
> sadd set-key item
(integer) 0
> smembers set-key
1) "item"
2) "item2"
3) "item3"
> sismember set-key item4
(integer) 0
> sismember set-key item
(integer) 1
> srem set-key item2
(integer) 1
> srem set-key item2
(integer) 0
> smembers set-key
1) "item"
2) "item3"
hash
> hset hash-key sub-key1 value1
(integer) 1
> hset hash-key sub-key2 value2
(integer) 1
> hset hash-key sub-key1 value1
(integer) 0
> hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"
> hdel hash-key sub-key2
(integer) 1
> hdel hash-key sub-key2
(integer) 0
> hget hash-key sub-key1
"value1"
> hgetall hash-key
1) "sub-key1"
2) "value1"
zset
> zadd zset-key 728 member1
(integer) 1
> zadd zset-key 982 member0
(integer) 1
> zadd zset-key 982 member0
(integer) 0
> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"
> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"
> zrem zset-key member1
(integer) 1
> zrem zset-key member1
(integer) 0
> zrange zset-key 0 -1 withscores
1) "member0"
2) "982"
鍵的過期時間
Redis 可以為每個鍵設(shè)置過期時間涮阔,當(dāng)鍵過期時猜绣,會自動刪除該鍵。
對于散列表這種容器敬特,只能為整個鍵設(shè)置過期時間(整個散列表)掰邢,而不能為鍵里面的單個元素設(shè)置過期時間牺陶。
過期時間對于清理緩存數(shù)據(jù)非常有用。
發(fā)布與訂閱
發(fā)布者和訂閱者都是Redis客戶端辣之,Channel則為Redis服務(wù)器端掰伸。發(fā)布者將消息發(fā)送到某個的頻道,訂閱了這個頻道的訂閱者就能接收到這條消息怀估。
發(fā)布與訂閱實際上是觀察者模式狮鸭,訂閱者訂閱了頻道之后,發(fā)布者向頻道發(fā)送字符串消息會被所有訂閱者接收到多搀。
(1)發(fā)送消息
Redis采用PUBLISH命令發(fā)送消息怕篷,其返回值為接收到該消息的訂閱者的數(shù)量。
(2)訂閱某個頻道
Redis采用SUBSCRIBE命令訂閱某個頻道酗昼,其返回值包括客戶端訂閱的頻道廊谓,目前已訂閱的頻道數(shù)量,以及接收到的消息麻削,其中subscribe表示已經(jīng)成功訂閱了某個頻道蒸痹。
(3)模式匹配
模式匹配功能允許客戶端訂閱符合某個模式的頻道,Redis采用PSUBSCRIBE訂閱符合某個模式所有頻道呛哟,用“”表示模式叠荠,“”可以被任意值代替。
(4)取消訂閱
Redis采用UNSUBSCRIBE和PUNSUBSCRIBE命令取消訂閱扫责,其返回值與訂閱類似榛鼎。
由于Redis的訂閱操作是阻塞式的,因此一旦客戶端訂閱了某個頻道或模式鳖孤,就將會一直處于訂閱狀態(tài)直到退出者娱。在SUBSCRIBE,PSUBSCRIBE苏揣,UNSUBSCRIBE和PUNSUBSCRIBE命令中黄鳍,其返回值都包含了該客戶端當(dāng)前訂閱的頻道和模式的數(shù)量,當(dāng)這個數(shù)量變?yōu)?時平匈,該客戶端會自動退出訂閱狀態(tài)框沟。
發(fā)布與訂閱有一些問題,很少使用它增炭,而是使用替代的解決方案忍燥。問題如下:
- 如果訂閱者讀取消息的速度很慢,會使得消息不斷積壓在發(fā)布者的輸出緩存區(qū)中隙姿,造成內(nèi)存占用過多梅垄;
- 如果訂閱者在執(zhí)行訂閱的過程中網(wǎng)絡(luò)出現(xiàn)問題,那么就會丟失斷線期間發(fā)送的所有消息孟辑。
Redis發(fā)布訂閱與ActiveMQ的比較
(1)ActiveMQ支持多種消息協(xié)議哎甲,包括AMQP,MQTT饲嗽,Stomp等炭玫,并且支持JMS規(guī)范,但Redis沒有提供對這些協(xié)議的支持貌虾;
(2)ActiveMQ提供持久化功能吞加,但Redis無法對消息持久化存儲,一旦消息被發(fā)送尽狠,如果沒有訂閱者接收衔憨,那么消息就會丟失;
(3)ActiveMQ提供了消息傳輸保障袄膏,當(dāng)客戶端連接超時或事務(wù)回滾等情況發(fā)生時践图,消息會被重新發(fā)送給客戶端,Redis沒有提供消息傳輸保障沉馆。
總之码党,ActiveMQ所提供的功能遠(yuǎn)比Redis發(fā)布訂閱要復(fù)雜,畢竟Redis不是專門做發(fā)布訂閱的斥黑,但是如果系統(tǒng)中已經(jīng)有了Redis揖盘,并且需要基本的發(fā)布訂閱功能,就沒有必要再安裝ActiveMQ了锌奴,因為可能ActiveMQ提供的功能大部分都用不到兽狭,而Redis的發(fā)布訂閱機制就能滿足需求。
redis事務(wù)
Redis事務(wù)允許在一次單獨的步驟中執(zhí)行一組命令鹿蜀,MULTI箕慧、EXEC、DISCARD和WATCH命令是Redis事務(wù)功能的基礎(chǔ)茴恰。
事務(wù)特性:
- Redis會將一個事務(wù)中的所有命令序列化销钝,然后按順序執(zhí)行。不可能在一個事務(wù)的執(zhí)行過程中插入其他命令琐簇。保證隔離性蒸健。
- Redis的一個事務(wù)要么全部執(zhí)行要么一點都不會執(zhí)行,保證原子性
- Redis借助AOF來將事務(wù)操作也持久化到日志文件中婉商,這樣就算在執(zhí)行事務(wù)的中途宕機似忧,重啟的時候也會先去日志中查找上次運行的事務(wù)情況,使用redis-check-aof工具將會從日志文件中刪除執(zhí)行不完全的事務(wù)丈秩。從2.2版本開始盯捌,除了上述兩項保證之外,Redis還能夠以樂觀鎖的形式提供更多的保證蘑秽,這種形式非常類似于“檢查再設(shè)置”(CAS:Check And Set)操作饺著。
事務(wù)命令:
- MULTI
用于標(biāo)記事務(wù)塊的開始箫攀。Redis會將后續(xù)的命令逐個放入隊列中,然后才能使用EXEC命令原子化地執(zhí)行這個命令序列幼衰。這個命令的返回值總是OK靴跛。 - EXEC
在一個事務(wù)中執(zhí)行所有先前放入隊列的命令,然后恢復(fù)正常的連接狀態(tài)渡嚣。當(dāng)使用WATCH命令時梢睛,只有當(dāng)受監(jiān)控的鍵沒有被修改時,EXEC命令才會執(zhí)行事務(wù)中的命令识椰,這種方式利用了檢查再設(shè)置(CAS)的機制绝葡。這個命令的返回值是一個數(shù)組,其中的每個元素分別是原子化事務(wù)中的每個命令的返回值腹鹉。當(dāng)使用WATCH命令時藏畅,如果事務(wù)執(zhí)行中止,那么EXEC命令就會返回一個Null值功咒。 - WATCH
當(dāng)某個事務(wù)需要按條件執(zhí)行時墓赴,就要使用這個命令將給定的鍵設(shè)置為受監(jiān)控的。
這個命令的運行格式如下所示:
WATCH key [key ...]
這個命令的返回值總是OK航瞭。
對于每個鍵來說诫硕,時間復(fù)雜度總是O(1)。 - DISCARD
清除所有先前在一個事務(wù)中放入隊列的命令刊侯,然后恢復(fù)正常的連接狀態(tài)章办。如果使用了WATCH命令,那么DISCARD命令就會將當(dāng)前連接監(jiān)控的所有鍵取消監(jiān)控滨彻。這個命令的返回值總是OK藕届。 - UNWATCH
清除所有先前為一個事務(wù)監(jiān)控的鍵。如果你調(diào)用了EXEC或DISCARD命令亭饵,那么就不需要手動調(diào)用UNWATCH命令休偶。時間復(fù)雜度總是O(1)。這個命令的返回值總是OK辜羊。
命令運行流程
使用MULTI命令便可以進(jìn)入一個Redis事務(wù)踏兜。此時,用戶可以發(fā)出多個Redis命令八秃。Redis會將這些命令放入隊列碱妆,而不是執(zhí)行這些命令。一旦調(diào)用EXEC命令昔驱,那么Redis就會執(zhí)行事務(wù)中的所有命令疹尾。相反,調(diào)用DISCARD命令將會清除事務(wù)隊列,然后退出事務(wù)纳本。
事務(wù)內(nèi)部錯誤處理
在redis2.6.5之后處理輸入事務(wù)命令過程中出現(xiàn)語法錯誤等問題時的解決方案是窍蓝,保留錯誤命令,不會中斷事務(wù)操作繁成,在用EXEC命令提交命令時吓笙,將所有命令交給服務(wù)器運行,運行過程中朴艰,即使遇到錯誤命令,其他命令也會繼續(xù)執(zhí)行混移。所以Redis不支持回滾祠墅,主要的原因是,命令失敗基本是命令語句錯誤歌径,也就是程序員開發(fā)時候犯的錯誤毁嗦,而此錯誤在生產(chǎn)環(huán)境下是不大可能的,所以redis沒必要支持回滾回铛。
事務(wù)丟棄
使用DISCARD命令之后狗准,終止事務(wù)運行。(在EXEC之前)
通過CAS實現(xiàn)樂觀鎖
Redis使用WATCH命令實現(xiàn)事務(wù)的“檢查再設(shè)置”(CAS)行為茵肃。WATCH命令可以被調(diào)用多次腔长。當(dāng)調(diào)用EXEC命令時,所有的鍵都會變?yōu)槲词鼙O(jiān)控的狀態(tài)验残。
持久化
RDB快照
Redis支持將當(dāng)前數(shù)據(jù)的快照存成一個二進(jìn)制數(shù)據(jù)文件的持久化機制捞附,即RDB快照。但是一個持續(xù)寫入的數(shù)據(jù)庫如何生成快照呢您没?Redis借助了fork命令的copy on write機制鸟召。在生成快照時,將當(dāng)前進(jìn)程fork出一個子進(jìn)程氨鹏,然后在子進(jìn)程中循環(huán)所有的數(shù)據(jù)欧募,將數(shù)據(jù)寫成為RDB文件。子進(jìn)程先將數(shù)據(jù)寫到臨時文件中(后綴不為RDB),寫完后通過操作系統(tǒng)rename命令(原子命令)將臨時文件后綴名改為.RDB仆抵。這樣在任何時候出現(xiàn)故障跟继,RDB文件都是可用的。
我們可以通過Redis的save指令來配置RDB快照生成的時機镣丑,比如配置10分鐘就生成快照还栓,也可以配置有1000次寫入就生成快照,也可以多個規(guī)則一起實施传轰。這些規(guī)則的定義就在Redis的配置文件中剩盒,你也可以通過Redis的CONFIG SET命令在Redis運行時設(shè)置規(guī)則,不需要重啟Redis。
將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本辽聊。
AOF
AOF日志的全稱是append only file纪挎,它是一個追加寫入的日志文件。與一般數(shù)據(jù)庫的binlog不同的是跟匆,AOF文件是可識別的純文本异袄,它的內(nèi)容就是一個個的Redis標(biāo)準(zhǔn)命令。并且只有那些會導(dǎo)致數(shù)據(jù)發(fā)生修改的命令才會追加到AOF文件玛臂。
AOF同步頻率
可以手動設(shè)置AOF緩存寫入到文件的同步頻率
always 選項會嚴(yán)重減低服務(wù)器的性能烤蜕;everysec 選項比較合適,可以保證系統(tǒng)奔潰時只會丟失一秒左右的數(shù)據(jù)迹冤,并且 Redis 每秒執(zhí)行一次同步對服務(wù)器性能幾乎沒有任何影響讽营;no 選項并不能給服務(wù)器性能帶來多大的提升,而且也會增加系統(tǒng)奔潰時數(shù)據(jù)丟失的數(shù)量泡徙。
AOF文件會越來越大橱鹏,所以Redis又提供了一個功能,叫做AOF rewrite堪藐。
- REWRITE:在主線程中重寫AOF莉兰,會阻塞工作線程,在生產(chǎn)環(huán)境中很少使用礁竞,處于廢棄狀態(tài)糖荒;
- BGREWRITE: 在后臺(子進(jìn)程)重寫AOF,不會阻塞工作線程,能正常服務(wù)模捂,此方法最常用
AOF BGREWRITE實現(xiàn)方式:
Server收到BGREWRITE命令或者系統(tǒng)觸發(fā)AOF重寫時寂嘉,主進(jìn)程Fork一個子進(jìn)程并進(jìn)行AOF重寫,重寫(復(fù)制)原AOF文件中的部分信息(不是全部)枫绅,主進(jìn)程異步等待子進(jìn)程結(jié)束(信號量)泉孩,此時主進(jìn)程能正常接收處理用戶請求,用戶請求會修改數(shù)據(jù)庫里數(shù)據(jù)并淋,會使得當(dāng)前數(shù)據(jù)庫的數(shù)據(jù)跟重寫后AOF里不一致寓搬,需要有種機制保證數(shù)據(jù)的一致性。當(dāng)前的做法是在重寫 AOF 期間系統(tǒng)會新開一塊內(nèi)存用于緩存重寫期間收到的命令(命令會被執(zhí)行再緩存)县耽,在重寫完成以后再將緩存中的命令追加到新的AOF句喷。在處理命令時既要將命令追加到 aof_buf(原AOF的buffer),也要追加到重寫AOF Buffer(重寫的AOF buffer)兔毙。最后再將重寫AOF Buffer寫入到新的AOF中唾琼。以上buf操作都是主線程來執(zhí)行,子線程只負(fù)責(zé)復(fù)制原AOF的工作澎剥。
AOF BGREWRITE存在的問題:
- 重寫AOF Buffer占用內(nèi)存
- 主線程在將AOF Buffer寫入到新AOF過程中會阻塞锡溯,影響用戶請求的運行。在緩存量大時會發(fā)生比較嚴(yán)重的問題。
解決方案:
官方的解決方案:
主進(jìn)程跟子進(jìn)程通過管道通信祭饭,主進(jìn)程實時將新寫入的數(shù)據(jù)發(fā)送給子進(jìn)程芜茵,子進(jìn)程從管道讀出數(shù)據(jù)緩存在buffer中(之前是主進(jìn)程寫入buffer,現(xiàn)在是由子線程寫)倡蝙,然后子進(jìn)程負(fù)責(zé)將buffer寫入新的AOF新的解決方案:
AOF重寫期間九串,主進(jìn)程創(chuàng)建一個新的aof_buf(兩個aof_buf),兩個aof_buf文件同時追加新寫入的命令寺鸥。當(dāng)主進(jìn)程收到子進(jìn)程重寫AOF文件完成后:
- 停止向舊的aof_buf猪钮,AOF文件追加命令;
- 刪除舊的的appendonly.aof.last文件胆建;
- 交換兩個aof_buf烤低,AOF文件指針游两;
- 回收舊的aof_buf,AOF文件;
- 重命令子進(jìn)程生成的AOF文件為appendonly.aof.last晃听;
新的解決方案的優(yōu)點在于赏表,可以減少更多的雙buffer寫入文件操作,舊的方案解決了主線程阻塞問題迎变,新方案在此基礎(chǔ)上還解決了內(nèi)存占用問題。
redis主從復(fù)制
- redis的復(fù)制功能是支持多個數(shù)據(jù)庫之間的數(shù)據(jù)同步。一類是主數(shù)據(jù)庫(master)一類是從數(shù)據(jù)庫(slave)沿侈,主數(shù)據(jù)庫可以進(jìn)行讀寫操作,當(dāng)發(fā)生寫操作的時候自動將數(shù)據(jù)同步到從數(shù)據(jù)庫市栗,而從數(shù)據(jù)庫一般是只讀的缀拭,并接收主數(shù)據(jù)庫同步過來的數(shù)據(jù),一個主數(shù)據(jù)庫可以有多個從數(shù)據(jù)庫填帽,而一個從數(shù)據(jù)庫只能有一個主數(shù)據(jù)庫蛛淋。
- 通過redis的復(fù)制功能可以很好的實現(xiàn)數(shù)據(jù)庫的讀寫分離,提高服務(wù)器的負(fù)載能力篡腌。主數(shù)據(jù)庫主要進(jìn)行寫操作褐荷,而從數(shù)據(jù)庫負(fù)責(zé)讀操作。
主從復(fù)制流程
- 當(dāng)一個從數(shù)據(jù)庫啟動時嘹悼,會向主數(shù)據(jù)庫發(fā)送sync命令叛甫,
- 主數(shù)據(jù)庫接收到sync命令后會開始在后臺保存快照(執(zhí)行rdb操作),并將保存期間接收到的命令緩存起來
- 當(dāng)快照完成后杨伙,redis會將快照文件和所有緩存的命令發(fā)送給從數(shù)據(jù)庫其监。
- 從數(shù)據(jù)庫收到后,會載入快照文件并執(zhí)行收到的緩存的命令限匣。
redis2.8之前的版本:當(dāng)主從數(shù)據(jù)庫同步的時候從數(shù)據(jù)庫因為網(wǎng)絡(luò)原因斷開重連后會重新執(zhí)行上述操作抖苦,不支持?jǐn)帱c續(xù)傳。redis2.8之后支持?jǐn)帱c續(xù)傳。
主從復(fù)制的設(shè)置
主服務(wù)器(master)
port 6000 //設(shè)置主服務(wù)器的對外端口
requirepass 123456 //設(shè)置密碼 要么不設(shè)置密碼睛约,要么主從全部設(shè)置密碼
在配置redis復(fù)制功能的時候如果主數(shù)據(jù)庫設(shè)置了密碼鼎俘,需要在從數(shù)據(jù)的配置文件中通過masterauth參數(shù)設(shè)置主數(shù)據(jù)庫的密碼,這樣從數(shù)據(jù)庫在連接主數(shù)據(jù)庫時就會自動使用auth命令認(rèn)證了辩涝。相當(dāng)于做了一個免密碼登錄贸伐。
從服務(wù)器slave1
port 6001
slaveof 127.0.0.1 6000 //通過使用 slaveof host port
masterauth 123456 //從服務(wù)器的密碼
requirepass 123456 //主服務(wù)器的密碼
從服務(wù)器slave2
port 6002
slaveof 127.0.0.1 6000 //通過使用 slaveof host port
masterauth 123456 //
requirepass 123456
之所以從服務(wù)器和主服務(wù)器的密碼設(shè)置成一樣是因為做主從交換,密碼一致會更方便怔揩。
redis-server redis.conf //啟動主機master
redis-server redis1.conf //啟動從機slave1
redis-server redis2.conf //啟動從機slave2
接下來驗證一下
master上:
[root@localhost master]# redis-cli -p 6000
127.0.0.1:6000> auth 123456
OK
127.0.0.1:6000> set test chenqm
OK
slave1上
[root@localhost slave2]# redis-cli -p 6001
127.0.0.1:6001> auth 123456
OK
127.0.0.1:6001> get test
"chenqm"
slave2上
[root@localhost slave2]# redis-cli -p 6002
127.0.0.1:6002> auth 123456
OK
127.0.0.1:6002> get test
"chenqm"
同步成功捉邢,此時讀寫分離也實現(xiàn)了。
注意
如果你使用主從復(fù)制商膊,那么要確保你的master激活了持久化伏伐,或者確保它不會在當(dāng)?shù)艉笞詣又貑ⅰT颍簊lave是master的完整備份晕拆,因此如果master通過一個空數(shù)據(jù)集重啟藐翎,slave也會被清掉。
sentinel(哨兵)
萬一主機掛了怎么辦实幕,這是個麻煩事情吝镣,所以redis提供了一個sentinel(哨兵),以此來實現(xiàn)主從切換的功能昆庇,類似與zookeeper.
//我們配置兩個sentinel進(jìn)程:
vim sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6000 2//這個后面的數(shù)字2,是指當(dāng)有兩個及以上的sentinel服務(wù)檢測到master宕機末贾,才會去執(zhí)行主從切換的功能。
sentinel auth-pass mymaster 123456
vim sentinel.conf
port 26479
sentinel monitor mymaster 127.0.0.1 6000 2
sentinel auth-pass mymaster 123456
redis-server sentinel.conf --sentinel //啟動sentinel服務(wù)
//查看日志
[7014] 11 Jan 19:42:30.918 # +monitor master mymaster 127.0.0.1 6000 quorum 2
[7014] 11 Jan 19:42:30.923 * +slave slave 127.0.0.1:6002 127.0.0.1 6002 @ mymaster 127.0.0.1 6000
[7014] 11 Jan 19:42:30.925 * +slave slave 127.0.0.1:6001 127.0.0.1 6002 @ mymaster 127.0.0.1 6000
//現(xiàn)在我們kill master
kill -9 6960
//觀察日志:
[7014] 11 Jan 19:43:41.463 # +sdown master mymaster 127.0.0.1 6000
[7014] 11 Jan 19:46:42.379 # +switch-master mymaster 127.0.0.1 6000 127.0.0.1 6001
master切換了整吆,當(dāng)6000端口的這個服務(wù)重啟的時候拱撵,他會變成6001端口服務(wù)的slave。因為sentinel在切換master的時候表蝙,會把對應(yīng)的sentinel.conf和redis.conf文件的配置修改拴测。期間我們還需要關(guān)注的一個問題:sentinel服務(wù)本身也不是萬能的,也會宕機府蛇,所以我們還得部署sentinel集群集索,像我這樣多啟動幾個sentinel。
除了監(jiān)控和自動故障轉(zhuǎn)移功能欲诺,Sentinel還有如下功能:當(dāng)被監(jiān)控的某個 Redis 服務(wù)器出現(xiàn)問題時抄谐, Redis Sentinel 可以向系統(tǒng)管理員發(fā)送通知, 也可以通過 API 向其他程序發(fā)送通知扰法。
主從鏈
隨著負(fù)載不斷上升蛹含,主服務(wù)器可能無法很快地更新所有從服務(wù)器,或者重新連接和重新同步從服務(wù)器將導(dǎo)致系統(tǒng)超載塞颁。為了解決這個問題浦箱,可以創(chuàng)建一個中間層來分擔(dān)主服務(wù)器的復(fù)制工作吸耿。中間層的服務(wù)器是最上層服務(wù)器的從服務(wù)器,又是最下層服務(wù)器的主服務(wù)器酷窥。
處理故障
當(dāng)主服務(wù)器出現(xiàn)故障時咽安,Redis 常用的做法是新開一臺服務(wù)器作為主服務(wù)器,具體步驟如下:假設(shè) A 為主服務(wù)器蓬推,B 為從服務(wù)器妆棒,當(dāng) A 出現(xiàn)故障時,讓 B 生成一個快照文件沸伏,將快照文件發(fā)送給 C糕珊,并讓 C 恢復(fù)快照文件的數(shù)據(jù)。最后毅糟,讓 B 成為 C 的從服務(wù)器红选。
分片
Redis 的分片承擔(dān)著兩個主要目標(biāo):
- 允許使用很多電腦的內(nèi)存總和來支持更大的數(shù)據(jù)庫。沒有分片姆另,你就被局限于單機能支持的內(nèi)存容量喇肋。
- 允許伸縮計算能力到多核或多服務(wù)器,伸縮網(wǎng)絡(luò)帶寬到多服務(wù)器或多網(wǎng)絡(luò)適配器迹辐。
分片算法
- 有很多不同的分片標(biāo)準(zhǔn)(criteria)蝶防。最簡單的執(zhí)行分片的方式之一是范圍分片(range partitioning),通過映射對象的范圍到指定的 Redis 實例來完成分片右核。例如慧脱,我可以假設(shè)用戶從 ID 0 到 ID 10000 進(jìn)入實例 R0渺绒,用戶從 ID 10001 到 ID 20000 進(jìn)入實例 R1贺喝,等等。這套辦法行得通宗兼,并且事實上在實踐中被采用躏鱼,然而,這有一個缺點殷绍,就是需要一個映射范圍到實例的表格染苛。這張表需要管理,不同類型的對象都需要一個表主到。
- 哈希分片(hash partitioning)茶行。這種模式適用于任何鍵。使用一個哈希函數(shù)(例如登钥,crc32 哈希函數(shù)) 將鍵名轉(zhuǎn)換為一個數(shù)字畔师。例如,如果鍵是 foobar牧牢,crc32(foobar)將會輸出類似于 93024922 的東西看锉。對這個數(shù)據(jù)進(jìn)行取模運算姿锭,以將其轉(zhuǎn)換為一個 0 到 16383 之間的數(shù)字,16384個哈希槽(slot)伯铣,16384個slot這樣這個數(shù)字就可以映射到我的 4 臺 Redis 實例之一呻此,4臺redis分別按一定比例分?jǐn)傔@16384個slot。這可以讓在集群中添加和移除節(jié)點非常容易腔寡。從一個節(jié)點向另一個節(jié)點移動哈希槽并不需要停止操作焚鲜,所以添加和移除節(jié)點,或者改變節(jié)點持有 的哈希槽百分比放前,都不需要任何停機時間(downtime)恃泪。
consistence hash 哈希一致性
一致性hash算法提出了在動態(tài)變化的Cache環(huán)境中,判定哈希算法好壞的四個定義:
- 平衡性(Balance):平衡性是指哈希的結(jié)果能夠盡可能分布到所有的緩沖中去犀斋,這樣可以使得所有的緩沖空間都得到利用贝乎。很多哈希算法都能夠滿足這一條件。
- 單調(diào)性(Monotonicity):單調(diào)性是指如果已經(jīng)有一些內(nèi)容通過哈希分派到了相應(yīng)的緩沖中叽粹,又有新的緩沖加入到系統(tǒng)中览效。哈希的結(jié)果應(yīng)能夠保證原有已分配的內(nèi)容可以被映射到原有的或者新的緩沖中去,而不會被映射到舊的緩沖集合中的其他緩沖區(qū)虫几。
- 分散性(Spread):在分布式環(huán)境中锤灿,終端有可能看不到所有的緩沖,而是只能看到其中的一部分辆脸。當(dāng)終端希望通過哈希過程將內(nèi)容映射到緩沖上時但校,由于不同終端所見的緩沖范圍有可能不同,從而導(dǎo)致哈希的結(jié)果不一致啡氢,最終的結(jié)果是相同的內(nèi)容被不同的終端映射到不同的緩沖區(qū)中状囱。這種情況顯然是應(yīng)該避免的,因為它導(dǎo)致相同內(nèi)容被存儲到不同緩沖中去倘是,降低了系統(tǒng)存儲的效率亭枷。分散性的定義就是上述情況發(fā)生的嚴(yán)重程度。好的哈希算法應(yīng)能夠盡量避免不一致的情況發(fā)生搀崭,也就是盡量降低分散性叨粘。
- 負(fù)載(Load):負(fù)載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內(nèi)容映射到不同的緩沖區(qū)中瘤睹,那么對于一個特定的緩沖區(qū)而言升敲,也可能被不同的用戶映射為不同 的內(nèi)容。與分散性一樣轰传,這種情況也是應(yīng)當(dāng)避免的驴党,因此好的哈希算法應(yīng)能夠盡量降低緩沖的負(fù)荷。
在分布式集群中绸吸,對機器的添加刪除鼻弧,或者機器故障后自動脫離集群這些操作是分布式集群管理最基本的功能设江。如果采用常用的hash(object)%N算法,那么在有機器添加或者刪除后攘轩,很多原有的數(shù)據(jù)就無法找到了叉存,這樣嚴(yán)重的違反了單調(diào)性原則。接下來主要講解一下一致性哈希算法是如何設(shè)計的:
環(huán)形Hash空間
按照常用的hash算法來將對應(yīng)的key哈希到一個具有232次方個桶的空間中度帮,即0~(232)-1的數(shù)字空間中〖吣螅現(xiàn)在我們可以將這些數(shù)字頭尾相連,想象成一個閉合的環(huán)形笨篷。如下圖
現(xiàn)在我們將object1瞳秽、object2、object3率翅、object4四個數(shù)據(jù)對象通過特定的Hash函數(shù)計算出對應(yīng)的key值练俐,然后散列到Hash環(huán)上。如下圖:
Hash(object1) = key1冕臭;
Hash(object2) = key2腺晾;
Hash(object3) = key3;
Hash(object4) = key4辜贵;
再采用一致性哈希算法的分布式集群中將新的機器加入悯蝉,其原理是通過使用與對象存儲一樣的Hash算法將機器也映射到環(huán)中(一般情況下對機器的hash計算是采用機器的IP或者機器唯一的別名作為輸入值),然后以順時針的方向計算托慨,將所有對象存儲到離自己最近的機器中鼻由。
假設(shè)現(xiàn)在有NODE1,NODE2厚棵,NODE3三臺機器蕉世,通過Hash算法得到對應(yīng)的KEY值,映射到環(huán)中窟感,其示意圖如下:
Hash(NODE1) = KEY1;
Hash(NODE2) = KEY2;
Hash(NODE3) = KEY3;
通過上圖可以看出對象與機器處于同一哈咸直耍空間中歉井,這樣按順時針轉(zhuǎn)動object1存儲到了NODE1中柿祈,object3存儲到了NODE2中,object2哩至、object4存儲到了NODE3中躏嚎。在這樣的部署環(huán)境中,hash環(huán)是不會變更的菩貌,因此卢佣,通過算出對象的hash值就能快速的定位到對應(yīng)的機器中,這樣就能找到對象真正的存儲位置了箭阶。
普通hash求余算法最為不妥的地方就是在有機器的添加或者刪除之后會照成大量的對象存儲位置失效虚茶,這樣就大大的不滿足單調(diào)性了戈鲁。下面來分析一下一致性哈希算法是如何處理的。
-
節(jié)點(機器)的刪除
以上面的分布為例嘹叫,如果NODE2出現(xiàn)故障被刪除了婆殿,那么按照順時針遷移的方法,object3將會被遷移到NODE3中罩扇,這樣僅僅是object3的映射位置發(fā)生了變化婆芦,其它的對象沒有任何的改動。如下圖:
-
節(jié)點(機器)的添加
如果往集群中添加一個新的節(jié)點NODE4喂饥,通過對應(yīng)的哈希算法得到KEY4消约,并映射到環(huán)中,如下圖:
通過按順時針遷移的規(guī)則员帮,那么object2被遷移到了NODE4中或粮,其它對象還保持這原有的存儲位置。通過對節(jié)點的添加和刪除的分析捞高,一致性哈希算法在保持了單調(diào)性的同時被啼,還是數(shù)據(jù)的遷移達(dá)到了最小,這樣的算法對分布式集群來說是非常合適的棠枉,避免了大量數(shù)據(jù)遷移浓体,減小了服務(wù)器的的壓力。
虛擬節(jié)點
一致性哈希算法滿足了單調(diào)性和負(fù)載均衡的特性以及一般hash算法的分散性辈讶,但這還并不能當(dāng)做其被廣泛應(yīng)用的原由命浴,因為還缺少了平衡性。即有可能很多數(shù)據(jù)都在某些節(jié)點集中分部贱除,沒有平均分布生闲。在一致性哈希算法中,為了盡可能的滿足平衡性月幌,其引入了虛擬節(jié)點碍讯。
“虛擬節(jié)點”( virtual node )是實際節(jié)點(機器)在 hash 空間的復(fù)制品( replica ),一個實際節(jié)點(機器)對應(yīng)了若干個“虛擬節(jié)點”扯躺,這個對應(yīng)個數(shù)也成為“復(fù)制個數(shù)”捉兴,“虛擬節(jié)點”在 hash 空間中以hash值排列。
根據(jù)上圖可知對象的映射關(guān)系:object1->NODE1-1录语,object2->NODE1-2倍啥,object3->NODE3-2,object4->NODE3-1澎埠。通過虛擬節(jié)點的引入虽缕,對象的分布就比較均衡了。
對象從hash到虛擬節(jié)點到實際節(jié)點的轉(zhuǎn)換如下圖:
“虛擬節(jié)點”的hash計算可以采用對應(yīng)節(jié)點的IP地址加數(shù)字后綴的方式蒲稳。例如假設(shè)NODE1的IP地址為192.168.1.100氮趋。引入“虛擬節(jié)點”前伍派,計算 cache A 的 hash 值:
Hash(“192.168.1.100”);
引入“虛擬節(jié)點”后,計算“虛擬節(jié)”點NODE1-1和NODE1-2的hash值:
Hash(“192.168.1.100#1”); // NODE1-1
Hash(“192.168.1.100#2”); // NODE1-2
Redis集群搭建
使用redis-server + .conf搭建
port 7000 //端口7000,7002,7003
bind 10.93.84.53 //默認(rèn)ip為127.0.0.1 需要改為其他節(jié)點機器可訪問的ip 否則創(chuàng)建集群時無法訪問對應(yīng)的端口剩胁,無法創(chuàng)建集群
daemonize yes //redis后臺運行
pidfile ./redis_7000.pid //pidfile文件對應(yīng)7000,7001,7002
cluster-enabled yes //開啟集群 把注釋#去掉
cluster-config-file nodes_7000.conf //集群的配置 配置文件首次啟動自動生成 7000,7001,7002
cluster-node-timeout 15000 //請求超時 默認(rèn)15秒拙已,可自行設(shè)置
appendonly yes //aof日志開啟 有需要就開啟,它會每次寫操作都記錄一條日志
分別啟動6個redis實例
./bin/redis-server cluster/conf/7000.conf
./bin/redis-server cluster/conf/7001.conf
./bin/redis-server cluster/conf/7002.conf
./bin/redis-server cluster/conf/7003.conf
./bin/redis-server cluster/conf/7004.conf
./bin/redis-server cluster/conf/7005.conf
使用redis-trib.rb搭建
Redis 3.0以上的集群方式是通過Redis安裝目錄下的bin/redis-trib.rb腳本搭建摧冀。這個腳本是用Ruby編寫的倍踪,所以需要安裝ruby環(huán)境。
ruby ./bin/redis-trib.rb create --replicas 1 10.93.84.53:7000 10.93.84.53:7001 10.93.84.53:7002 10.93.84.53:7003 10.93.84.53:7004 10.93.84.53:7005
--replicas 1表示為集群的master節(jié)點創(chuàng)建1個副本索昂。那么6個實例里建车,有三個master,有三個是slave椒惨。
查看集群狀態(tài)
./bin/redis-cli -h 10.93.84.53 -p 7000 -c //登錄集群缤至,-c標(biāo)識以集群方式登錄
10.93.84.53:7000> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:8
cluster_my_epoch:8
cluster_stats_messages_sent:215
cluster_stats_messages_received:215
10.93.84.53:7000> cluster nodes
942c9f97dc68198c39f425d13df0d8e3c40c5a58 10.93.84.53:7004 slave 5ac973bceab0d486c497345fe884ff54d1bb225a 0 1507806791940 5 connected
5ac973bceab0d486c497345fe884ff54d1bb225a 10.93.84.53:7001 master - 0 1507806788937 2 connected 5461-10922
a92a81532b63652bbd862be6f19a9bd8832e5e05 10.93.84.53:7005 slave cc46a4a1c0ec3f621b6b5405c6c10b7cffe73932 0 1507806790939 6 connected
cc46a4a1c0ec3f621b6b5405c6c10b7cffe73932 10.93.84.53:7002 master - 0 1507806789937 3 connected 10923-16383
6346ae8c7af7949658619fcf4021cc7aca454819 10.93.84.53:7000 myself,slave 92f62ec93a0550d962f81213ca7e9b3c9c996afd 0 0 1 connected
92f62ec93a0550d962f81213ca7e9b3c9c996afd 10.93.84.53:7003 master - 0 1507806792941 8 connected 0-5460
分片定位方式
- 客戶端分片(Client side partitioning)意味著,客戶端直接選擇正確的節(jié)點來寫入和讀取指定鍵康谆。許多 Redis 客戶端實現(xiàn)了客戶端分片领斥。
- 代理協(xié)助分片(Proxy assisted partitioning)意味著,我們的客戶端發(fā)送請求到一個可以理解 Redis 協(xié)議的代理上沃暗,而不是直接發(fā)送請求到 Redis 實例上月洛。代理會根據(jù)配置好的分片模式,來保證轉(zhuǎn)發(fā)我們的請求到正確的 Redis 實例孽锥,并返回響應(yīng)給客戶端嚼黔。Redis 和 Memcached 的代理 Twemproxy 實現(xiàn)了代理協(xié)助的分片。
- 客戶端分片惜辑、查詢路由(Query routing)意味著唬涧,你可以發(fā)送你的查詢到一個隨機實例,這個實例會保證轉(zhuǎn)發(fā)你的查詢到正確的節(jié)點盛撑。Redis 集群在客戶端的幫助下碎节,實現(xiàn)了查詢路由的一種混合形式 (請求不是直接從 Redis 實例轉(zhuǎn)發(fā)到另一個,而是客戶端收到重定向到正確的節(jié)點)抵卫。
集群通信
每個 Redis 集群節(jié)點需要兩個 TCP 連接打開狮荔。正常的 TCP 端口用來服務(wù)客戶端,稱作命令端口陌僵,例如 6379轴合。加 10000 的端口用作數(shù)據(jù)端口,服務(wù)集群總線(bus)碗短,使用點到點通信,ping-pong機制實現(xiàn)信道碧庹牵活偎谁。命令端口和集群總線端口的偏移量一直固定為 10000总滩。集群總線端口用于錯誤檢測,配置更新巡雨,故障轉(zhuǎn)移授權(quán)闰渔,節(jié)點之間通信等操作。
節(jié)點的 fail 是通過集群中超過半數(shù)的節(jié)點檢測失效時才生效.如果集群任意 master 掛掉,且當(dāng)前 master 沒有 slave.集群進(jìn)入 fail 狀態(tài)铐望。如果集群超過半數(shù)以上 master 掛掉冈涧,無論是否有 slave ,集群進(jìn)入 fail 狀態(tài)正蛙。當(dāng)集群不可用時,所有對集群的操作做都不可用督弓。
分片的缺點
- 涉及多個鍵的操作通常不支持(例如,你不能對映射在兩個不同 Redis 實例上的鍵執(zhí)行交集)乒验。涉及多個鍵的事務(wù)不能使用愚隧。
- 數(shù)據(jù)持久化和備份變得復(fù)雜
- 數(shù)據(jù)的添加刪除變得復(fù)雜
Twemproxy
Twemproxy 是 Twitter 開發(fā)的一個支持 Memcached ASCII 和 Redis 協(xié)議的代理。它是單線程的锻全,由 C 語言編寫狂塘,運行非常的快。他是基于 Apache 2.0 許可的開源項目鳄厌。內(nèi)部處理是無狀態(tài)的荞胡,它本身可以很輕松地集群,這樣可避免單點壓力或故障了嚎。
數(shù)據(jù)淘汰策略
可以設(shè)置內(nèi)存最大使用量硝训,當(dāng)內(nèi)存使用量超過時施行淘汰策略,具體有 6 種淘汰策略新思。
redis事件
文件事件:
服務(wù)器有許多套接字窖梁,事件產(chǎn)生時會對這些套接字進(jìn)行操作,服務(wù)器通過監(jiān)聽套接字來處理事件夹囚。常見的文件事件有:客戶端的連接事件纵刘;客戶端的命令請求事件;服務(wù)器向客戶端返回命令結(jié)果的事件荸哟。文件事件處理器使用I/O多路復(fù)用程序來同時監(jiān)聽多個套接字假哎,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事務(wù)處理器。文件事件處理器由四個部分組成:套接字鞍历、I/O多路復(fù)用程序舵抹、文件事件分派器以及事件處理器
時間事件:
又分為兩類:定時事件是讓一段程序在指定的時間之內(nèi)執(zhí)行一次;周期性時間是讓一段程序每隔指定時間就執(zhí)行一次劣砍。時間事件都放在一個無序鏈表中惧蛹,時間事件執(zhí)行器運行時,遍歷整個鏈表,查找所有已到達(dá)的時間事件香嗓,并調(diào)用相應(yīng)的事件處理器迅腔。
redis與memcached對比
Redis數(shù)據(jù)類型多
Memcached 僅支持字符串類型,而 Redis 支持五種不同種類的數(shù)據(jù)類型靠娱,使得它可以更靈活地解決問題沧烈。
redis支持?jǐn)?shù)據(jù)持久化
Redis 支持兩種持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化像云。
redis支持分布式
Memcached 不支持分布式锌雀,只能通過在客戶端使用像一致性哈希這樣的分布式算法來實現(xiàn)分布式存儲,這種方式在存儲和查詢時都需要先在客戶端計算一次數(shù)據(jù)所在的節(jié)點迅诬。用第三方代理工具Twemproxy都可以實現(xiàn)腋逆。
內(nèi)存管理機制
在 Redis 中,并不是所有數(shù)據(jù)都一直存儲在內(nèi)存中百框,可以將一些很久沒用的 value 交換swap到磁盤闲礼。而 Memcached 的數(shù)據(jù)則會一直在內(nèi)存中。
Memcached 將內(nèi)存分割成特定長度的塊來存儲數(shù)據(jù)铐维,以完全解決內(nèi)存碎片的問題柬泽,但是這種方式會使得內(nèi)存的利用率不高,例如塊的大小為 128 bytes嫁蛇,只存儲 100 bytes 的數(shù)據(jù)锨并,那么剩下的 28 bytes 就浪費掉了。
性能對比
由于Redis只使用單核睬棚,而Memcached可以使用多核第煮,所以平均每一個核上Redis在存儲小數(shù)據(jù)時比Memcached性能更高。而在100k以上的數(shù)據(jù)中抑党,Memcached性能要高于Redis包警,雖然Redis最近也在存儲大數(shù)據(jù)的性能上進(jìn)行優(yōu)化,但是比起Memcached底靠,還是稍有遜色害晦。