redis 5種數據類型
string
字符串是 Redis 里最基礎的數據類型,一個鍵對應一個值。
設置值
SET key value
例如:
SET name "John"
獲取值
GET key
例如:
GET name
list
列表是簡單的字符串列表,按插入順序排序。
在列表頭部添加元素
LPUSH key value1 [value2 ...]
例如:
LPUSH mylist "apple" "banana"
從列表頭部移除并返回元素
LPOP key
例如:
LPOP mylist
set
集合是字符串的無序集合,且每個元素唯一。
添加元素到集合
SADD key member1 [member2 ...]
例如:
SADD myset "red" "green"
獲取集合中的所有元素
SMEMBERS key
例如:
SMEMBERS myset
hash
哈希類型是鍵值對的集合,特別適合用于存儲對象。
設置字段值
HSET key field value
例如:
HSET user:1 name "John" age 30
獲取字段值
HGET key field
例如:
HGET user:1 name
zset
有序集合和集合類似,不過每個成員都關聯了一個分數,分數用于對成員進行排序。
添加成員到有序集合
ZADD key score1 member1 [score2 member2 ...]
例如:
ZADD myzset 1 "one" 2 "two"
獲取有序集合中指定范圍的成員
ZRANGE key start stop [WITHSCORES]
例如:
ZRANGE myzset 0 -1 WITHSCORES
redis 5種基本數據類型使用場景
- 字符串(String)
- 緩存數據:常用于緩存各種類型的數據,如數據庫查詢結果、頁面片段、JSON 數據等。例如,將一個復雜的數據庫查詢結果緩存為字符串,下次請求相同數據時可以直接從 Redis 中獲取,減少數據庫查詢壓力。
計數器:利用字符串類型的原子遞增 / 遞減操作,可以輕松實現計數器功能。比如,記錄網站的訪問量、文章的點贊數、視頻的播放量等。 - 分布式鎖:通過設置一個具有特定過期時間的字符串鍵來實現分布式鎖。當一個客戶端成功設置了這個鍵,就表示它獲取了鎖,其他客戶端在鎖過期之前無法獲取鎖,以此來保證在分布式環境下的資源互斥訪問。
- 緩存數據:常用于緩存各種類型的數據,如數據庫查詢結果、頁面片段、JSON 數據等。例如,將一個復雜的數據庫查詢結果緩存為字符串,下次請求相同數據時可以直接從 Redis 中獲取,減少數據庫查詢壓力。
- 哈希(Hash)
- 存儲對象:非常適合存儲對象的相關信息,如用戶信息、商品信息等。可以將對象的各個屬性作為哈希的字段,屬性值作為字段的值,這樣可以方便地對對象的屬性進行單獨的查詢、更新等操作,而不需要像在關系型數據庫中那樣進行復雜的表連接操作。
- 配置信息管理:用于存儲應用的配置信息,將配置項作為字段,配置值作為字段值。這樣可以方便地在運行時動態修改配置信息,而不需要重新啟動應用程序。
- 列表(List)
- 消息隊列:可以作為簡單的消息隊列使用,生產者將消息通過 LPUSH 命令從列表頭部插入,消費者通過 RPOP 命令從列表尾部取出消息,實現異步消息處理。例如,在一個電商系統中,訂單生成后可以將訂單信息放入一個列表中,由后臺的訂單處理服務從列表中取出訂單進行處理。
- 任務隊列:與消息隊列類似,用于存儲待處理的任務。不同的是,任務隊列可能會根據任務的優先級、類型等進行分類存儲,消費者可以根據自身的能力和需求從不同的列表中獲取任務進行處理。
歷史記錄和日志:用于記錄用戶的操作歷史、系統的日志信息等。可以按照時間順序將相關信息通過 RPUSH 命令插入到列表中,然后根據需要可以通過 LRANGE 命令獲取指定范圍的歷史記錄或日志。
- 集合(Set)
- 標簽和分類:用于存儲對象的標簽或分類信息。例如,在一個文章系統中,每篇文章可以有多個標簽,將文章的標簽存儲在一個集合中,方便進行標簽的添加、刪除和查詢操作,也可以輕松實現根據標簽進行文章的篩選和分類。
- 去重:由于集合中的元素是唯一的,因此可以用于對數據進行去重處理。例如,在處理用戶注冊信息時,可以將用戶的郵箱或手機號碼存儲在一個集合中,當有新用戶注冊時,只需檢查集合中是否已存在該郵箱或手機號碼,就可以快速判斷用戶是否重復注冊。
- 交集、并集和差集運算:可以方便地對多個集合進行交集、并集和差集等集合運算。例如,在社交網絡中,可以通過集合運算找出兩個用戶共同關注的人(交集),或者某個用戶關注的所有人與另一個用戶關注的所有人的總和(并集),以及某個用戶關注但另一個用戶未關注的人(差集)。
- 有序集合(Sorted Set)
- 排行榜:常用于實現各種排行榜功能,如游戲排行榜、商品銷量排行榜、用戶積分排行榜等。將排行榜的對象(如用戶 ID、商品 ID 等)作為有序集合的成員,將對應的排名依據(如積分、銷量等)作為分數,通過 ZRANK、ZREVRANK 等命令可以快速獲取某個成員的排名,通過 ZRANGE、ZREVRANGE 等命令可以獲取排行榜的前幾名或后幾名。
- 帶權重的任務隊列:與普通的列表任務隊列不同,有序集合可以根據任務的優先級或權重來進行排序。將任務的權重作為分數,任務的標識作為成員,這樣消費者在獲取任務時,可以優先獲取權重高的任務進行處理。
- 地理位置信息存儲和查詢:可以將地理位置信息(如經緯度)存儲在有序集合中,通過計算成員之間的距離來實現附近地點查詢等功能。例如,在一個外賣應用中,可以通過有序集合存儲商家的地理位置信息,當用戶查詢附近的商家時,可以根據用戶的位置計算與各個商家的距離,并按照距離進行排序返回。
redis 5種基本數據類型實現原理
Redis 的 5 種基本數據類型分別是字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set),下面為詳細介紹它們的實現原理。
- 字符串(String)
- 簡單動態字符串(SDS):Redis 并沒有直接使用 C 語言的字符串,而是采用簡單動態字符串(SDS)來實現。SDS 結構包含字符串長度、剩余空間和實際存儲的字符串內容。這種結構讓 Redis 能高效地執行字符串操作。
- 優勢:獲取字符串長度的時間復雜度為 O(1);能避免緩沖區溢出;可以二進制安全地存儲任意數據;并且在修改字符串時,通過預分配和惰性釋放機制減少內存重新分配的次數。
struct sdshdr {//記錄buf數組中已使用字節的數量//等于SDS所保存字符串的長度int len;//記錄buf數組中未使用字節的數量,表示聲音可用空間int free;//字節數組,用于存放字符串char buf[];
};
- 哈希(Hash)
- 實現方式:Redis 的哈希類型有兩種實現方式,分別是壓縮列表(ziplist)和哈希表(hashtable)。
- 壓縮列表:當哈希元素數量較少且每個元素的鍵值長度較短時,Redis 會使用壓縮列表來存儲哈希。壓縮列表是一種連續內存塊組成的順序數據結構,能有效節省內存。
- 哈希表:當哈希元素數量增多或者鍵值長度變長時,Redis 會將存儲方式轉換為哈希表。哈希表使用鏈地址法來解決哈希沖突,每個哈希桶是一個鏈表,當鏈表過長時會進行 rehash 操作,將數據重新分配到新的哈希表中。
- 壓縮列表是Redis為了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序性(sequential)數據結構。一個壓縮列表可以包含任意多個節點(entry),每個節點可以保存一個字節數組或者一個整數值
??下圖是壓縮列表的各個組成部分:


下圖是一個壓縮列表示例:

- 列表(List)
- 實現方式:Redis 的列表類型也有兩種實現方式,即壓縮列表和雙向鏈表。
- 壓縮列表:與哈希類型類似,在元素數量少且元素長度短時,使用壓縮列表存儲,節省內存。
- 雙向鏈表:當元素數量增多或元素長度變長時,使用雙向鏈表存儲。雙向鏈表的每個節點包含指向前一個節點和后一個節點的指針,方便進行插入、刪除和遍歷操作。
- 集合(Set)
- 實現方式:集合類型的實現方式有整數集合(intset)和哈希表。
- 整數集合:當集合中的元素都是整數,并且元素數量較少時,Redis 使用整數集合存儲。整數集合是一個有序的整數數組,能高效地存儲和查找整數元素。
- 哈希表:當集合中的元素包含非整數或者元素數量增多時,Redis 會使用哈希表存儲。哈希表的鍵就是集合的元素,值統一為 null,通過哈希表的特性保證元素的唯一性。
- 整數集合(intset)可以保存類型為int16_t、int32_t、int64_t的整數值,并且保證集合中不會出現重復元素。整數集合有序、無重復

- 有序集合(Sorted Set)
- 實現方式:有序集合采用跳躍表(skiplist)和哈希表的組合來實現。
- 跳躍表:跳躍表是一種有序的數據結構,它在鏈表的基礎上增加了多層索引,使得查找、插入和刪除操作的平均時間復雜度O(logN)。在有序集合中,跳躍表根據元素的分數進行排序,方便進行范圍查找。
- 哈希表:哈希表用于存儲元素和分數的映射關系,使得可以在 O(1)時間復雜度內根據元素獲取其分數。
Redis 中的有序集合zset是一種非常重要的數據結構,它通過巧妙地結合哈希表和跳表來實現高效的存儲和操作。以下是zset對哈希表和跳表的具體使用方式:
- 哈希表的使用
- 實現成員到分數的映射:zset中的哈希表用于存儲成員到分數的映射關系。這樣,通過成員可以快速查找對應的分數,時間復雜度為 (O(1))。例如,在一個存儲學生成績的zset中,學生的姓名作為成員,成績作為分數,通過哈希表可以迅速根據學生姓名找到其成績。
- 實現快速存在性檢查:利用哈希表的特性,可以快速判斷一個成員是否存在于zset中。這在一些應用場景中非常有用,比如在實時統計在線用戶的活躍時間時,能夠快速判斷某個用戶是否已經在zset中,以便進行相應的操作。
- 跳表的使用
- 實現按分數排序:跳表是一種多層的有序鏈表結構,zset中的跳表按照分數對成員進行排序。在跳表中,每個節點包含成員、分數以及指向其他節點的指針。通過這種結構,可以在 (O(log n)) 的時間復雜度內完成對有序集合的插入、刪除和查找操作。例如,在一個存儲商品銷量排名的zset中,通過跳表可以快速找到銷量最高的商品,或者根據銷量范圍查找相應的商品列表。
- 支持范圍查詢:跳表的結構使得zset能夠高效地支持范圍查詢。例如,可以快速查找分數在某個區間內的所有成員,或者按照排名范圍獲取相應的成員列表。這在很多實際應用中非常常見,比如查找成績排名在班級前 10% 的學生,或者查找熱度排名在某個范圍內的文章等。
redis持久化
Redis 持久化是指將 Redis 內存中的數據保存到硬盤等持久化存儲介質中,以便在 Redis 服務器重啟或故障時能夠恢復數據,保證數據的安全性和可靠性。Redis 提供了兩種主要的持久化方式:RDB(Redis Database)持久化和 AOF(Append Only File)持久化。
RDB 持久化
工作原理
RDB 持久化是通過將 Redis 在內存中的數據庫狀態保存到磁盤上的 RDB 文件來實現的。它可以在指定的時間間隔內,對內存中的數據進行快照,將數據以二進制的格式寫入到 RDB 文件中。當 Redis 服務器重啟時,它會自動加載 RDB 文件,將數據恢復到內存中。
觸發方式:
- 自動觸發:可以通過配置 save 命令來設置自動觸發 RDB 持久化的條件。例如,save 900 1 表示在 900 秒內如果至少有 1 個鍵發生了修改,就會觸發 RDB 持久化;save 300 10 表示在 300 秒內如果至少有 10 個鍵發生了修改,就會觸發 RDB 持久化。可以配置多個 save 條件,只要滿足其中一個條件,就會觸發 RDB 持久化。
- 手動觸發:可以使用 SAVE 或 BGSAVE 命令手動觸發 RDB 持久化。SAVE 命令會阻塞 Redis 服務器進程,直到 RDB 文件生成完畢;BGSAVE 命令則會在后臺 fork 一個子進程來執行 RDB 持久化操作,不會阻塞服務器進程。
優點:
- 數據恢復快:RDB 文件是一個緊湊的二進制文件,在恢復數據時,Redis 可以快速地將 RDB 文件中的數據加載到內存中,相比 AOF 方式,恢復速度通常更快。
- 適合大規模數據備份:RDB 文件可以用于數據的定期備份,例如可以將 RDB 文件復制到其他存儲設備或服務器上,以防止數據丟失。由于 RDB 文件是一個整體,因此在進行大規模數據備份和恢復時非常方便。
缺點:
- 數據可能丟失:由于 RDB 是按照一定的時間間隔進行快照的,如果在兩次快照之間發生了服務器故障,那么這期間的數據將會丟失。例如,如果設置了每 5 分鐘進行一次 RDB 快照,而在第 4 分鐘時服務器發生故障,那么這 4 分鐘內的數據將無法恢復。
- fork 子進程開銷:在執行 BGSAVE 命令時,Redis 需要 fork 一個子進程來進行 RDB 文件的生成。fork 操作會消耗一定的系統資源,并且如果內存中的數據量很大,fork 過程可能會比較耗時,甚至會導致服務器短暫的阻塞。

AOF 持久化
- 工作原理:AOF 持久化是將 Redis 服務器執行的寫命令以追加的方式記錄到 AOF 文件中。當 Redis 服務器重啟時,它會重新執行 AOF 文件中的命令,將數據恢復到內存中。
- 觸發方式:AOF 持久化是在每次執行寫命令時,將命令追加到 AOF 文件中。不過,實際的寫入磁盤操作并不是立即執行的,而是根據配置的 appendfsync 參數來決定何時將數據真正寫入磁盤。
優點:
- 數據完整性高:由于 AOF 是記錄每一個寫命令,因此可以保證數據的完整性。只要 AOF 文件沒有損壞,就可以通過重新執行 AOF 文件中的命令來恢復到故障前的狀態,相比 RDB 方式,數據丟失的風險更小。
- 可讀性好:AOF 文件是一個文本文件,其中記錄了 Redis 執行的所有寫命令,因此可以很容易地查看和分析 AOF 文件中的內容,對于故障排查和數據審計非常有幫助。
缺點:
- 文件體積大:隨著 Redis 服務器的運行,AOF 文件會不斷增大,因為它記錄了所有的寫命令。這可能會導致磁盤空間不足,并且在恢復數據時,由于需要執行大量的命令,恢復速度相對較慢。
- 恢復速度慢:相比 RDB,AOF 在恢復數據時需要重新執行大量的寫命令,因此恢復速度會比較慢。特別是當 AOF 文件非常大時,恢復過程可能會消耗很長時間。

AOF重寫是什么
- 背景:由于AOF文件里記錄的是寫命令,那命令越來越多,AOF文件就會越來越大,數據還原時間就會越來越長。比如我連續rpush key v1-6,執行6次,AOF就需要保存6條命令。
- AOF文件重寫:讀取Redis數據庫中的數據,而不是讀取現有的AOF文件。比如舊AOF文件記錄了6條rpush命令,那么新AOF文件會從數據庫中讀取鍵現在的值,然后用一條命令去記錄鍵值對,這樣6條命令變成了一條。重新完后新的AOF文件會替換舊AOF文件。但是新AOF文件不會包含浪費空間的冗余命令,所以體積較小,這就解決了AOF文件體積膨脹的問題
- 特殊情況,比如一個集合鍵有100個元素,按理說AOF文件中會記錄SADD key + 100個元素,實際上,這個命令太長了,執行時會造成客戶端緩沖區溢出,所以重寫程序在處理列表、哈希表、集合和有序集合這4種鍵時,會見檢查元素量,如果元素超過64個,就會用多條命令記錄鍵的值,比如SADD100個元素,那么分2條命令,一條是SADD 64個,另一條是SADD36個。
AOF有哪幾種日志策略
- AOF_FSYNC_ALWAYS:每執行一次命令就保存一下數據,性能低
- AOF_FSYNC_NO:不主動將AOF文件的內存從緩存保存到磁盤中,交給操作系統去決定何時保存到磁盤(比如Redis被關閉,AOF功能被關閉的時候)
- AOF_FSYNC_EVERYSE:每秒保存一次
混合持久化
從 Redis 4.0 開始,支持混合持久化方式。這種方式結合了 RDB 和 AOF 的優點,在進行持久化時,首先會使用 RDB 方式將內存中的數據進行快照,然后將 RDB 數據和后續的寫命令以 AOF 的格式追加到同一個文件中。在恢復數據時,先加載 RDB 部分的數據,然后再執行 AOF 部分的命令,這樣既可以提高數據恢復的速度,又可以保證數據的完整性。
持久化配置
在 Redis 的配置文件中,可以通過以下參數來配置持久化方式和相關參數:
- save:用于配置 RDB 持久化的觸發條件,如 save 900 1 save 300 10 等。
- appendonly:用于開啟或關閉 AOF 持久化,appendonly yes 表示開啟 AOF 持久化,appendonly no 表示關閉 AOF 持久化。
- appendfsync:用于配置 AOF 文件的同步策略,有 always、everysec 和 no 三個可選值。always 表示每次寫命令都立即同步到磁盤,數據安全性最高,但性能最低;everysec 表示每秒將 AOF 緩沖區中的數據同步到磁盤,這是默認的配置,兼顧了數據安全性和性能;no 表示由操作系統來決定何時將數據同步到磁盤,性能最高,但數據安全性最低。
可以根據實際應用場景和對數據安全性、性能的要求,選擇合適的持久化方式和配置參數。通常情況下,可以同時開啟 RDB 和 AOF 持久化,以提供更可靠的數據保護。
redis發布訂閱
Redis 發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。微信、微博、關注系統!
Redis客戶端可以訂閱任意數量的頻道
訂閱/發布消息圖:

下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關系:

當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個客戶端:

命令
這些命令被廣泛用于構建即時通信應用,比如網絡聊天室(chatroom)和實時廣播、實時提醒等。
命令
PSUBSCRIBE pattern [pattern…] 訂閱一個或多個符合給定模式的頻道。
PUNSUBSCRIBE pattern [pattern…] 退訂一個或多個符合給定模式的頻道。
PUBSUB subcommand [argument[argument]] 查看訂閱與發布系統狀態。
PUBLISH channel message 向指定頻道發布消息
SUBSCRIBE channel [channel…] 訂閱給定的一個或多個頻道。
SUBSCRIBE channel [channel…] 退訂一個或多個頻道



127.0.0.1:6379> subscribe blog # 訂閱頻道
Reading messages... (press Ctrl-C to quit) # 等待推送信息
1) "subscribe"
2) "blog"
3) (integer) 1
1) "message" # 消息
2) "blog" # 消息來自頻道
3) "hello world!" # 消息內容
127.0.0.1:6379> publish blog "hello world!" # 發送消息到頻道
(integer) 1
127.0.0.1:6379>
原理


每個 Redis 服務器進程都維持著一個表示服務器狀態的 redis.h/redisServer 結構, 結構的 pubsub_channels 屬性是一個字典, 這個字典就用于保存訂閱頻道的信息,其中,字典的鍵為正在被訂閱的頻道, 而字典的值則是一個鏈表, 鏈表中保存了所有訂閱這個頻道的客戶端。
客戶端訂閱,就被鏈接到對應頻道的鏈表的尾部,退訂則就是將客戶端節點從鏈表中移除。
使用場景:
實時消息系統!
實時聊天!(頻道當作聊天室,將信息回顯給所有人)
訂閱,關注系統都是可以
復雜的情況,使用專業的消息中間件
做訂閱的缺點
如果一個客戶端訂閱了頻道,但自己讀取消息的速度卻不夠快的話,那么不斷積壓的消息會使redis輸出緩沖區的體積變得越來越大,這可能使得redis本身的速度變慢,甚至直接崩潰。
這和數據傳輸可靠性有關,如果在訂閱方斷線,那么他將會丟失所有在短線期間發布者發布的消息。
redis事務
Redis 事務允許在一個步驟中執行一組命令,并且保證這些命令作為一個單獨的、不可分割的操作序列執行。
事務的基本概念
Redis 事務通過 MULTI、EXEC、DISCARD 和 WATCH 這幾個命令來實現。其基本特性包括:
- 原子性:事務中的所有命令要么全部執行,要么全部不執行。不過,Redis 事務的原子性是有限的,在執行過程中如果某條命令執行失敗,后續命令仍會繼續執行。
- 隔離性:在事務執行期間,不會有其他客戶端的命令插入到事務執行序列中,保證事務的操作是相互隔離的。
事務的基本命令及使用示例
- MULTI
用于開啟一個事務塊,后續輸入的命令會被放入事務隊列中,而不會立即執行。
MULTI
- EXEC
執行事務隊列中的所有命令。如果在執行 EXEC 之前沒有使用 DISCARD 取消事務,那么隊列中的命令將按順序依次執行。
EXEC
- DISCARD
取消當前事務,清空事務隊列,之前放入隊列中的命令不會被執行。
DISCARD
- WATCH
用于監視一個或多個鍵,在事務執行之前,如果被監視的鍵被其他客戶端修改,那么事務將被取消,EXEC 命令會返回 nil。
WATCH key1 key2 ...
事務使用示例
下面是一個簡單的 Redis 事務使用示例,展示了如何使用 MULTI 和 EXEC 來執行一組命令:
# 開啟事務
MULTI
# 設置鍵值對
SET key1 "value1"
SET key2 "value2"
# 獲取鍵的值
GET key1
GET key2
# 執行事務
EXEC
在這個示例中,SET 和 GET 命令會被依次放入事務隊列中,當執行 EXEC 命令時,這些命令會按順序執行,并返回每個命令的執行結果。
Redis 事務的局限性
- 錯誤處理:在事務隊列中,如果某條命令存在語法錯誤,整個事務在執行 EXEC 時會失敗;但如果是在執行過程中某個命令執行失敗(例如對一個非哈希類型的鍵執行哈希操作),其他命令仍會繼續執行,不會回滾已經執行的命令。
- 不支持嵌套事務:Redis 不支持在一個事務中嵌套另一個事務。
結合 WATCH 實現樂觀鎖
WATCH 命令可以用于實現樂觀鎖機制,保證在并發環境下數據的一致性。以下是一個使用 WATCH 的示例:
# 監視鍵
WATCH key
# 開啟事務
MULTI
# 獲取鍵的值
GET key
# 修改鍵的值
SET key "new_value"
# 執行事務
EXEC
在這個示例中,如果在執行 EXEC 之前,key 被其他客戶端修改,那么事務將被取消,EXEC 會返回 nil。客戶端可以根據返回結果來決定是否重新嘗試執行事務
測試多線程修改值,使用watch可以當作redis的樂觀鎖操作
①開啟兩個客戶端,模擬多線程情況

②左邊支出20元(但是不執行事務),然后右邊修改money的數值

③左邊執行事務,發現執行操作返回nil,查看money和out,發現事務并沒有被執行(確實有樂觀鎖的效果)

如果修改失敗獲取最新的值就好(exec、unwatch、discard都可以清除連接時所有的監視)

小結
使用Redis實現樂觀鎖(watch監聽某一個key,獲取其最新的value)
在提交事務時,如果key的value沒有發生變化,則成功執行
在提交事務時,如果key的value發生了變化,則無法成功執行
redis部署模式
Redis 有多種部署模式,以滿足不同應用場景下的性能、可靠性和可擴展性需求。
單機模式
- 部署方式:將 Redis 服務器安裝在一臺獨立的服務器上,所有數據都存儲在這臺服務器的內存中。這是最簡單的部署方式,適用于小型應用或開發測試環境。
- 優點:部署簡單,易于管理和維護。成本較低,只需要一臺服務器。適用于數據量較小、并發訪問量不高的場景。
- 缺點:單點故障問題,如果服務器出現故障,整個系統將無法正常運行。內存容量有限,受限于服務器的內存大小。性能也受限于單臺服務器的處理能力,無法滿足高并發、大規模數據處理的需求。
主從復制模式
- 部署方式:由一個主節點(Master)和多個從節點(Slave)組成。主節點負責處理所有的寫操作,并將數據異步復制到從節點。從節點可以接受讀請求,分擔主節點的讀壓力。
- 優點:提高了系統的讀性能,通過增加從節點可以水平擴展讀能力。實現了數據的冗余備份,從節點可以在主節點出現故障時提供數據恢復。
- 缺點:主節點仍然是單點故障,如果主節點出現故障,需要手動或通過其他機制將從節點提升為主節點,否則整個系統的寫操作將無法進行。數據復制存在一定的延遲,特別是在網絡環境較差的情況下,可能會導致從節點的數據與主節點不一致。
Sentinel(哨兵)模式
- 部署方式:在主從復制模式的基礎上,引入了 Sentinel 進程。Sentinel 用于監控 Redis 主節點和從節點的狀態,當主節點出現故障時,自動進行故障轉移,將一個從節點提升為主節點,并通知其他從節點和客戶端新的主節點地址。
- 優點:實現了自動故障轉移,提高了系統的可用性。多個 Sentinel 節點可以組成集群,避免了 Sentinel 單點故障問題。
- 缺點:Sentinel 本身也需要一定的資源來運行,增加了系統的復雜性和運維成本。在故障轉移過程中,可能會有短暫的服務中斷,并且可能會丟失部分未及時復制到從節點的數據。
集群模式
- 部署方式:將數據分布在多個節點上,每個節點負責存儲一部分數據。節點之間通過 gossip 協議進行通信,以維護集群的狀態。客戶端可以連接到集群中的任意節點,通過請求重定向機制來訪問所需的數據。
- 優點:可以水平擴展系統的存儲和處理能力,通過增加節點來應對大規模數據和高并發訪問。數據在多個節點上分布存儲,提高了數據的可靠性和容錯能力。
- 缺點:集群的部署和管理相對復雜,需要考慮數據的分片、節點的故障處理、數據遷移等問題。對客戶端的支持要求較高,需要客戶端能夠感知集群的拓撲結構并進行請求的正確路由。
云托管模式
- 部署方式:將 Redis 部署在云服務提供商提供的托管環境中,如阿里云的 Redis 云數據庫、騰訊云的 Redis 數據庫等。用戶無需關心底層的服務器硬件和運維管理,由云服務提供商負責服務器的部署、維護、監控和故障處理等工作。
- 優點:用戶可以快速創建和部署 Redis 實例,無需進行復雜的安裝和配置工作。云服務提供商通常提供了高可用性、自動備份、性能監控等一系列的增值服務,降低了用戶的運維成本。可以根據業務需求靈活調整實例的規格和配置,實現資源的彈性伸縮。
- 缺點:用戶對底層服務器的控制能力有限,無法進行一些底層的優化和定制化操作。費用相對較高,尤其是對于大規模、高性能的 Redis 實例,使用云托管模式的成本可能會比較高。
在實際應用中,需要根據業務的需求、數據量、并發量、預算等因素綜合考慮,選擇合適的 Redis 部署模式。同時,還需要注意數據的備份、安全防護等方面的問題,以確保 Redis 系統的穩定運行和數據的安全可靠。
面試題
redis實現分布式鎖的原理
基本原理
Redis 分布式鎖的核心思想是利用 Redis 的原子性操作,在多個客戶端嘗試獲取鎖時,只有一個客戶端能夠成功設置特定的鍵值對,這個客戶端就被認為獲取到了鎖。其他客戶端在鎖被釋放之前無法再次設置該鍵值對,從而實現了資源的互斥訪問。
實現步驟與原理細節
- 獲取鎖
原子操作:客戶端使用SET命令來嘗試獲取鎖,SET命令需要帶上特定的參數,格式為SET key value NX PX timeout。
key:作為鎖的唯一標識,通常可以使用業務相關的名稱,例如order🔒123表示訂單 ID 為 123 的鎖。
value:可以是一個唯一的隨機字符串,用于在釋放鎖時進行驗證,防止誤釋放其他客戶端的鎖。
NX:表示只有當鍵不存在時才進行設置操作,如果鍵已經存在則設置失敗,這保證了同一時間只有一個客戶端能夠設置成功。
PX timeout:設置鍵的過期時間,單位為毫秒。這樣做是為了防止客戶端在獲取鎖后由于某些原因(如崩潰)未能正常釋放鎖,導致其他客戶端永遠無法獲取該鎖,造成死鎖。
示例代碼:
SET order:lock:123 "unique_value_123" NX PX 5000
若命令返回OK,則表示客戶端成功獲取到了鎖;若返回nil,則表示鎖已被其他客戶端持有,當前客戶端獲取鎖失敗。
2. 執行業務邏輯
當客戶端成功獲取到鎖后,就可以執行需要互斥訪問的業務邏輯,例如對共享資源進行讀寫操作。
3. 釋放鎖
驗證與刪除:釋放鎖時,客戶端需要先驗證鎖的value是否與自己設置的一致,以避免誤釋放其他客戶端的鎖。這可以通過執行 Lua 腳本來保證操作的原子性。
Lua 腳本示例:
if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])
elsereturn 0
end
執行 Lua 腳本:在 Redis 客戶端中可以使用EVAL命令來執行上述 Lua 腳本,示例如下:
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 order:lock:123 "unique_value_123"
分布式鎖的安全性和可靠性考慮
- 鎖的過期時間:需要合理設置鎖的過期時間,若過期時間過短,可能會導致業務邏輯還未執行完鎖就已經過期,從而引發并發問題;若過期時間過長,可能會在客戶端異常時導致其他客戶端長時間無法獲取鎖。
- 鎖的可重入性:在某些場景下,可能需要支持鎖的可重入性,即同一個客戶端在持有鎖的情況下可以再次獲取該鎖,這可以通過在鎖的value中記錄客戶端的標識和重入次數來實現。
- Redis 集群環境:在 Redis 集群環境中,需要考慮鎖的一致性問題。可以使用 Redlock 算法來提高鎖的可靠性,該算法通過在多個 Redis 節點上獲取鎖,只有當在大多數節點上都成功獲取到鎖時,才認為客戶端獲取鎖成功。
redis緩存雪崩、緩存穿透、緩存擊穿
Redis 緩存擊穿、穿透和雪崩是在使用 Redis 作為緩存時常見的問題,下面將詳細描述它們的概念、產生原因、帶來的影響以及解決方案。
緩存擊穿
- 概念:緩存擊穿是指熱點數據在緩存中過期的瞬間,大量并發請求同時訪問該數據,這些請求會繞過緩存直接訪問數據庫,導致數據庫壓力瞬間增大。
- 產生原因:一般是由于某個熱點數據的緩存過期時間設置不合理,或者在高并發場景下,緩存過期的瞬間有大量請求同時到達。
- 影響:可能會使數據庫的負載瞬間過高,甚至可能導致數據庫響應緩慢或崩潰,影響整個系統的性能和穩定性。
- 解決方案:
- 設置熱點數據永不過期:對于一些經常被訪問的熱點數據,可以設置其在緩存中永不過期,但需要注意內存的使用情況,避免內存溢出。
- 使用互斥鎖:在緩存過期時,只允許一個請求去查詢數據庫并更新緩存,其他請求則等待該請求完成后從緩存中獲取數據。可以使用 Redis 的分布式鎖來實現。
緩存穿透
- 概念:緩存穿透是指客戶端請求的數據在緩存中不存在,并且在數據庫中也不存在,導致請求直接穿透緩存訪問數據庫。如果有大量這樣的請求,會使數據庫壓力增大。
- 產生原因:可能是由于惡意攻擊,如黑客故意發送不存在的 key 進行大量請求;也可能是業務邏輯問題,導致某些數據在緩存和數據庫中都沒有被正確存儲。
- 影響:大量請求直接訪問數據庫,可能導致數據庫性能下降,甚至引發數據庫故障,影響系統的正常運行。
- 解決方案:
- 布隆過濾器:在緩存之前使用布隆過濾器來判斷數據是否存在。布隆過濾器是一種概率型數據結構,它可以快速判斷一個元素是否在一個集合中,具有空間效率高、查詢速度快的特點。當布隆過濾器判斷數據不存在時,直接返回,不再訪問數據庫。
- 緩存空值:當查詢數據庫發現數據不存在時,也將空值緩存起來,并設置一個較短的過期時間,這樣后續相同的請求就可以直接從緩存中獲取空值,而不會再穿透到數據庫。
緩存雪崩
- 概念:緩存雪崩是指在某一時刻,緩存中大量的數據同時過期,導致大量請求同時訪問數據庫,使數據庫壓力驟增,甚至可能導致數據庫崩潰,進而影響整個系統的可用性。
- 產生原因:通常是因為緩存數據的過期時間設置過于集中,或者 Redis 服務器出現故障,導致緩存中的數據全部丟失。
- 影響:數據庫可能會因為無法承受巨大的并發壓力而崩潰,從而使整個系統無法正常提供服務,造成嚴重的業務影響。
- 解決方案:
- 分散過期時間:避免將大量緩存數據的過期時間設置為同一時刻,而是將過期時間分散在一個時間段內,例如可以在原來的過期時間基礎上加上一個隨機的時間偏移量,這樣可以使緩存數據的過期時間更加均勻地分布,減少同時過期的可能性。
- 使用多級緩存:采用多級緩存架構,如本地緩存(如 Ehcache)和分布式緩存(如 Redis)相結合。當分布式緩存中的數據過期或不可用時,可以先從本地緩存中獲取數據,減輕數據庫的壓力。
- 數據預熱:在系統啟動時,提前將一些熱點數據加載到緩存中,避免在系統運行過程中突然出現大量數據同時過期的情況。
搭建 Redis 集群:通過搭建 Redis 集群來提高 Redis 的可用性和可靠性,避免因單個 Redis 節點故障導致緩存數據全部丟失。同時,可以使用 Redis 的持久化功能,如 RDB 和 AOF,在服務器重啟后能夠快速恢復數據。
redis 3種特殊數據類型
redis一致性hash
一致性哈希的基本原理是:把所有的哈希值空間組織成一個虛擬的圓環(哈希環),整個空間按照順時針方向組織,其中0和最高位2^32-1是重疊的。
假設我們有四臺機器要哈希環來實現映射(分布數據),我們先根據機器的名稱或者ip計算哈希值,然后分布到環中,也就是圖中紅色標記。

現在有4條數據或者4個訪問請求,對key計算后,得到哈希環中的位置(綠色位置)。沿哈希環順時針找到的一個Node,就是數據存儲的結點。

在這種情況下,新增一個Node5結點 ,只影響一部分數據的分布,如下:

如果我們刪除一個結點Node4,只影響相鄰的一個結點。

一致性哈希解決了動態增減結點時,所有數據都需要重新分布的問題,它只會影響到下一個相鄰的結點,對其他結點沒有影響。
但是這樣的一致性哈希有一個缺點,因為結點不一定是均勻分布的,特別是在節點數比較少的情況下,所以數據不能得到均勻分布。解決這個問題的辦法是引入虛擬結點
比如,2個節點,5條數據 ,只有1條分布到Node2,4條分布到Node1,不均勻。

Node1設置了兩個虛擬結點,Node2也設置了兩個虛擬結點(虛線圓圈),這時候有3條數據分不到Node1,1條分布到Node2。

一致性Hash在分布式系統中很多場景都有應用,例如負載均衡、分庫分表等等,是一個非常重要的基礎算法。
來自豆包
Redis 本身并不直接實現一致性哈希算法,但在 Redis 集群和分布式緩存系統中,一致性哈希算法有著重要的應用,下面為你詳細介紹。
一致性哈希的基本概念
一致性哈希是一種特殊的哈希算法,在傳統哈希算法里,若哈希空間大小改變(如增加或減少服務器節點),幾乎所有的鍵都會被重新映射,這會帶來大量的數據遷移。而一致性哈希算法能夠盡可能減少鍵的重新映射,在節點數量變化時,只有一小部分鍵需要重新映射到新的節點,從而提高系統的可擴展性和穩定性。
一致性哈希的原理
- 構建哈希環
首先,確定一個哈希空間,通常是一個 2^32 大小的整數環(范圍從 0 到 2**32 - 1)。
接著,使用哈希函數(如 MD5、SHA - 1 等)將服務器節點的標識(如 IP 地址、主機名)映射到這個哈希環上的某個點。 - 鍵的映射
同樣運用哈希函數,把數據的鍵映射到哈希環上的某個點。
沿著哈希環順時針查找,找到第一個遇到的服務器節點,就把該鍵存儲到這個節點上。 - 節點的添加與刪除
添加節點:當新增一個服務器節點時,只需將哈希環上從新增節點位置開始,順時針方向到下一個節點之間的鍵重新映射到新節點,其他鍵的映射關系保持不變。
刪除節點:若刪除一個服務器節點,將該節點上的鍵重新映射到哈希環上順時針方向的下一個節點,其余鍵的映射不受影響。
一致性哈希在 Redis 中的應用場景
- Redis 集群
在 Redis 集群環境中,需要把數據均勻地分布到多個節點上。一致性哈希算法可以讓數據在節點數量變化時,只有少量數據需要遷移,減少了數據遷移的開銷,保證了集群的穩定性和可用性。 - 分布式緩存
在分布式緩存系統里,使用一致性哈希算法能把緩存數據均勻地分布到多個緩存節點上。當某個緩存節點出現故障或需要添加新的緩存節點時,只有部分緩存數據需要重新分布,降低了緩存失效的范圍。
一致性哈希的優缺點
優點
節點增減時數據遷移少:在服務器節點數量發生變化時,只有少數數據的映射關系需要調整,大大減少了數據遷移的工作量。
負載均衡:能夠將數據均勻地分布到各個節點上,避免了部分節點負載過重的問題。
缺點
數據分布不均:若服務器節點在哈希環上分布不均勻,可能會導致部分節點負載過高,而部分節點負載過低。
虛擬節點開銷:為了解決數據分布不均的問題,通常會引入虛擬節點,但這會增加額外的計算和存儲開銷。
簡單的 Python 示例代碼
import hashlibclass ConsistentHashing:def __init__(self, replicas=3):self.replicas = replicasself.ring = {}self.sorted_keys = []def _hash(self, key):return int(hashlib.md5(str(key).encode()).hexdigest(), 16)def add_node(self, node):for i in range(self.replicas):virtual_node = f"{node}-{i}"hash_value = self._hash(virtual_node)self.ring[hash_value] = nodeself.sorted_keys.append(hash_value)self.sorted_keys.sort()def remove_node(self, node):for i in range(self.replicas):virtual_node = f"{node}-{i}"hash_value = self._hash(virtual_node)del self.ring[hash_value]self.sorted_keys.remove(hash_value)def get_node(self, key):if not self.ring:return Nonehash_value = self._hash(key)for node_hash in self.sorted_keys:if hash_value <= node_hash:return self.ring[node_hash]return self.ring[self.sorted_keys[0]]# 使用示例
ch = ConsistentHashing()
ch.add_node("node1")
ch.add_node("node2")
ch.add_node("node3")key = "data_key"
node = ch.get_node(key)
print(f"Key {key} is mapped to node {node}")
這個示例實現了一個簡單的一致性哈希類,包含添加節點、刪除節點和查找鍵對應的節點等功能。
布隆過濾器
布隆過濾器 (Bloom Filter)是由 Burton Howard Bloom 于 1970 年提出,它是一種 space efficient 的概率型數據結構,用于判斷一個元素是否在集合中。
當布隆過濾器說,某個數據存在時,這個數據可能不存在;當布隆過濾器說,某個數據不存在時,那么這個數據一定不存在。
哈希表也能用于判斷元素是否在集合中,但是布隆過濾器只需要哈希表的 1/8 或 1/4 的空間復雜度就能完成同樣的問題。
布隆過濾器可以插入元素,但不可以刪除已有元素。
其中的元素越多,false positive rate(誤報率)越大,但是 false negative (漏報)是不可能的。
布隆過濾器原理
BloomFilter 的算法是,首先分配一塊內存空間做 bit 數組,數組的 bit 位初始值全部設為 0。
加入元素時,采用 k 個相互獨立的 Hash 函數計算,然后將元素 Hash 映射的 K 個位置全部設置為 1。
檢測 key 是否存在,仍然用這 k 個 Hash 函數計算出 k 個位置,如果位置全部為 1,則表明 key 存在,否則不存在。
如下圖所示:

布隆過濾器原理
哈希函數會出現碰撞,所以布隆過濾器會存在誤判。
這里的誤判率是指,BloomFilter 判斷某個 key 存在,但它實際不存在的概率,因為它存的是 key 的 Hash 值,而非 key 的值。
所以有概率存在這樣的 key,它們內容不同,但多次 Hash 后的 Hash 值都相同。
對于 BloomFilter 判斷不存在的 key ,則是 100% 不存在的,反證法,如果這個 key 存在,那它每次 Hash 后對應的 Hash 值位置肯定是 1,而不會是 0。布隆過濾器判斷存在不一定真的存在。
為什么不允許刪除元素呢?
刪除意味著需要將對應的 k 個 bits 位置設置為 0,其中有可能是其他元素對應的位。
因此 remove 會引入 false negative,這是絕對不被允許的。
redlock
來自豆包
Redlock 是一種分布式鎖的實現算法,由 Redis 官方提出,旨在解決在分布式環境中實現鎖的可靠性和可用性問題。以下是關于 Redlock 的詳細介紹:
工作原理
Redlock 算法基于多個獨立的 Redis 節點來實現分布式鎖。與傳統的基于單個 Redis 節點的鎖不同,它通過與多個節點進行交互,確保在大多數節點都成功獲取鎖的情況下,才認為鎖獲取成功。這樣可以避免單點故障導致的鎖失效問題,提高了鎖的可靠性。
具體來說,當客戶端嘗試獲取鎖時,它會依次向多個 Redis 節點發送獲取鎖的請求。每個節點會獨立地處理請求,如果節點成功地將鎖設置(例如,通過設置一個具有特定過期時間的鍵),則會返回成功響應。客戶端在收到一定數量(超過半數)的節點的成功響應后,才認為鎖獲取成功。如果客戶端在規定的時間內沒有收到足夠數量的成功響應,或者在獲取鎖的過程中出現錯誤,它會認為鎖獲取失敗,并自動釋放已經在部分節點上獲取到的鎖。
優勢
- 高可用性:由于依賴多個 Redis 節點,即使部分節點出現故障,只要大多數節點正常工作,鎖仍然可以正常獲取和釋放,不會影響系統的正常運行。
- 容錯性強:能夠容忍一定數量的節點故障,只要剩余的正常節點數量足夠多,就可以保證分布式鎖的正確性和可靠性。
- 性能較好:在大多數情況下,Redlock 的性能表現良好。雖然需要與多個節點進行交互,但由于 Redis 本身的高性能,以及 Redlock 算法的優化,使得獲取和釋放鎖的操作能夠在較短的時間內完成。
應用場景
- 分布式系統中的資源競爭控制:在分布式系統中,多個節點可能同時訪問共享資源,如數據庫記錄、文件等。Redlock 可以用于確保在同一時間只有一個節點能夠訪問這些資源,避免數據沖突和不一致性。
- 任務調度:在分布式任務調度系統中,保證同一任務在集群中只被一個節點執行。例如,定時任務、數據同步任務等,通過 Redlock 可以避免任務被重復執行,確保任務的準確性和一致性。
- 分布式緩存的更新控制:當多個應用節點共享一個分布式緩存時,可能會出現多個節點同時更新緩存的情況。使用 Redlock 可以保證在同一時間只有一個節點能夠更新緩存,避免緩存數據的不一致性。
redis內存淘汰策略
- noeviction:不會驅逐任何key
- allkeys-lru:對所有key使用LRU算法進行刪除
- volatile-lru:對所有設置了過期時間的key使用LRU算法進行刪除
- allkeys-random:對所有key隨機刪除
- volatile-random:對所有設置了過期時間的key隨機刪除
- volatile-ttl:刪除馬上要過期的key
- allkeys-lfu:對所有key使用LFU算法進行刪除
- volatile-lfy:對所有設置了過期時間的key使用LFU算法進行刪除
一般采用第2種,LRU算法思想:刪除最久的那些沒有被使用到的key,注意強調的是時間上最久
LFU:刪除使用次數最少得key,注意強調的是使用次數最少
redis鍵的刪除策略

redis主從復制
主從復制的作用
- 讀寫分離:master寫,slave讀,提高服務器的讀寫負載能力
- 負載均衡:基于主從復制、讀寫分離,由slave分擔master負載,通過多個從節點分擔數據讀取負載,提高服務器的并發量和吞吐量
- 故障恢復:mater出現問題時,由slave提供服務,實現快速的故障恢復
- 高可用基石:基于主從復制,構建哨兵與集群,實現Redis的高可用方案
Redis主從復制原理
- slave成功連接到maser后,會向master發送SYNC命令。
- mater收到SYNC命令后,執行BGSAVE命令,在后臺生成一個RDB文件,并使用一個緩沖區記錄從現在開始執行的所有寫命令。
- mater會將BGSAVE命令生成的RDB文件發送給slave,slave接收并載入RDB文件,將自己的數據庫狀態更新至master執行BGSAVE命令時的數據庫狀態。
- mater將記錄在緩沖區的所有寫命令發送給從slave,slave執行這些寫命令,將自己的數據庫狀態更新至master數據庫當前所處的狀態。
全量復制和增量復制
全量復制:slave在接受到master的RDB文件后,將其存盤并載入到內存中
增量復制:master將寫命令發送給從slave執行,避免每次都是全量復制。比如同步完成后,master刪除了key,slave沒刪除,這時主從不一致,master就需要將寫操作發送給salve并執行。這就是命令傳播。
redis IO多路復用
Redis 是一個高性能的鍵值對存儲數據庫,它單線程卻能處理大量并發客戶端連接,這主要得益于其使用的 I/O 多路復用技術。下面將詳細介紹 Redis I/O 多路復用的相關內容。
基本概念
I/O 多路復用是一種讓單個線程高效處理多個 I/O 流的技術。在傳統的阻塞 I/O 模型中,一個線程在處理一個 I/O 操作時會被阻塞,直到該操作完成,這使得線程無法同時處理其他 I/O 操作。而 I/O 多路復用通過一個機制可以監視多個文件描述符(如套接字),當其中任何一個文件描述符就緒(有數據可讀、可寫或者發生異常)時,就會通知程序進行相應的處理。
Redis 中使用 I/O 多路復用的原因
Redis 是單線程的,為了處理大量的并發客戶端連接,使用 I/O 多路復用技術可以讓 Redis 在一個線程內高效地處理多個客戶端的 I/O 操作,避免了為每個客戶端連接創建一個線程帶來的線程切換開銷和內存消耗問題,從而提高了系統的性能和可擴展性。
Redis 支持的 I/O 多路復用模型
Redis 支持多種 I/O 多路復用模型,不同的操作系統支持的模型有所不同,主要包括以下幾種:
- select:這是一種最早的 I/O 多路復用模型,幾乎所有操作系統都支持。它通過一個fd_set數據結構來存儲需要監視的文件描述符,每次調用select函數時,會將fd_set從用戶空間復制到內核空間,在內核中遍歷所有的文件描述符,檢查是否有就緒的文件描述符。select的缺點是支持的文件描述符數量有限(通常為 1024),并且每次調用都需要進行用戶空間和內核空間的復制操作,效率較低。
- poll:poll是select的改進版本,它使用一個pollfd數組來存儲需要監視的文件描述符,解決了select中文件描述符數量有限的問題。與select類似,poll也需要進行用戶空間和內核空間的復制操作,但由于使用了數組,在處理大量文件描述符時性能相對較好。
- epoll:epoll是 Linux 系統特有的 I/O 多路復用模型,它使用事件驅動的方式,通過epoll_ctl函數注冊需要監視的文件描述符和事件類型,當有事件發生時,內核會主動通知程序。epoll避免了select和poll中每次調用都需要遍歷所有文件描述符的問題,性能更高,尤其在處理大量并發連接時表現出色。
- kqueue:kqueue是 FreeBSD 系統特有的 I/O 多路復用模型,類似于epoll,它也是基于事件驅動的方式,性能較高。
Redis 選擇 I/O 多路復用模型的策略
Redis 在啟動時會根據當前操作系統的支持情況,自動選擇最合適的 I/O 多路復用模型。一般來說,會優先選擇性能最高的模型,如在 Linux 系統上會優先選擇epoll,在 FreeBSD 系統上會優先選擇kqueue。
示例代碼理解(偽代碼)
下面是一個簡單的偽代碼示例,展示了 Redis 如何使用 I/O 多路復用處理客戶端連接:
# 初始化 I/O 多路復用器,這里以 epoll 為例
epoll = epoll_create()# 創建監聽套接字并綁定地址
listen_socket = socket_create()
socket_bind(listen_socket, address)
socket_listen(listen_socket)# 注冊監聽套接字到 epoll 中,監聽讀事件
epoll_ctl(epoll, EPOLL_CTL_ADD, listen_socket, EPOLLIN)while True:# 等待事件發生events = epoll_wait(epoll)for event in events:if event.fd == listen_socket:# 有新的客戶端連接client_socket = socket_accept(listen_socket)# 注冊客戶端套接字到 epoll 中,監聽讀事件epoll_ctl(epoll, EPOLL_CTL_ADD, client_socket, EPOLLIN)else:# 客戶端套接字有數據可讀data = socket_read(event.fd)if data:# 處理客戶端請求response = process_request(data)socket_write(event.fd, response)else:# 客戶端關閉連接epoll_ctl(epoll, EPOLL_CTL_DEL, event.fd, 0)socket_close(event.fd)
這個偽代碼示例展示了 Redis 使用epoll進行 I/O 多路復用的基本流程:
初始化epoll實例。
創建監聽套接字并綁定地址,將監聽套接字注冊到epoll中,監聽讀事件。
進入一個無限循環,調用epoll_wait等待事件發生。
當有事件發生時,根據事件類型進行相應的處理:如果是監聽套接字有事件,表示有新的客戶端連接,接受連接并將客戶端套接字注冊到epoll中;如果是客戶端套接字有事件,表示有數據可讀,讀取數據并處理請求,然后將響應發送給客戶端;如果客戶端關閉連接,從epoll中刪除該套接字并關閉。
通過這種方式,Redis 可以在一個線程內高效地處理多個客戶端的 I/O 操作。
IO多路復用與異步IO的關系
I/O 多路復用和異步是兩個不同但又有一定關聯的概念,它們在處理 I/O 操作時有著不同的作用和特點。以下是它們的關系分析:
區別
概念定義
- I/O 多路復用:是一種同步 I/O 模型,通過一個線程監視多個文件描述符,當其中有文件描述符就緒時,通知應用程序進行處理。應用程序在收到通知后,需要主動進行讀寫操作,操作過程中線程仍然可能會阻塞。例如在使用select、poll或epoll等 I/O 多路復用機制時,線程會阻塞在select、poll或epoll_wait函數上等待事件發生,當有事件發生后,線程需要自行處理 I/O 操作。
- 異步:是一種更高級的 I/O 模型,在異步 I/O 中,應用程序發起 I/O 操作后,無需等待操作完成,而是繼續執行其他任務。當 I/O 操作完成后,系統會通過回調函數或信號等方式通知應用程序。在整個過程中,應用程序不需要主動去查詢 I/O 操作的狀態,也不會因為 I/O 操作而阻塞線程。
數據處理流程
- I/O 多路復用:應用程序需要不斷地輪詢或等待 I/O 多路復用機制返回的就緒事件,然后根據事件類型來處理相應的 I/O 操作。例如,在使用epoll的情況下,應用程序通過epoll_wait獲取就緒的文件描述符列表,然后對每個就緒的文件描述符進行數據讀取或寫入操作。
- 異步:應用程序發起 I/O 請求后,繼續執行其他代碼。當 I/O 操作完成時,系統會自動調用事先注冊的回調函數來處理數據。例如,在使用異步 I/O 庫時,應用程序可以在發起文件讀取請求后,繼續執行其他任務,當文件讀取完成后,系統會調用回調函數將讀取的數據傳遞給應用程序。
聯系
- 配合使用:在實際應用中,I/O 多路復用常常與異步操作配合使用,以提高系統的性能和響應能力。例如,在一些高性能的網絡服務器中,會使用 I/O 多路復用機制來監聽多個客戶端連接的事件,當有數據可讀事件發生時,通過異步的方式將數據讀取到內存中,然后再進行處理。這樣可以避免在讀取數據時阻塞線程,提高線程的利用率。
- 都用于提高 I/O 效率:I/O 多路復用和異步的目的都是為了更高效地處理 I/O 操作,減少線程阻塞的時間,提高系統的并發性和吞吐量。它們都可以讓一個線程同時處理多個 I/O 流,避免了為每個 I/O 操作創建一個線程所帶來的開銷。
參考鏈接
- Redis詳解
- 2025全網最硬核Redis面試題(大廠必備)
- Redis數據結構之Zset
- 【征服redis14】認真理解一致性Hash與Redis的三種集群
- 硬核 | Redis 布隆(Bloom Filter)過濾器原理與實戰