Redis簡介
Redis 是一個使用 C 語言編寫的,開源的(BSD許可)高性能非關系型(NoSQL)的鍵值對數據庫。
Redis 可以存儲鍵和五種不同類型的值之間的映射。鍵的類型只能為字符串,值支持五種數據類型:字符串、列表、集合、散列表、有序集合。
與傳統數據庫不同的是 Redis 的數據是存在內存中的,所以讀寫速度非常快,因此 redis 被廣泛應用于緩存方向,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。另外,Redis 也經常用來做分布式鎖。除此之外,Redis 支持事務 、持久化、LUA腳本、LRU驅動事件、多種集群方案。
從2010年3月15日起,Redis的開發工作由VMware主持。從2013年5月開始,Redis的開發由Pivotal贊助。
Redis的優缺點
優點
- 讀寫性能優異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
- 支持數據持久化,支持AOF和RDB兩種持久化方式。
- 支持事務,Redis的所有操作都是原子性的,同時Redis還支持對幾個操作合并后的原子性執行。
- 數據結構豐富,除了支持string類型的value外還支持hash、set、zset、list等數據結構。
- 支持主從復制,主機會自動將數據同步到從機,可以進行讀寫分離。
缺點
- 數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要局限在較小數據量的高性能操作和運算上。
- Redis 不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。
- 主機宕機,宕機前有部分數據未能及時同步到從機,切換IP后還會引入數據不一致的問題,降低了系統的可用性。
- Redis 較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。為避免這一問題,運維人員在系統上線時必須確保有足夠的空間,這對資源造成了很大的浪費。
數據類型
數據類型 | 可以存儲的值 | 操作 |
---|---|---|
STRING | 字符串、整數或者浮點數 | 對整個字符串或者字符串的其中一部分執行操作<br />對整數和浮點數執行自增或者自減操作 |
LIST | 列表 | 從兩端壓入或者彈出元素<br />對單個或者多個元素進行修剪,<br />只保留一個范圍內的元素 |
SET | 無序集合 | 添加、獲取、移除單個元素<br />檢查一個元素是否存在于集合中<br /> 計算交集、并集、差集<br />從集合里面隨機獲取元素 |
HASH | 包含鍵值對的無序散列表 | 添加、獲取、移除單個鍵值對<br />獲取所有鍵值對<br /> 檢查某個鍵是否存在 |
ZSET | 有序集合 | 添加、獲取、刪除元素<br />根據分值范圍或者成員來獲取元素<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"
使用場景
計數器
可以對 String 進行自增自減運算,從而實現計數器功能。
Redis 這種內存型數據庫的讀寫性能非常高,很適合存儲頻繁讀寫的計數量。
緩存
將熱點數據放到內存中,設置內存的最大使用量以及淘汰策略來保證緩存的命中率。
會話緩存
可以使用 Redis 來統一存儲多臺應用服務器的會話信息。
當應用服務器不再存儲用戶的會話信息,也就不再具有狀態,一個用戶可以請求任意一個應用服務器,從而更容易實現高可用性以及可伸縮性。
全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC平臺。
以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
查找表
例如 DNS 記錄就很適合使用 Redis 進行存儲。
查找表和緩存類似,也是利用了 Redis 快速的查找特性。但是查找表的內容不能失效,而緩存的內容可以失效,因為緩存不作為可靠的數據來源。
消息隊列(發布/訂閱功能)
List 是一個雙向鏈表,可以通過 lpush 和 rpop 寫入和讀取消息
不過最好使用 Kafka、RabbitMQ 等消息中間件。
分布式鎖實現
在分布式場景下,無法使用單機環境下的鎖來對多個節點上的進程進行同步。
可以使用 Redis 自帶的 SETNX 命令實現分布式鎖,除此之外,還可以使用官方提供的 RedLock 分布式鎖實現。
其它
Set 可以實現交集、并集等操作,從而實現共同好友等功能。
ZSet 可以實現有序性操作,從而實現排行榜等功能。
持久化
Redis 是內存型數據庫,為了之后重用數據(比如重啟機器、機器故障之后回復數據),或者是為了防止系統故障而將數據備份到一個遠程位置,需要將內存中的數據持久化到硬盤上。
Redis 提供了RDB和AOF兩種持久化方式。默認是只開啟RDB,當Redis重啟時,它會優先使用AOF文件來還原數據集。
RDB 持久化(快照持久化)
RDB 持久化:將某個時間點的所有數據都存放到硬盤上。
可以將快照復制到其它服務器從而創建具有相同數據的服務器副本。如果系統發生故障,將會丟失最后一次創建快照之后的數據。如果數據量很大,保存快照的時間會很長。
快照持久化是Redis默認采用的持久化方式,在redis.conf配置文件中默認有此下配置:
#在900秒(15分鐘)之后,如果至少有1個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。
save 900 1
#在300秒(5分鐘)之后,如果至少有10個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。
save 300 10
#在60秒(1分鐘)之后,如果至少有10000個key發生變化,Redis就會自動觸發BGSAVE命令創建快照。
save 60 10000
根據配置,快照將被寫入dbfilename選項指定的文件里面,并存儲在dir選項指定的路徑上面。如果在新的快照文件創建完畢之前,Redis、系統或者硬件這三者中的任意一個崩潰了,那么Redis將丟失最近一次創建快照寫入的所有數據。
舉個例子:假設Redis的上一個快照是2:35開始創建的,并且已經創建成功。下午3:06時,Redis又開始創建新的快照,并且在下午3:08快照創建完畢之前,有35個鍵進行了更新。如果在下午3:06到3:08期間,系統發生了崩潰,導致Redis無法完成新快照的創建工作,那么Redis將丟失下午2:35之后寫入的所有數據。另一方面,如果系統恰好在新的快照文件創建完畢之后崩潰,那么Redis將丟失35個鍵的更新數據。
創建快照的辦法有如下幾種:
- BGSAVE命令: 客戶端向Redis發送 BGSAVE命令 來創建一個快照。對于支持BGSAVE命令的平臺來說(基本上所有平臺支持,除了Windows平臺),Redis會調用fork來創建一個子進程,然后子進程負責將快照寫入硬盤,而父進程則繼續處理命令請求。
- SAVE命令: 客戶端還可以向Redis發送 SAVE命令 來創建一個快照,接到SAVE命令的Redis服務器在快照創建完畢之前不會再響應任何其他命令。SAVE命令不常用,我們通常只會在沒有足夠內存去執行BGSAVE命令的情況下,又或者即使等待持久化操作執行完畢也無所謂的情況下,才會使用這個命令。
- save選項: 如果用戶設置了save選項(一般會默認設置),比如 save 60 10000,那么從Redis最近一次創建快照之后開始算起,當“60秒之內有10000次寫入”這個條件被滿足時,Redis就會自動觸發BGSAVE命令。
- SHUTDOWN命令: 當Redis通過SHUTDOWN命令接收到關閉服務器的請求時,或者接收到標準TERM信號時,會執行一個SAVE命令,阻塞所有客戶端,不再執行客戶端發送的任何命令,并在SAVE命令執行完畢之后關閉服務器。
- 一個Redis服務器連接到另一個Redis服務器: 當一個Redis服務器連接到另一個Redis服務器,并向對方發送SYNC命令來開始一次復制操作的時候,如果主服務器目前沒有執行BGSAVE操作,或者主服務器并非剛剛執行完BGSAVE操作,那么主服務器就會執行BGSAVE命令
如果系統真的發生崩潰,用戶將丟失最近一次生成快照之后更改的所有數據。因此,快照持久化只適用于即使丟失一部分數據也不會造成一些大問題的應用程序。不能接受這個缺點的話,可以考慮AOF持久化。
AOF 持久化
AOF 持久化:將寫命令添加到 AOF 文件(Append Only File)的末尾。
與快照持久化相比,AOF持久化 的實時性更好,因此已成為主流的持久化方案。默認情況下Redis沒有開啟AOF(append only file)方式的持久化,可以通過appendonly參數開啟:
appendonly yes
開啟AOF持久化后每執行一條會更改Redis中的數據的命令,Redis就會將該命令寫入硬盤中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的,默認的文件名是appendonly.aof。
使用 AOF 持久化需要設置同步選項,從而確定寫命令同步到磁盤文件上的時機。這是因為對文件進行寫入并不會馬上將內容同步到磁盤上,而是先存儲到緩沖區,然后由操作系統決定什么時候同步到磁盤。在Redis的配置文件中存在三種同步方式
選項 | 同步頻率 |
---|---|
always | 每個寫命令都同步,這樣會嚴重降低Redis的速度 |
everysec | 每秒同步一次 |
no | 讓操作系統來決定何時同步 |
appendfsync always 可以實現將數據丟失減到最少,不過這種方式需要對硬盤進行大量的寫入而且每次只寫入一個命令,十分影響Redis的速度。另外使用固態硬盤的用戶謹慎使用appendfsync always選項,因為這會明顯降低固態硬盤的使用壽命。
appendfsync everysec 為了兼顧數據和寫入性能,用戶可以考慮 appendfsync everysec選項 ,讓Redis每秒同步一次AOF文件,Redis性能幾乎沒受到任何影響。而且這樣即使出現系統崩潰,用戶最多只會丟失一秒之內產生的數據。當硬盤忙于執行寫入操作的時候,Redis還會優雅的放慢自己的速度以便適應硬盤的最大寫入速度。
appendfsync no 選項一般不推薦,這種方案會使Redis丟失不定量的數據而且如果用戶的硬盤處理寫入操作的速度不夠的話,那么當緩沖區被等待寫入的數據填滿時,Redis的寫入操作將被阻塞,這會導致Redis的請求速度變慢。
隨著服務器寫請求的增多,AOF 文件會越來越大。Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 文件中的冗余寫命令。
雖然AOF持久化非常靈活地提供了多種不同的選項來滿足不同應用程序對數據安全的不同要求,但AOF持久化也有缺陷——AOF文件的體積太大。
重寫/壓縮AOF
AOF雖然在某個角度可以將數據丟失降低到最小而且對性能影響也很小,但是極端的情況下,體積不斷增大的AOF文件很可能會用完硬盤空間。另外,如果AOF體積過大,那么還原操作執行時間就可能會非常長。
為了解決AOF體積過大的問題,用戶可以向Redis發送 BGREWRITEAOF命令 ,這個命令會通過移除AOF文件中的冗余命令來重寫(rewrite)AOF文件來減小AOF文件的體積。BGREWRITEAOF命令和BGSAVE創建快照原理十分相似,所以AOF文件重寫也需要用到子進程,這樣會導致性能問題和內存占用問題,和快照持久化一樣。更糟糕的是,如果不加以控制的話,AOF文件的體積可能會比快照文件大好幾倍。
文件重寫流程:
和快照持久化可以通過設置save選項來自動執行BGSAVE一樣,AOF持久化設置以下參數
auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size
選項自動執行BGREWRITEAOF命令。舉例:假設用戶對Redis設置了如下配置選項并且啟用了AOF持久化。那么當AOF文件體積大于64mb,并且AOF的體積比上一次重寫之后的體積大了至少一倍(100%)的時候,Redis將執行BGREWRITEAOF命令。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
無論是AOF持久化還是快照持久化,將數據持久化到硬盤上都是非常有必要的,但除了進行持久化外,用戶還必須對持久化得到的文件進行備份(最好是備份到不同的地方),這樣才能盡量避免數據丟失事故發生。如果條件允許的話,最好能將快照文件和重新重寫的AOF文件備份到不同的服務器上面。
隨著負載量的上升,或者數據的完整性變得越來越重要時,用戶可能需要使用到復制特性。
Redis 4.0 對持久化機制的優化
Redis 4.0 開始支持 RDB 和 AOF 的混合持久化(默認關閉,可以通過配置項 aof-use-rdb-preamble
開啟)。
如果把混合持久化打開,AOF 重寫的時候就直接把 RDB 的內容寫到 AOF 文件開頭。這樣做的好處是可以結合 RDB 和 AOF 的優點, 快速加載同時避免丟失過多的數據。當然缺點也是有的, AOF 里面的 RDB 部分就是壓縮格式不再是 AOF 格式,可讀性較差。
如何選擇合適的持久化方式
- 一般來說, 如果想達到足以媲美PostgreSQL的數據安全性,你應該同時使用兩種持久化功能。在這種情況下,當 Redis 重啟的時候會優先載入AOF文件來恢復原始的數據,因為在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。
- 如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失,那么你可以只使用RDB持久化。
- 有很多用戶都只使用AOF持久化,但并不推薦這種方式,因為定時生成RDB快照(snapshot)非常便于進行數據庫備份, 并且 RDB 恢復數據集的速度也要比AOF恢復的速度要快,除此之外,使用RDB還可以避免AOF程序的bug。
- 如果你只希望你的數據在服務器運行的時候存在,你也可以不使用任何持久化方式。
過期鍵的刪除策略
Redis中有個設置時間過期的功能,即對存儲在 redis 數據庫中的值可以設置一個過期時間。作為一個緩存數據庫,這是非常實用的。如我們一般項目中的 token 或者一些登錄信息,尤其是短信驗證碼都是有時間限制的,按照傳統的數據庫處理方式,一般都是自己判斷過期,這樣無疑會嚴重影響項目性能。
我們 set key 的時候,都可以給一個 expire time,就是過期時間,通過過期時間我們可以指定這個 key 可以存活的時間。
注:對于散列表這種容器,只能為整個鍵設置過期時間(整個散列表),而不能為鍵里面的單個元素設置過期時間。
如果一個鍵是過期的,那它到了過期時間之后是不是馬上就從內存中被被刪除呢?如果不是,那過期后到底什么時候被刪除呢?
其實有三種不同的刪除策略:
(1):立即刪除。在設置鍵的過期時間時,創建一個回調事件,當過期時間達到時,由時間處理器自動執行鍵的刪除操作。
(2):惰性刪除。鍵過期了就過期了,不管。每次從dict字典中按key取值時,先檢查此key是否已經過期,如果過期了就刪除它,并返回nil,如果沒過期,就返回鍵值。
(3):定時刪除。每隔一段時間,對expires字典進行檢查,刪除里面的過期鍵。
可以看到,第二種為被動刪除,第一種和第三種為主動刪除,且第一種實時性更高。下面對這三種刪除策略進行具體分析。
立即刪除
立即刪除能保證內存中數據的最大新鮮度,因為它保證過期鍵值會在過期后馬上被刪除,其所占用的內存也會隨之釋放。但是立即刪除對cpu是最不友好的。因為刪除操作會占用cpu的時間,如果剛好碰上了cpu很忙的時候,比如正在做交集或排序等計算的時候,就會給cpu造成額外的壓力。
而且目前redis事件處理器對時間事件的處理方式--無序鏈表,查找一個key的時間復雜度為O(n),所以并不適合用來處理大量的時間事件。
惰性刪除
惰性刪除是指,某個鍵值過期后,此鍵值不會馬上被刪除,而是等到下次被使用的時候,才會被檢查到過期,此時才能得到刪除。所以惰性刪除的缺點很明顯:浪費內存。dict字典和expires字典都要保存這個鍵值的信息。
舉個例子,對于一些按時間點來更新的數據,比如log日志,過期后在很長的一段時間內可能都得不到訪問,這樣在這段時間內就要拜拜浪費這么多內存來存log。這對于性能非常依賴于內存大小的redis來說,是比較致命的。
定時刪除
從上面分析來看,立即刪除會短時間內占用大量cpu,惰性刪除會在一段時間內浪費內存,所以定時刪除是一個折中的辦法。
定時刪除是:每隔一段時間執行一次刪除操作,并通過限制刪除操作執行的時長和頻率,來減少刪除操作對cpu的影響。另一方面定時刪除也有效的減少了因惰性刪除帶來的內存浪費。
redis使用的策略
redis使用的過期鍵值刪除策略是:惰性刪除加上定期刪除,兩者配合使用。
但是僅僅通過設置過期時間還是有問題的。我們想一下:如果定期刪除漏掉了很多過期 key,然后你也沒及時去查,也就沒走惰性刪除,此時會怎么樣?如果大量過期key堆積在內存里,導致redis內存塊耗盡了。怎么解決這個問題呢? redis 數據淘汰策略。
數據淘汰策略
可以設置內存最大使用量,當內存使用量超出時,會施行數據淘汰策略。
Redis 具體有 6 種淘汰策略:
策略 | 描述 | 應用場景 |
---|---|---|
volatile-lru | 從已設置過期時間的數據集中挑選最近最少使用的數據淘汰 | 如果設置了過期時間,且分熱數據與冷數據,推薦使用 volatile-lru 策略。 |
volatile-ttl | 從已設置過期時間的數據集中挑選將要過期的數據淘汰 | 如果讓 Redis 根據 TTL 來篩選需要刪除的key,請使用 volatile-ttl 策略。 |
volatile-random | 從已設置過期時間的數據集中任意選擇數據淘汰 | 很少使用 |
allkeys-lru | 從所有數據集中挑選最近最少使用的數據淘汰 | 使用 Redis 緩存數據時,為了提高緩存命中率,需要保證緩存數據都是熱點數據。可以將內存最大使用量設置為熱點數據占用的內存量,然后啟用 allkeys-lru 淘汰策略,將最近最少使用的數據淘汰。<br />值得一提的是,設置 expire 會消耗額外的內存,所以使用 allkeys-lru 策略,可以更高效地利用內存,因為這樣就可以不再設置過期時間了。 |
allkeys-random | 從所有數據集中任意選擇數據進行淘汰 | 如果需要循環讀寫所有的key,或者各個key的訪問頻率差不多,可以使用 allkeys-random 策略 |
noeviction | 不刪除策略,達到最大內存限制時,如果需要更多內存,直接返回錯誤信息。大多數寫命令都會導致占用更多的內存 | 很少使用 |
作為內存數據庫,出于對性能和內存消耗的考慮,Redis 的淘汰算法實際實現上并非針對所有 key,而是抽樣一小部分并且從中選出被淘汰的 key。
Redis 4。0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通過統計訪問頻率,將訪問頻率最少的鍵值對淘汰。
您需要根據系統的特征,來選擇合適的淘汰策略。 當然,在運行過程中也可以通過命令動態設置淘汰策略,并通過 INFO 命令監控緩存的 miss 和 hit,來進行調優。
淘汰策略的內部實現
- 客戶端執行一個命令,導致 Redis 中的數據增加,占用更多內存
- Redis 檢查內存使用量,如果超出 maxmemory 限制,根據策略清除部分 key
- 繼續執行下一條命令,以此類推
在這個過程中,內存使用量會不斷地達到 limit 值,然后超過,然后刪除部分 key,使用量又下降到 limit 值之下。
如果某個命令導致大量內存占用(比如通過新key保存一個很大的set),在一段時間內,可能內存的使用量會明顯超過 maxmemory 限制。
Redis 與 Memcached 的區別
兩者都是非關系型內存鍵值數據庫,現在公司一般都是用 Redis 來實現緩存,而且 Redis 自身也越來越強大了!Redis 與 Memcached 主要有以下不同:
對比參數 | Redis | Memcached |
---|---|---|
類型 | 1. 支持內存 2. 非關系型數據庫 | 1. 支持內存 2. 鍵值對形式 3. 緩存形式 |
數據存儲類型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗稱ZSet】 | 1. 文本型 2. 二進制類型 |
查詢【操作】類型 | 1. 批量操作 2. 事務支持 3. 每個類型不同的CRUD | 1.常用的CRUD 2. 少量的其他命令 |
附加功能 | 1. 發布/訂閱模式 2. 主從分區 3. 序列化支持 4. 腳本支持【Lua腳本】 | 1. 多線程服務支持 |
網絡IO模型 | 1. 單線程的多路 IO 復用模型 | 1. 多線程,非阻塞IO模式 |
事件庫 | 自封轉簡易事件庫AeEvent | 貴族血統的LibEvent事件庫 |
持久化支持 | 1. RDB 2. AOF | 不支持 |
集群模式 | 原生支持 cluster 模式 | 沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據 |
內存管理機制 | 在 Redis 中,并不是所有數據都一直存儲在內存中,可以將一些很久沒用的 value 交換到磁盤 | Memcached 的數據則會一直在內存中,Memcached 將內存分割成特定長度的塊來存儲數據,以完全解決內存碎片的問題。但是這種方式會使得內存的利用率不高,例如塊的大小為 128 bytes,只存儲 100 bytes 的數據,那么剩下的 28 bytes 就浪費掉了。 |
事務
Redis 通過 MULTI、EXEC、WATCH 等命令來實現事務(transaction)功能。事務提供了一種將多個命令請求打包,然后一次性、按順序地執行多個命令的機制,并且在事務執行期間,服務器不會中斷事務而改去執行其他客戶端的命令請求,它會將事務中的所有命令都執行完畢,然后才去處理其他客戶端的命令請求。
事務中的多個命令被一次性發送給服務器,而不是一條一條發送,這種方式被稱為流水線,可以減少客戶端與服務器之間的網絡通信次數從而提升性能。
在傳統的關系式數據庫中,常用 ACID 性質來檢驗事務功能的可靠性和安全性。在 Redis 中,事務總是具有原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation),并且當 Redis 運行在某種特定的持久化模式下時,事務也具有持久性(Durability)。
事件
Redis 服務器是一個事件驅動程序。
文件事件
服務器通過套接字與客戶端或者其它服務器進行通信,文件事件就是對套接字操作的抽象。
Redis 基于 Reactor 模式開發了自己的網絡事件處理器,使用 I/O 多路復用程序來同時監聽多個套接字,并將到達的事件傳送給文件事件分派器,分派器會根據套接字產生的事件類型調用相應的事件處理器。
時間事件
服務器有一些操作需要在給定的時間點執行,時間事件是對這類定時操作的抽象。
時間事件又分為:
- 定時事件:是讓一段程序在指定的時間之內執行一次
- 周期性事件:是讓一段程序每隔指定時間就執行一次
目前Redis只使用周期性事件,而沒有使用定時事件。 一個事件時間主要由三個屬性組成:
- id:服務器為時間事件創建的全局唯一ID
- when:毫秒精度的UNIX時間戳,記錄了時間事件的到達時間
- timeProc:時間事件處理器,一個函數
實現服務器將所有時間事件都放在一個無序鏈表中,每當時間事件執行器運行時,遍歷整個鏈表,查找所有已到達的時間事件,并調用相應的事件處理器。(該鏈表為無序鏈表,不按when屬性的大小排序)
事件的調度與執行
服務器需要不斷監聽文件事件的套接字才能得到待處理的文件事件,但是不能一直監聽,否則時間事件無法在規定的時間內執行,因此監聽時間應該根據距離現在最近的時間事件來決定。
Sentinel
Sentinel(哨兵)可以監聽集群中的服務器,并在主服務器進入下線狀態時,自動從從服務器中選舉出新的主服務器。
分片
分片是將數據劃分為多個部分的方法,可以將數據存儲到多臺機器里面,這種方法在解決某些問題時可以獲得線性級別的性能提升。
假設有 4 個 Redis 實例 R0,R1,R2,R3,還有很多表示用戶的鍵 user:1,user:2,... ,有不同的方式來選擇一個指定的鍵存儲在哪個實例中。
- 最簡單的方式是范圍分片,例如用戶 id 從 0~1000 的存儲到實例 R0 中,用戶 id 從 1001~2000 的存儲到實例 R1 中,等等。但是這樣需要維護一張映射范圍表,維護操作代價很高。
- 還有一種方式是哈希分片,使用 CRC32 哈希函數將鍵轉換為一個數字,再對實例數量求模就能知道應該存儲的實例。
根據執行分片的位置,可以分為三種分片方式:
- 客戶端分片:客戶端使用一致性哈希等算法決定鍵應當分布到哪個節點。
- 代理分片:將客戶端請求發送到代理上,由代理轉發請求到正確的節點上。
- 服務器分片:Redis Cluster。
復制
通過使用 slaveof host port 命令來讓一個服務器成為另一個服務器的從服務器。
一個從服務器只能有一個主服務器,并且不支持主主復制。
連接過程
- 主服務器創建快照文件,發送給從服務器,并在發送期間使用緩沖區記錄執行的寫命令。快照文件發送完畢之后,開始向從服務器發送存儲在緩沖區中的寫命令
- 從服務器丟棄所有舊數據,載入主服務器發來的快照文件,之后從服務器開始接受主服務器發來的寫命令
- 主服務器每執行一次寫命令,就向從服務器發送相同的寫命令
主從鏈
隨著負載不斷上升,主服務器可能無法很快地更新所有從服務器,或者重新連接和重新同步從服務器將導致系統超載。為了解決這個問題,可以創建一個中間層來分擔主服務器的復制工作。中間層的服務器是最上層服務器的從服務器,又是最下層服務器的主服務器。
一個簡單的論壇系統
該論壇系統功能如下:
- 可以發布文章
- 可以對文章進行點贊
- 在首頁可以按文章的發布時間或者文章的點贊數進行排序顯示
文章信息
文章包括標題、作者、贊數等信息,在關系型數據庫中很容易構建一張表來存儲這些信息,在 Redis 中可以使用 HASH 來存儲每種信息以及其對應的值的映射。
Redis 沒有關系型數據庫中的表這一概念來將同種類型的數據存放在一起,而是使用命名空間的方式來實現這一功能。鍵名的前面部分存儲命名空間,后面部分的內容存儲 ID,通常使用 : 來進行分隔。例如下面的 HASH 的鍵名為 article:92617,其中 article 為命名空間,ID 為 92617。
點贊功能
當有用戶為一篇文章點贊時,除了要對該文章的 votes 字段進行加 1 操作,還必須記錄該用戶已經對該文章進行了點贊,防止用戶點贊次數超過 1。可以建立文章的已投票用戶集合來進行記錄。
為了節約內存,規定一篇文章發布滿一周之后,就不能再對它進行投票,而文章的已投票集合也會被刪除,可以為文章的已投票集合設置一個一周的過期時間就能實現這個規定。
對文章進行排序
為了按發布時間和點贊數進行排序,可以建立一個文章發布時間的有序集合和一個文章點贊數的有序集合。(下圖中的 score 就是這里所說的點贊數;下面所示的有序集合分值并不直接是時間和點贊數,而是根據時間和點贊數間接計算出來的)