
Redis簡介
Redis 是一個使用 C 語言編寫的,開源的(BSD許可)高性能非關(guān)系型(NoSQL)的鍵值對數(shù)據(jù)庫。
Redis 可以存儲鍵和五種不同類型的值之間的映射。鍵的類型只能為字符串,值支持五種數(shù)據(jù)類型:字符串、列表、集合、散列表、有序集合。
與傳統(tǒng)數(shù)據(jù)庫不同的是 Redis 的數(shù)據(jù)是存在內(nèi)存中的,所以讀寫速度非???,因此 redis 被廣泛應(yīng)用于緩存方向,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。另外,Redis 也經(jīng)常用來做分布式鎖。除此之外,Redis 支持事務(wù) 、持久化、LUA腳本、LRU驅(qū)動事件、多種集群方案。
從2010年3月15日起,Redis的開發(fā)工作由VMware主持。從2013年5月開始,Redis的開發(fā)由Pivotal贊助。
Redis的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 讀寫性能優(yōu)異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
- 支持?jǐn)?shù)據(jù)持久化,支持AOF和RDB兩種持久化方式。
- 支持事務(wù),Redis的所有操作都是原子性的,同時Redis還支持對幾個操作合并后的原子性執(zhí)行。
- 數(shù)據(jù)結(jié)構(gòu)豐富,除了支持string類型的value外還支持hash、set、zset、list等數(shù)據(jù)結(jié)構(gòu)。
- 支持主從復(fù)制,主機(jī)會自動將數(shù)據(jù)同步到從機(jī),可以進(jìn)行讀寫分離。
缺點(diǎn)
- 數(shù)據(jù)庫容量受到物理內(nèi)存的限制,不能用作海量數(shù)據(jù)的高性能讀寫,因此Redis適合的場景主要局限在較小數(shù)據(jù)量的高性能操作和運(yùn)算上。
- Redis 不具備自動容錯和恢復(fù)功能,主機(jī)從機(jī)的宕機(jī)都會導(dǎo)致前端部分讀寫請求失敗,需要等待機(jī)器重啟或者手動切換前端的IP才能恢復(fù)。
- 主機(jī)宕機(jī),宕機(jī)前有部分?jǐn)?shù)據(jù)未能及時同步到從機(jī),切換IP后還會引入數(shù)據(jù)不一致的問題,降低了系統(tǒng)的可用性。
- Redis 較難支持在線擴(kuò)容,在集群容量達(dá)到上限時在線擴(kuò)容會變得很復(fù)雜。為避免這一問題,運(yùn)維人員在系統(tǒng)上線時必須確保有足夠的空間,這對資源造成了很大的浪費(fèi)。
數(shù)據(jù)類型
| 數(shù)據(jù)類型 | 可以存儲的值 | 操作 |
|---|---|---|
| STRING | 字符串、整數(shù)或者浮點(diǎn)數(shù) | 對整個字符串或者字符串的其中一部分執(zhí)行操作<br />對整數(shù)和浮點(diǎn)數(shù)執(zhí)行自增或者自減操作 |
| LIST | 列表 | 從兩端壓入或者彈出元素<br />對單個或者多個元素進(jìn)行修剪,<br />只保留一個范圍內(nèi)的元素 |
| SET | 無序集合 | 添加、獲取、移除單個元素<br />檢查一個元素是否存在于集合中<br /> 計算交集、并集、差集<br />從集合里面隨機(jī)獲取元素 |
| HASH | 包含鍵值對的無序散列表 | 添加、獲取、移除單個鍵值對<br />獲取所有鍵值對<br /> 檢查某個鍵是否存在 |
| ZSET | 有序集合 | 添加、獲取、刪除元素<br />根據(jù)分值范圍或者成員來獲取元素<br /> 計算一個鍵的排名 |
STRING

> 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"
使用場景
計數(shù)器
可以對 String 進(jìn)行自增自減運(yùn)算,從而實現(xiàn)計數(shù)器功能。
Redis 這種內(nèi)存型數(shù)據(jù)庫的讀寫性能非常高,很適合存儲頻繁讀寫的計數(shù)量。
緩存
將熱點(diǎn)數(shù)據(jù)放到內(nèi)存中,設(shè)置內(nèi)存的最大使用量以及淘汰策略來保證緩存的命中率。
會話緩存
可以使用 Redis 來統(tǒng)一存儲多臺應(yīng)用服務(wù)器的會話信息。
當(dāng)應(yīng)用服務(wù)器不再存儲用戶的會話信息,也就不再具有狀態(tài),一個用戶可以請求任意一個應(yīng)用服務(wù)器,從而更容易實現(xiàn)高可用性以及可伸縮性。
全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC平臺。
以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
查找表
例如 DNS 記錄就很適合使用 Redis 進(jìn)行存儲。
查找表和緩存類似,也是利用了 Redis 快速的查找特性。但是查找表的內(nèi)容不能失效,而緩存的內(nèi)容可以失效,因為緩存不作為可靠的數(shù)據(jù)來源。
消息隊列(發(fā)布/訂閱功能)
List 是一個雙向鏈表,可以通過 lpush 和 rpop 寫入和讀取消息
不過最好使用 Kafka、RabbitMQ 等消息中間件。
分布式鎖實現(xiàn)
在分布式場景下,無法使用單機(jī)環(huán)境下的鎖來對多個節(jié)點(diǎn)上的進(jìn)程進(jìn)行同步。
可以使用 Redis 自帶的 SETNX 命令實現(xiàn)分布式鎖,除此之外,還可以使用官方提供的 RedLock 分布式鎖實現(xiàn)。
其它
Set 可以實現(xiàn)交集、并集等操作,從而實現(xiàn)共同好友等功能。
ZSet 可以實現(xiàn)有序性操作,從而實現(xiàn)排行榜等功能。
持久化
Redis 是內(nèi)存型數(shù)據(jù)庫,為了之后重用數(shù)據(jù)(比如重啟機(jī)器、機(jī)器故障之后回復(fù)數(shù)據(jù)),或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個遠(yuǎn)程位置,需要將內(nèi)存中的數(shù)據(jù)持久化到硬盤上。
Redis 提供了RDB和AOF兩種持久化方式。默認(rèn)是只開啟RDB,當(dāng)Redis重啟時,它會優(yōu)先使用AOF文件來還原數(shù)據(jù)集。
RDB 持久化(快照持久化)
RDB 持久化:將某個時間點(diǎn)的所有數(shù)據(jù)都存放到硬盤上。
可以將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本。如果系統(tǒng)發(fā)生故障,將會丟失最后一次創(chuàng)建快照之后的數(shù)據(jù)。如果數(shù)據(jù)量很大,保存快照的時間會很長。
快照持久化是Redis默認(rèn)采用的持久化方式,在redis.conf配置文件中默認(rèn)有此下配置:
#在900秒(15分鐘)之后,如果至少有1個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 900 1
#在300秒(5分鐘)之后,如果至少有10個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 300 10
#在60秒(1分鐘)之后,如果至少有10000個key發(fā)生變化,Redis就會自動觸發(fā)BGSAVE命令創(chuàng)建快照。
save 60 10000
根據(jù)配置,快照將被寫入dbfilename選項指定的文件里面,并存儲在dir選項指定的路徑上面。如果在新的快照文件創(chuàng)建完畢之前,Redis、系統(tǒng)或者硬件這三者中的任意一個崩潰了,那么Redis將丟失最近一次創(chuàng)建快照寫入的所有數(shù)據(jù)。
舉個例子:假設(shè)Redis的上一個快照是2:35開始創(chuàng)建的,并且已經(jīng)創(chuàng)建成功。下午3:06時,Redis又開始創(chuàng)建新的快照,并且在下午3:08快照創(chuàng)建完畢之前,有35個鍵進(jìn)行了更新。如果在下午3:06到3:08期間,系統(tǒng)發(fā)生了崩潰,導(dǎo)致Redis無法完成新快照的創(chuàng)建工作,那么Redis將丟失下午2:35之后寫入的所有數(shù)據(jù)。另一方面,如果系統(tǒng)恰好在新的快照文件創(chuàng)建完畢之后崩潰,那么Redis將丟失35個鍵的更新數(shù)據(jù)。
創(chuàng)建快照的辦法有如下幾種:
- BGSAVE命令: 客戶端向Redis發(fā)送 BGSAVE命令 來創(chuàng)建一個快照。對于支持BGSAVE命令的平臺來說(基本上所有平臺支持,除了Windows平臺),Redis會調(diào)用fork來創(chuàng)建一個子進(jìn)程,然后子進(jìn)程負(fù)責(zé)將快照寫入硬盤,而父進(jìn)程則繼續(xù)處理命令請求。
- SAVE命令: 客戶端還可以向Redis發(fā)送 SAVE命令 來創(chuàng)建一個快照,接到SAVE命令的Redis服務(wù)器在快照創(chuàng)建完畢之前不會再響應(yīng)任何其他命令。SAVE命令不常用,我們通常只會在沒有足夠內(nèi)存去執(zhí)行BGSAVE命令的情況下,又或者即使等待持久化操作執(zhí)行完畢也無所謂的情況下,才會使用這個命令。
- save選項: 如果用戶設(shè)置了save選項(一般會默認(rèn)設(shè)置),比如 save 60 10000,那么從Redis最近一次創(chuàng)建快照之后開始算起,當(dāng)“60秒之內(nèi)有10000次寫入”這個條件被滿足時,Redis就會自動觸發(fā)BGSAVE命令。
- SHUTDOWN命令: 當(dāng)Redis通過SHUTDOWN命令接收到關(guān)閉服務(wù)器的請求時,或者接收到標(biāo)準(zhǔn)TERM信號時,會執(zhí)行一個SAVE命令,阻塞所有客戶端,不再執(zhí)行客戶端發(fā)送的任何命令,并在SAVE命令執(zhí)行完畢之后關(guān)閉服務(wù)器。
- 一個Redis服務(wù)器連接到另一個Redis服務(wù)器: 當(dāng)一個Redis服務(wù)器連接到另一個Redis服務(wù)器,并向?qū)Ψ桨l(fā)送SYNC命令來開始一次復(fù)制操作的時候,如果主服務(wù)器目前沒有執(zhí)行BGSAVE操作,或者主服務(wù)器并非剛剛執(zhí)行完BGSAVE操作,那么主服務(wù)器就會執(zhí)行BGSAVE命令
如果系統(tǒng)真的發(fā)生崩潰,用戶將丟失最近一次生成快照之后更改的所有數(shù)據(jù)。因此,快照持久化只適用于即使丟失一部分?jǐn)?shù)據(jù)也不會造成一些大問題的應(yīng)用程序。不能接受這個缺點(diǎn)的話,可以考慮AOF持久化。
AOF 持久化
AOF 持久化:將寫命令添加到 AOF 文件(Append Only File)的末尾。
與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。默認(rèn)情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數(shù)開啟:
appendonly yes
開啟AOF持久化后每執(zhí)行一條會更改Redis中的數(shù)據(jù)的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設(shè)置的,默認(rèn)的文件名是appendonly.aof。
使用 AOF 持久化需要設(shè)置同步選項,從而確定寫命令同步到磁盤文件上的時機(jī)。這是因為對文件進(jìn)行寫入并不會馬上將內(nèi)容同步到磁盤上,而是先存儲到緩沖區(qū),然后由操作系統(tǒng)決定什么時候同步到磁盤。在Redis的配置文件中存在三種同步方式
| 選項 | 同步頻率 |
|---|---|
| always | 每個寫命令都同步,這樣會嚴(yán)重降低Redis的速度 |
| everysec | 每秒同步一次 |
| no | 讓操作系統(tǒng)來決定何時同步 |
appendfsync always 可以實現(xiàn)將數(shù)據(jù)丟失減到最少,不過這種方式需要對硬盤進(jìn)行大量的寫入而且每次只寫入一個命令,十分影響Redis的速度。另外使用固態(tài)硬盤的用戶謹(jǐn)慎使用appendfsync always選項,因為這會明顯降低固態(tài)硬盤的使用壽命。
appendfsync everysec 為了兼顧數(shù)據(jù)和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰,用戶最多只會丟失一秒之內(nèi)產(chǎn)生的數(shù)據(jù)。當(dāng)硬盤忙于執(zhí)行寫入操作的時候,Redis還會優(yōu)雅的放慢自己的速度以便適應(yīng)硬盤的最大寫入速度。
appendfsync no 選項一般不推薦,這種方案會使Redis丟失不定量的數(shù)據(jù)而且如果用戶的硬盤處理寫入操作的速度不夠的話,那么當(dāng)緩沖區(qū)被等待寫入的數(shù)據(jù)填滿時,Redis的寫入操作將被阻塞,這會導(dǎo)致Redis的請求速度變慢。
隨著服務(wù)器寫請求的增多,AOF 文件會越來越大。Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 文件中的冗余寫命令。
雖然AOF持久化非常靈活地提供了多種不同的選項來滿足不同應(yīng)用程序?qū)?shù)據(jù)安全的不同要求,但AOF持久化也有缺陷——AOF文件的體積太大。
重寫/壓縮AOF
AOF雖然在某個角度可以將數(shù)據(jù)丟失降低到最小而且對性能影響也很小,但是極端的情況下,體積不斷增大的AOF文件很可能會用完硬盤空間。另外,如果AOF體積過大,那么還原操作執(zhí)行時間就可能會非常長。
為了解決AOF體積過大的問題,用戶可以向Redis發(fā)送 BGREWRITEAOF命令 ,這個命令會通過移除AOF文件中的冗余命令來重寫(rewrite)AOF文件來減小AOF文件的體積。BGREWRITEAOF命令和BGSAVE創(chuàng)建快照原理十分相似,所以AOF文件重寫也需要用到子進(jìn)程,這樣會導(dǎo)致性能問題和內(nèi)存占用問題,和快照持久化一樣。更糟糕的是,如果不加以控制的話,AOF文件的體積可能會比快照文件大好幾倍。
文件重寫流程:

和快照持久化可以通過設(shè)置save選項來自動執(zhí)行BGSAVE一樣,AOF持久化設(shè)置以下參數(shù)
auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size
選項自動執(zhí)行BGREWRITEAOF命令。舉例:假設(shè)用戶對Redis設(shè)置了如下配置選項并且啟用了AOF持久化。那么當(dāng)AOF文件體積大于64mb,并且AOF的體積比上一次重寫之后的體積大了至少一倍(100%)的時候,Redis將執(zhí)行BGREWRITEAOF命令。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
無論是AOF持久化還是快照持久化,將數(shù)據(jù)持久化到硬盤上都是非常有必要的,但除了進(jìn)行持久化外,用戶還必須對持久化得到的文件進(jìn)行備份(最好是備份到不同的地方),這樣才能盡量避免數(shù)據(jù)丟失事故發(fā)生。如果條件允許的話,最好能將快照文件和重新重寫的AOF文件備份到不同的服務(wù)器上面。
隨著負(fù)載量的上升,或者數(shù)據(jù)的完整性變得越來越重要時,用戶可能需要使用到復(fù)制特性。
Redis 4.0 對持久化機(jī)制的優(yōu)化
Redis 4.0 開始支持 RDB 和 AOF 的混合持久化(默認(rèn)關(guān)閉,可以通過配置項 aof-use-rdb-preamble 開啟)。
如果把混合持久化打開,AOF 重寫的時候就直接把 RDB 的內(nèi)容寫到 AOF 文件開頭。這樣做的好處是可以結(jié)合 RDB 和 AOF 的優(yōu)點(diǎn), 快速加載同時避免丟失過多的數(shù)據(jù)。當(dāng)然缺點(diǎn)也是有的, AOF 里面的 RDB 部分就是壓縮格式不再是 AOF 格式,可讀性較差。
如何選擇合適的持久化方式
- 一般來說, 如果想達(dá)到足以媲美PostgreSQL的數(shù)據(jù)安全性,你應(yīng)該同時使用兩種持久化功能。在這種情況下,當(dāng) Redis 重啟的時候會優(yōu)先載入AOF文件來恢復(fù)原始的數(shù)據(jù),因為在通常情況下AOF文件保存的數(shù)據(jù)集要比RDB文件保存的數(shù)據(jù)集要完整。
- 如果你非常關(guān)心你的數(shù)據(jù), 但仍然可以承受數(shù)分鐘以內(nèi)的數(shù)據(jù)丟失,那么你可以只使用RDB持久化。
- 有很多用戶都只使用AOF持久化,但并不推薦這種方式,因為定時生成RDB快照(snapshot)非常便于進(jìn)行數(shù)據(jù)庫備份, 并且 RDB 恢復(fù)數(shù)據(jù)集的速度也要比AOF恢復(fù)的速度要快,除此之外,使用RDB還可以避免AOF程序的bug。
- 如果你只希望你的數(shù)據(jù)在服務(wù)器運(yùn)行的時候存在,你也可以不使用任何持久化方式。
過期鍵的刪除策略
Redis中有個設(shè)置時間過期的功能,即對存儲在 redis 數(shù)據(jù)庫中的值可以設(shè)置一個過期時間。作為一個緩存數(shù)據(jù)庫,這是非常實用的。如我們一般項目中的 token 或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統(tǒng)的數(shù)據(jù)庫處理方式,一般都是自己判斷過期,這樣無疑會嚴(yán)重影響項目性能。
我們 set key 的時候,都可以給一個 expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存活的時間。
注:對于散列表這種容器,只能為整個鍵設(shè)置過期時間(整個散列表),而不能為鍵里面的單個元素設(shè)置過期時間。
如果一個鍵是過期的,那它到了過期時間之后是不是馬上就從內(nèi)存中被被刪除呢?如果不是,那過期后到底什么時候被刪除呢?
其實有三種不同的刪除策略:
(1):立即刪除。在設(shè)置鍵的過期時間時,創(chuàng)建一個回調(diào)事件,當(dāng)過期時間達(dá)到時,由時間處理器自動執(zhí)行鍵的刪除操作。
(2):惰性刪除。鍵過期了就過期了,不管。每次從dict字典中按key取值時,先檢查此key是否已經(jīng)過期,如果過期了就刪除它,并返回nil,如果沒過期,就返回鍵值。
(3):定時刪除。每隔一段時間,對expires字典進(jìn)行檢查,刪除里面的過期鍵。
可以看到,第二種為被動刪除,第一種和第三種為主動刪除,且第一種實時性更高。下面對這三種刪除策略進(jìn)行具體分析。
立即刪除
立即刪除能保證內(nèi)存中數(shù)據(jù)的最大新鮮度,因為它保證過期鍵值會在過期后馬上被刪除,其所占用的內(nèi)存也會隨之釋放。但是立即刪除對cpu是最不友好的。因為刪除操作會占用cpu的時間,如果剛好碰上了cpu很忙的時候,比如正在做交集或排序等計算的時候,就會給cpu造成額外的壓力。
而且目前redis事件處理器對時間事件的處理方式--無序鏈表,查找一個key的時間復(fù)雜度為O(n),所以并不適合用來處理大量的時間事件。
惰性刪除
惰性刪除是指,某個鍵值過期后,此鍵值不會馬上被刪除,而是等到下次被使用的時候,才會被檢查到過期,此時才能得到刪除。所以惰性刪除的缺點(diǎn)很明顯:浪費(fèi)內(nèi)存。dict字典和expires字典都要保存這個鍵值的信息。
舉個例子,對于一些按時間點(diǎn)來更新的數(shù)據(jù),比如log日志,過期后在很長的一段時間內(nèi)可能都得不到訪問,這樣在這段時間內(nèi)就要拜拜浪費(fèi)這么多內(nèi)存來存log。這對于性能非常依賴于內(nèi)存大小的redis來說,是比較致命的。
定時刪除
從上面分析來看,立即刪除會短時間內(nèi)占用大量cpu,惰性刪除會在一段時間內(nèi)浪費(fèi)內(nèi)存,所以定時刪除是一個折中的辦法。
定時刪除是:每隔一段時間執(zhí)行一次刪除操作,并通過限制刪除操作執(zhí)行的時長和頻率,來減少刪除操作對cpu的影響。另一方面定時刪除也有效的減少了因惰性刪除帶來的內(nèi)存浪費(fèi)。
redis使用的策略
redis使用的過期鍵值刪除策略是:惰性刪除加上定期刪除,兩者配合使用。
但是僅僅通過設(shè)置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然后你也沒及時去查,也就沒走惰性刪除,此時會怎么樣?如果大量過期key堆積在內(nèi)存里,導(dǎo)致redis內(nèi)存塊耗盡了。怎么解決這個問題呢? redis 數(shù)據(jù)淘汰策略。
數(shù)據(jù)淘汰策略
可以設(shè)置內(nèi)存最大使用量,當(dāng)內(nèi)存使用量超出時,會施行數(shù)據(jù)淘汰策略。
Redis 具體有 6 種淘汰策略:
| 策略 | 描述 | 應(yīng)用場景 |
|---|---|---|
| volatile-lru | 從已設(shè)置過期時間的數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰 | 如果設(shè)置了過期時間,且分熱數(shù)據(jù)與冷數(shù)據(jù),推薦使用 volatile-lru 策略。 |
| volatile-ttl | 從已設(shè)置過期時間的數(shù)據(jù)集中挑選將要過期的數(shù)據(jù)淘汰 | 如果讓 Redis 根據(jù) TTL 來篩選需要刪除的key,請使用 volatile-ttl 策略。 |
| volatile-random | 從已設(shè)置過期時間的數(shù)據(jù)集中任意選擇數(shù)據(jù)淘汰 | 很少使用 |
| allkeys-lru | 從所有數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰 | 使用 Redis 緩存數(shù)據(jù)時,為了提高緩存命中率,需要保證緩存數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)??梢詫?nèi)存最大使用量設(shè)置為熱點(diǎn)數(shù)據(jù)占用的內(nèi)存量,然后啟用 allkeys-lru 淘汰策略,將最近最少使用的數(shù)據(jù)淘汰。<br />值得一提的是,設(shè)置 expire 會消耗額外的內(nèi)存,所以使用 allkeys-lru 策略,可以更高效地利用內(nèi)存,因為這樣就可以不再設(shè)置過期時間了。 |
| allkeys-random | 從所有數(shù)據(jù)集中任意選擇數(shù)據(jù)進(jìn)行淘汰 | 如果需要循環(huán)讀寫所有的key,或者各個key的訪問頻率差不多,可以使用 allkeys-random 策略 |
| noeviction | 不刪除策略,達(dá)到最大內(nèi)存限制時,如果需要更多內(nèi)存,直接返回錯誤信息。大多數(shù)寫命令都會導(dǎo)致占用更多的內(nèi)存 | 很少使用 |
作為內(nèi)存數(shù)據(jù)庫,出于對性能和內(nèi)存消耗的考慮,Redis 的淘汰算法實際實現(xiàn)上并非針對所有 key,而是抽樣一小部分并且從中選出被淘汰的 key。
Redis 4。0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通過統(tǒng)計訪問頻率,將訪問頻率最少的鍵值對淘汰。
您需要根據(jù)系統(tǒng)的特征,來選擇合適的淘汰策略。 當(dāng)然,在運(yùn)行過程中也可以通過命令動態(tài)設(shè)置淘汰策略,并通過 INFO 命令監(jiān)控緩存的 miss 和 hit,來進(jìn)行調(diào)優(yōu)。
淘汰策略的內(nèi)部實現(xiàn)
- 客戶端執(zhí)行一個命令,導(dǎo)致 Redis 中的數(shù)據(jù)增加,占用更多內(nèi)存
- Redis 檢查內(nèi)存使用量,如果超出 maxmemory 限制,根據(jù)策略清除部分 key
- 繼續(xù)執(zhí)行下一條命令,以此類推
在這個過程中,內(nèi)存使用量會不斷地達(dá)到 limit 值,然后超過,然后刪除部分 key,使用量又下降到 limit 值之下。
如果某個命令導(dǎo)致大量內(nèi)存占用(比如通過新key保存一個很大的set),在一段時間內(nèi),可能內(nèi)存的使用量會明顯超過 maxmemory 限制。
Redis 與 Memcached 的區(qū)別
兩者都是非關(guān)系型內(nèi)存鍵值數(shù)據(jù)庫,現(xiàn)在公司一般都是用 Redis 來實現(xiàn)緩存,而且 Redis 自身也越來越強(qiáng)大了!Redis 與 Memcached 主要有以下不同:
| 對比參數(shù) | Redis | Memcached |
|---|---|---|
| 類型 | 1. 支持內(nèi)存 2. 非關(guān)系型數(shù)據(jù)庫 | 1. 支持內(nèi)存 2. 鍵值對形式 3. 緩存形式 |
| 數(shù)據(jù)存儲類型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗稱ZSet】 | 1. 文本型 2. 二進(jìn)制類型 |
| 查詢【操作】類型 | 1. 批量操作 2. 事務(wù)支持 3. 每個類型不同的CRUD | 1.常用的CRUD 2. 少量的其他命令 |
| 附加功能 | 1. 發(fā)布/訂閱模式 2. 主從分區(qū) 3. 序列化支持 4. 腳本支持【Lua腳本】 | 1. 多線程服務(wù)支持 |
| 網(wǎng)絡(luò)IO模型 | 1. 單線程的多路 IO 復(fù)用模型 | 1. 多線程,非阻塞IO模式 |
| 事件庫 | 自封轉(zhuǎn)簡易事件庫AeEvent | 貴族血統(tǒng)的LibEvent事件庫 |
| 持久化支持 | 1. RDB 2. AOF | 不支持 |
| 集群模式 | 原生支持 cluster 模式 | 沒有原生的集群模式,需要依靠客戶端來實現(xiàn)往集群中分片寫入數(shù)據(jù) |
| 內(nèi)存管理機(jī)制 | 在 Redis 中,并不是所有數(shù)據(jù)都一直存儲在內(nèi)存中,可以將一些很久沒用的 value 交換到磁盤 | Memcached 的數(shù)據(jù)則會一直在內(nèi)存中,Memcached 將內(nèi)存分割成特定長度的塊來存儲數(shù)據(jù),以完全解決內(nèi)存碎片的問題。但是這種方式會使得內(nèi)存的利用率不高,例如塊的大小為 128 bytes,只存儲 100 bytes 的數(shù)據(jù),那么剩下的 28 bytes 就浪費(fèi)掉了。 |
事務(wù)
Redis 通過 MULTI、EXEC、WATCH 等命令來實現(xiàn)事務(wù)(transaction)功能。事務(wù)提供了一種將多個命令請求打包,然后一次性、按順序地執(zhí)行多個命令的機(jī)制,并且在事務(wù)執(zhí)行期間,服務(wù)器不會中斷事務(wù)而改去執(zhí)行其他客戶端的命令請求,它會將事務(wù)中的所有命令都執(zhí)行完畢,然后才去處理其他客戶端的命令請求。
事務(wù)中的多個命令被一次性發(fā)送給服務(wù)器,而不是一條一條發(fā)送,這種方式被稱為流水線,可以減少客戶端與服務(wù)器之間的網(wǎng)絡(luò)通信次數(shù)從而提升性能。
在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫中,常用 ACID 性質(zhì)來檢驗事務(wù)功能的可靠性和安全性。在 Redis 中,事務(wù)總是具有原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation),并且當(dāng) Redis 運(yùn)行在某種特定的持久化模式下時,事務(wù)也具有持久性(Durability)。
事件
Redis 服務(wù)器是一個事件驅(qū)動程序。
文件事件
服務(wù)器通過套接字與客戶端或者其它服務(wù)器進(jìn)行通信,文件事件就是對套接字操作的抽象。
Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器,使用 I/O 多路復(fù)用程序來同時監(jiān)聽多個套接字,并將到達(dá)的事件傳送給文件事件分派器,分派器會根據(jù)套接字產(chǎn)生的事件類型調(diào)用相應(yīng)的事件處理器。

時間事件
服務(wù)器有一些操作需要在給定的時間點(diǎn)執(zhí)行,時間事件是對這類定時操作的抽象。
時間事件又分為:
- 定時事件:是讓一段程序在指定的時間之內(nèi)執(zhí)行一次
- 周期性事件:是讓一段程序每隔指定時間就執(zhí)行一次
目前Redis只使用周期性事件,而沒有使用定時事件。 一個事件時間主要由三個屬性組成:
- id:服務(wù)器為時間事件創(chuàng)建的全局唯一ID
- when:毫秒精度的UNIX時間戳,記錄了時間事件的到達(dá)時間
- timeProc:時間事件處理器,一個函數(shù)
實現(xiàn)服務(wù)器將所有時間事件都放在一個無序鏈表中,每當(dāng)時間事件執(zhí)行器運(yùn)行時,遍歷整個鏈表,查找所有已到達(dá)的時間事件,并調(diào)用相應(yīng)的事件處理器。(該鏈表為無序鏈表,不按when屬性的大小排序)
事件的調(diào)度與執(zhí)行
服務(wù)器需要不斷監(jiān)聽文件事件的套接字才能得到待處理的文件事件,但是不能一直監(jiān)聽,否則時間事件無法在規(guī)定的時間內(nèi)執(zhí)行,因此監(jiān)聽時間應(yīng)該根據(jù)距離現(xiàn)在最近的時間事件來決定。

Sentinel
Sentinel(哨兵)可以監(jiān)聽集群中的服務(wù)器,并在主服務(wù)器進(jìn)入下線狀態(tài)時,自動從從服務(wù)器中選舉出新的主服務(wù)器。
分片
分片是將數(shù)據(jù)劃分為多個部分的方法,可以將數(shù)據(jù)存儲到多臺機(jī)器里面,這種方法在解決某些問題時可以獲得線性級別的性能提升。
假設(shè)有 4 個 Redis 實例 R0,R1,R2,R3,還有很多表示用戶的鍵 user:1,user:2,... ,有不同的方式來選擇一個指定的鍵存儲在哪個實例中。
- 最簡單的方式是范圍分片,例如用戶 id 從 0~1000 的存儲到實例 R0 中,用戶 id 從 1001~2000 的存儲到實例 R1 中,等等。但是這樣需要維護(hù)一張映射范圍表,維護(hù)操作代價很高。
- 還有一種方式是哈希分片,使用 CRC32 哈希函數(shù)將鍵轉(zhuǎn)換為一個數(shù)字,再對實例數(shù)量求模就能知道應(yīng)該存儲的實例。
根據(jù)執(zhí)行分片的位置,可以分為三種分片方式:
- 客戶端分片:客戶端使用一致性哈希等算法決定鍵應(yīng)當(dāng)分布到哪個節(jié)點(diǎn)。
- 代理分片:將客戶端請求發(fā)送到代理上,由代理轉(zhuǎn)發(fā)請求到正確的節(jié)點(diǎn)上。
- 服務(wù)器分片:Redis Cluster。
復(fù)制
通過使用 slaveof host port 命令來讓一個服務(wù)器成為另一個服務(wù)器的從服務(wù)器。
一個從服務(wù)器只能有一個主服務(wù)器,并且不支持主主復(fù)制。
連接過程
- 主服務(wù)器創(chuàng)建快照文件,發(fā)送給從服務(wù)器,并在發(fā)送期間使用緩沖區(qū)記錄執(zhí)行的寫命令??煺瘴募l(fā)送完畢之后,開始向從服務(wù)器發(fā)送存儲在緩沖區(qū)中的寫命令
- 從服務(wù)器丟棄所有舊數(shù)據(jù),載入主服務(wù)器發(fā)來的快照文件,之后從服務(wù)器開始接受主服務(wù)器發(fā)來的寫命令
- 主服務(wù)器每執(zhí)行一次寫命令,就向從服務(wù)器發(fā)送相同的寫命令
主從鏈
隨著負(fù)載不斷上升,主服務(wù)器可能無法很快地更新所有從服務(wù)器,或者重新連接和重新同步從服務(wù)器將導(dǎo)致系統(tǒng)超載。為了解決這個問題,可以創(chuàng)建一個中間層來分擔(dān)主服務(wù)器的復(fù)制工作。中間層的服務(wù)器是最上層服務(wù)器的從服務(wù)器,又是最下層服務(wù)器的主服務(wù)器。

一個簡單的論壇系統(tǒng)
該論壇系統(tǒng)功能如下:
- 可以發(fā)布文章
- 可以對文章進(jìn)行點(diǎn)贊
- 在首頁可以按文章的發(fā)布時間或者文章的點(diǎn)贊數(shù)進(jìn)行排序顯示
文章信息
文章包括標(biāo)題、作者、贊數(shù)等信息,在關(guān)系型數(shù)據(jù)庫中很容易構(gòu)建一張表來存儲這些信息,在 Redis 中可以使用 HASH 來存儲每種信息以及其對應(yīng)的值的映射。
Redis 沒有關(guān)系型數(shù)據(jù)庫中的表這一概念來將同種類型的數(shù)據(jù)存放在一起,而是使用命名空間的方式來實現(xiàn)這一功能。鍵名的前面部分存儲命名空間,后面部分的內(nèi)容存儲 ID,通常使用 : 來進(jìn)行分隔。例如下面的 HASH 的鍵名為 article:92617,其中 article 為命名空間,ID 為 92617。

點(diǎn)贊功能
當(dāng)有用戶為一篇文章點(diǎn)贊時,除了要對該文章的 votes 字段進(jìn)行加 1 操作,還必須記錄該用戶已經(jīng)對該文章進(jìn)行了點(diǎn)贊,防止用戶點(diǎn)贊次數(shù)超過 1??梢越⑽恼碌囊淹镀庇脩艏蟻磉M(jìn)行記錄。
為了節(jié)約內(nèi)存,規(guī)定一篇文章發(fā)布滿一周之后,就不能再對它進(jìn)行投票,而文章的已投票集合也會被刪除,可以為文章的已投票集合設(shè)置一個一周的過期時間就能實現(xiàn)這個規(guī)定。

對文章進(jìn)行排序
為了按發(fā)布時間和點(diǎn)贊數(shù)進(jìn)行排序,可以建立一個文章發(fā)布時間的有序集合和一個文章點(diǎn)贊數(shù)的有序集合。(下圖中的 score 就是這里所說的點(diǎn)贊數(shù);下面所示的有序集合分值并不直接是時間和點(diǎn)贊數(shù),而是根據(jù)時間和點(diǎn)贊數(shù)間接計算出來的)
