Redis 支持多種數據類型:
字符串
示例:存儲用戶信息
// 假設我們使用 redis-plus-plus 客戶端庫
auto redis = Redis("tcp://127.0.0.1:6379");
redis.set("user:1000", "{'name': 'John Doe', 'email': 'john.doe@example.com'}");
std::string userInfo = redis.get("user:1000").value_or("");
列表(List)
示例:任務隊列
// 將任務添加到隊列尾部
redis.lpush("task_queue", "Task 1");
redis.rpop("task_queue"); // 從隊列頭部取出并刪除任務
集合(Set)
示例:標簽系統
// 給文章添加標簽
redis.sadd("article:12345:tags", {"tech", "news"});
// 獲取所有標簽
auto tags = redis.smembers("article:12345:tags");
有序集合(Sorted Set)
// 添加玩家分數
redis.zadd("leaderboard", {{ "player:1", 850 }, { "player:2", 920 }});
// 獲取前五名玩家
auto topPlayers = redis.zrevrange("leaderboard", 0, 4, true);
for (const auto &player : topPlayers) {std::cout << "Player: " << player.member << ", Score: " << player.score << std::endl;
}
哈希(Hash)
示例:存儲用戶詳細信息
// 存儲用戶詳情
redis.hset("user:1000", {{"name", "Jane Doe"}, {"age", "30"}, {"email", "jane.doe@example.com"}});
// 獲取用戶的年齡
auto age = redis.hget("user:1000", "age").value_or("");
位圖(Bitmaps)
示例:簽到統計
// 記錄用戶第i天是否簽到
redis.setbit("user:1000:sign_in", 1, 1); // 第一天簽到
bool signedInToday = redis.getbit("user:1000:sign_in", 1); // 檢查第一天是否簽到
HyperLogLog
示例:獨立訪客統計
// 增加一個獨立訪客
redis.pfadd("unique_visitors", {"user1", "user2", "user3"});
// 估算獨立訪客數量
long long estimatedCount = redis.pfcount("unique_visitors");
地理空間(Geospatial)
// 添加一些地點
redis.geoadd("locations", {{-122.41, 37.77, "San Francisco"}, {-118.24, 34.05, "Los Angeles"}});
// 查找距離舊金山100公里內的地點
auto locations = redis.georadius("locations", -122.41, 37.77, 100, "km");
for (const auto &loc : locations) {std::cout << loc.member << std::endl;
}
redis和memcached的區別
Redis 和 Memcached 都是高性能的內存鍵值存儲系統,常被用作緩存解決方案來提升應用性能。盡管它們有相似的應用場景,但在設計理念、功能特性等方面存在顯著差異。以下是 Redis 和 Memcached 的主要區別:
- 數據類型支持
Redis:支持豐富的數據結構,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)、位圖(Bitmaps)、HyperLogLog、地理空間(Geospatial)等。這使得 Redis 能夠處理更復雜的任務,如排序、范圍查詢等。
Memcached:僅支持簡單的鍵值對,其中鍵和值都是字節數組。它的設計目標是提供一個快速的緩存層,因此沒有復雜的數據結構支持。 - 持久化
Redis:支持持久化選項,包括 RDB 快照和 AOF(Append Only File)。這意味著 Redis 可以在重啟后恢復之前的數據狀態。
Memcached:不支持持久化,所有數據都存儲在內存中,如果服務器重啟或崩潰,所有緩存的數據都會丟失。 - 內存管理
Redis:具有更復雜的內存管理機制,能夠根據需要自動回收內存,并且可以配置最大內存使用限制以及淘汰策略(如 LRU 等)。
Memcached:采用 slab 分配算法來分配內存,一旦分配就不能改變大小,可能導致內存碎片問題。 - 復雜操作
Redis:提供了諸如事務、發布/訂閱、Lua 腳本執行、分布式鎖等多種高級功能,允許用戶執行更為復雜的操作。
Memcached:主要是為簡單讀寫操作優化的,不支持事務、腳本執行等高級功能。 - 主從復制與集群
Redis:支持主從復制和哨兵(Sentinel)機制,用于實現高可用性;還支持集群模式,允許水平擴展。
Memcached:本身不具備主從復制和集群功能,但可以通過客戶端實現一致性哈希等技術來構建分布式緩存。 - 性能
Redis:由于其額外的功能特性,可能在某些情況下比 Memcached 稍慢,但對于大多數應用場景而言,這種差異是可以忽略的。
Memcached:專注于高速緩存,對于簡單的鍵值對操作非常高效。 - 使用場景
Redis:適合需要持久化、復雜數據結構、消息隊列、計數器等高級功能的場景。
Memcached:適用于那些只需要簡單鍵值對緩存、對持久化沒有要求的高性能讀寫場景。
綜上所述,選擇 Redis 還是 Memcached 應基于具體的需求和使用場景。如果你的應用需要復雜的操作、持久化或者高級功能,那么 Redis 是更好的選擇。而如果你的應用只需簡單的緩存機制,追求極致的速度,那么 Memcached 可能更適合。
Redis 提供的兩種持久化機制
RDB 快照 (Redis Database Backup)
RDB 是一種快照持久化方式,它會在指定的時間間隔內將內存中的數據集以二進制形式寫入磁盤上的一個文件中,通常這個文件名為 dump.rdb。
當 Redis 需要恢復數據時,它會加載最近一次生成的 RDB 文件來恢復數據。
可以通過修改 Redis 的配置文件 (redis.conf) 來設置觸發快照的條件,例如:
save 900 1 # 在過去900秒內至少有1個鍵發生變化則觸發快照
save 300 10 # 在過去300秒內至少有10個鍵發生變化則觸發快照
save 60 10000 # 在過去60秒內至少有10000個鍵發生變化則觸發快照
優點:
性能影響小:由于 RDB 是在特定時間點進行的數據快照,因此對性能的影響相對較小。
恢復速度快:因為 RDB 文件是以緊湊的二進制格式存儲的,所以讀取速度較快,適合用于災難恢復。
缺點:
數據丟失風險:如果 Redis 發生故障,自上次快照以來的所有數據更新都將丟失。
占用更多內存:創建快照期間可能需要額外的內存來執行復制操作。
AOF (Append Only File)
描述:
AOF 持久化記錄服務器接收到的每一個寫操作,在 Redis 重啟時會重新執行這些命令來重建原始數據集。
默認情況下,AOF 文件每秒鐘同步一次(fsync),也可以配置為每次寫操作后立即同步,但這會影響性能。
配置:
啟用 AOF 需要在 redis.conf 中設置
appendonly yes
還可以調整同步策略,比如:
appendfsync everysec # 每秒同步一次
appendfsync always # 每次寫操作后同步
appendfsync no # 不主動同步,依賴操作系統
優點:
更高的數據安全性:因為所有寫操作都會被記錄下來,所以在發生崩潰時最多只會丟失一秒鐘的數據。
靈活性:可以通過不同的 fsync 策略平衡性能與數據安全之間的關系。
缺點:
文件體積較大:隨著時間推移,AOF 文件可能會變得非常大,盡管 Redis 支持自動重寫(rewrite)功能來壓縮文件大小。
恢復速度較慢:由于需要重新執行所有的命令來恢復數據,因此啟動時間可能比使用 RDB 更長。
Redis 對于過期鍵的刪除采用了兩種主要策略:
惰性刪除(Lazy Expiration)和定期刪除(Active Expiration)。這兩種策略相互補充,旨在保證在不過多影響性能的前提下,有效地管理和清理過期鍵。
- 惰性刪除(Lazy Expiration)
描述:
當訪問一個鍵時,Redis 會檢查該鍵是否已經過期。如果鍵已過期,則 Redis 會立即刪除該鍵,并返回不存在給客戶端。
這種方式確保了即使鍵已經過期,它也不會占用內存,直到下一次被訪問。
優點:
非常簡單且高效,因為只有在鍵被訪問時才會進行過期檢查,避免了不必要的資源消耗。
缺點:
如果某個鍵過期后長時間未被訪問,那么它仍然可能占據著內存空間,直到下一次訪問時才被刪除。
- 定期刪除(Active Expiration)
描述:
Redis 會周期性地隨機檢查一些鍵,并對這些鍵執行過期檢查。通過這種方式,Redis 能夠主動地清除那些已經過期但尚未被訪問到的鍵。
Redis 實現了一個定時任務,默認情況下每秒運行10次,每次從設置了過期時間的鍵中隨機抽取一定數量的鍵進行檢查并刪除那些已經過期的鍵。
為了防止這個過程消耗過多的CPU資源,Redis 使用了一些限制條件來控制其執行頻率和每次處理的數量。
優點:
可以有效減少因惰性刪除導致的過期鍵長期占用內存的問題。
有助于保持內存使用的健康狀態,避免大量過期鍵積累。
缺點:
如果設置不當,可能會增加系統的負載。然而,由于Redis對其進行了精心的設計,通常不會對性能造成顯著影響。
如何選擇
這兩種策略并不是互相排斥的,而是共同作用來管理過期鍵。惰性刪除保證了每個訪問的鍵都是有效的,而定期刪除則幫助清理那些雖然過期但尚未被訪問的鍵。這樣的組合使得 Redis 在大多數情況下能夠很好地平衡性能與內存使用效率。
此外,Redis 還允許用戶配置具體的淘汰策略(如 volatile-lru、allkeys-lru 等),當達到設定的最大內存限制時,Redis 將根據所選策略自動移除部分鍵值對,這也有助于控制內存使用量。不過,這些策略主要是針對內存壓力管理而不是直接處理過期鍵的刪除。
redis的回收策略
Redis 提供了幾種不同的內存回收策略(Eviction Policies),這些策略決定了當 Redis 達到配置的最大內存限制時,如何選擇并移除某些鍵以釋放空間。Redis 的內存回收策略主要通過 maxmemory 和 maxmemory-policy 參數來控制。
配置最大內存
首先,你需要設置 Redis 實例允許使用的最大內存量。這可以通過 maxmemory 配置項完成:
maxmemory 256mb
此設置告訴 Redis 當使用的內存達到 256MB 時開始執行內存回收策略。
內存回收策略 (maxmemory-policy)
Redis 支持多種內存回收策略,可以通過 maxmemory-policy 參數進行配置。以下是幾種常見的策略:
noeviction
默認策略。當內存達到上限時,任何寫操作都會返回錯誤(除了讀操作如 GET),不會嘗試刪除任何鍵。
allkeys-lru
從所有鍵中挑選最近最少使用的(Least Recently Used, LRU)鍵進行淘汰。適用于大多數需要緩存的應用場景,因為它傾向于保留最常用的數據。
volatile-lru
只針對設置了過期時間的鍵使用 LRU 算法進行淘汰。如果系統中沒有或很少有過期鍵,則效果類似于 noeviction。
allkeys-random
隨機選擇鍵進行淘汰,不論其是否設置了過期時間。適合于數據重要性大致相同的情況。
volatile-random
隨機選擇那些已經設置了過期時間的鍵進行淘汰。適用于希望優先清理臨時數據的場景。
volatile-ttl
選擇即將過期的鍵優先淘汰。這有助于盡可能長時間地保持數據的有效性,特別適用于依賴 TTL 來管理緩存生命周期的應用。
allkeys-lfu (Redis 4.0+)
從所有鍵中挑選最不經常使用的(Least Frequently Used, LFU)鍵進行淘汰。比 LRU 更加智能,因為它考慮了鍵的訪問頻率。
volatile-lfu (Redis 4.0+)
類似于 allkeys-lfu,但僅應用于設置了過期時間的鍵。
如何選擇合適的策略
選擇哪種策略取決于你的應用需求:
如果你希望最大限度地利用緩存中的熱門數據,那么 allkeys-lru 或 allkeys-lfu 是不錯的選擇。
如果你有大量設置了過期時間的鍵,并且希望通過自然過期機制來管理內存,可以考慮 volatile-lru 或 volatile-ttl。
對于那些對數據丟失敏感的應用,可能需要避免使用自動淘汰機制,或者至少確保關鍵數據不被誤刪,這時可以選擇 noeviction 并手動處理內存超限問題。
redis的同步機制:
Redis 的主從復制、哨兵模式和集群模式雖然各自有不同的設計目標,但它們都涉及數據同步機制,以確保數據的一致性和高可用性。下面詳細解釋這三種模式與同步機制的關系,并通過具體例子來說明它們如何工作。
- 主從復制(Replication)
關系:主從復制是 Redis 最基礎的同步機制,它通過異步的方式將主節點的數據變化同步到一個或多個從節點上。這種機制為 Redis 提供了讀擴展能力和一定的故障恢復能力。
同步機制:
全量同步(Full Resynchronization):當一個新的從節點連接到主節點時,或者在某些錯誤情況下,會進行一次完整的數據同步,包括生成 RDB 文件并傳輸給從節點。
部分同步(Partial Resynchronization):如果主從之間的連接斷開后重新建立,且滿足一定條件(如復制積壓緩沖區中存在所需數據),則可以只同步斷連期間丟失的部分數據,而不是整個數據庫。
例子:
假設你有一個電商網站,主節點負責處理所有寫操作(如用戶下單),而從節點用于處理讀請求(如查看商品詳情)。這樣不僅可以分擔主節點的壓力,還能提高查詢速度。
- 哨兵模式(Sentinel)
關系:哨兵模式基于主從復制之上,增加了自動故障檢測和故障轉移功能。當主節點出現故障時,哨兵系統能夠自動將某個從節點提升為主節點,從而保證服務的連續性。
同步機制:
在正常運行期間,哨兵持續監控主從節點的狀態。
一旦發現主節點不可用,哨兵將啟動選舉流程,選擇一個新的主節點,并通知所有相關的從節點切換到新的主節點上。
例子:
繼續上述電商網站的例子,如果你希望在主服務器發生故障時,系統能夠自動恢復而不影響用戶體驗,那么你可以部署哨兵模式。例如,當主節點因硬件故障離線時,哨兵會自動將其中一個從節點升級為主節點,確保訂單處理和其他關鍵業務不受影響。
- 集群模式(Cluster)
關系:集群模式不僅實現了數據的分布式存儲,還內置了數據同步機制,以確保各分片之間的數據一致性。每個節點都管理著一部分數據及其副本,提高了系統的可擴展性和容錯能力。
同步機制:
數據根據鍵的空間分區分布到不同的節點上。
每個節點都知道其他節點的信息,以及哪些鍵應該存儲在哪一個節點上。
當需要進行數據遷移或添加新節點時,集群會自動調整數據分布。
例子:
假設你的電商網站規模擴大,單臺服務器無法再承載所有的數據和請求。這時你可以使用 Redis Cluster 將數據分散到多個節點上。比如,可以根據用戶ID對用戶會話信息進行分片存儲,使得不同用戶的會話信息分布在不同的節點上,既提高了性能也增強了系統的擴展能力。
綜合比較
主從復制:適用于需要增加讀性能或提供簡單備份方案的場景。它的同步機制相對簡單,但不具備自動故障恢復的能力。
哨兵模式:在主從復制的基礎上增加了自動故障檢測和恢復功能,適合那些對服務連續性要求較高的應用。
集群模式:提供了更強大的擴展性和容錯能力,特別適合大規模數據集和高并發訪問的場景。它不僅解決了單點故障問題,還支持水平擴展。
實際應用場景示例
小型電商網站初期階段:可能只需要一個主節點加一個從節點的配置,使用主從復制來分擔讀負載。此時,哨兵模式可用于確保在主節點失敗時能快速恢復服務。
隨著業務增長:如果流量增加導致單一主節點成為瓶頸,可以考慮引入哨兵模式,確保即使主節點出現問題也能迅速恢復正常運營。
大型電商平臺:面對海量用戶和復雜業務邏輯,可能會采用 Redis Cluster 來實現數據分片,同時結合哨兵模式保障高可用性。這樣既能處理大量并發請求,又能靈活應對數據增長帶來的挑戰。
通過合理選擇和組合這些模式,可以根據實際需求構建出既高效又可靠的 Redis 部署方案。
redis集群的原理
Redis 集群(Redis Cluster)是 Redis 官方提供的分布式解決方案,旨在解決單節點 Redis 在數據量大和高并發場景下的局限性。它通過數據分片、自動故障轉移等機制實現了高可用性和可擴展性。以下是 Redis 集群的工作原理及其核心特性:
核心概念
- 數據分片(Sharding):
Redis 集群將整個鍵空間分成多個槽(slot),默認情況下共有 16384 個槽。
每個節點負責處理一部分槽中的數據。例如,在一個包含 3 個主節點的集群中,每個節點可能負責大約 5461 個槽。
- 主從復制:
每個主節點都有一個或多個從節點作為備份。當主節點發生故障時,其中一個從節點可以被提升為主節點,以保證服務的連續性。
- 一致性哈希:
鍵值對根據哈希算法分配到不同的槽中。具體來說,Redis 使用 CRC16 算法計算鍵的哈希值,并對 16384 取模來確定該鍵應位于哪個槽中。
- 工作流程
數據分布與訪問
當客戶端請求到來時,首先需要知道要操作的數據位于哪個節點上。為此,客戶端要么事先知道鍵所在的槽,要么查詢集群得知該信息。
對于寫操作,如果目標槽位于當前主節點,則直接執行;否則,重定向至正確的節點。
讀操作則可以根據需求選擇是否從主節點或從節點讀取數據。
自動故障檢測與恢復
集群內的節點會定期交換消息(PING/PONG),以檢查其他節點的狀態。
如果某個主節點被認為不可用,集群會觸發故障轉移過程:從其副本中選出一個新的主節點,并更新集群狀態,使得其他節點知曉新的配置。
特性與優勢
高可用性:
通過主從復制和自動故障轉移,確保即使某些節點失敗,服務也能繼續運行。
可擴展性:
支持在線添加或移除節點,動態調整數據分布,適應不斷變化的工作負載。
分區容錯性:
雖然 Redis 集群不支持強一致性(遵循最終一致性模型),但在網絡分區情況下仍能提供一定程度的服務。
- 實際例子
假設我們有一個由 6 個節點組成的 Redis 集群,其中 3 個為 主節點(A, B, C),另外 3 個為對應的從節點(A1, B1, C1)。每個主節點分別管理 5461 個槽。
初始狀態:
A 節點負責槽 0-5460
B 節點負責槽 5461-10922
C 節點負責槽 10923-16383
客戶端操作:
如果客戶端想要獲取鍵 “user:1000” 的值,首先計算出該鍵對應的槽號(例如 1234),然后根據集群配置找到負責這個槽的節點(假設是 A 節點),并向該節點發起請求。
故障處理:
假設主節點 A 發生故障,集群會檢測到這一情況,并啟動故障轉移流程,將 A1 提升為主節點,接管原 A 節點負責的所有槽。在此過程中,其他節點也會更新自己的路由表,確保后續請求能夠正確路由到新主節點。
redis中的哈希槽
在 Redis 集群中,“哈希槽”(Hash Slot)是實現數據分片和分布的關鍵概念。Redis 集群通過將整個鍵空間劃分為固定的 16384 個哈希槽,實現了數據的分布式存儲與負載均衡。每個鍵都歸屬于這 16384 個哈希槽中的一個,而這些哈希槽則分布在集群的不同節點上。
- 哈希槽的工作原理
分配規則:
當向 Redis 集群中添加一個鍵時,首先使用 CRC16 算法計算該鍵的哈希值。
接著,將得到的哈希值對 16384 取模(即 hash(key) % 16384),以確定該鍵應歸屬于哪個哈希槽。
最后,根據當前集群的狀態,找到負責管理該哈希槽的節點,并將鍵存儲在這個節點上。
節點分配:
在 Redis 集群啟動或重新配置時,所有 16384 個哈希槽會被分配給集群中的各個主節點。
每個主節點負責處理其分配到的哈希槽中的所有操作(讀寫)。同時,每個主節點可以有一個或多個從節點作為備份,用于故障轉移。
數據遷移與擴展性
數據遷移:當需要增加或移除集群中的節點時,可以通過重新分配哈希槽來調整數據分布。例如,如果要添加一個新的節點,可以從現有的節點中移動一部分哈希槽到新節點上。這一過程可以在不影響服務的情況下進行,從而支持在線擴展。
擴展性:由于 Redis 集群采用了固定數量的哈希槽(16384 個),因此可以根據實際需求靈活地調整集群規模。無論是增加還是減少節點,都可以通過重新分配哈希槽來實現負載均衡和數據分布優化。
客戶端交互
客戶端在執行命令之前,需要知道目標鍵位于哪個哈希槽以及該哈希槽由哪個節點管理。通常情況下,客戶端會維護一份映射表,記錄每個哈希槽對應的節點信息。當客戶端接收到一個命令時,它會根據命令涉及的鍵計算出相應的哈希槽,并將請求發送到正確的節點。
如果客戶端嘗試訪問的哈希槽不在當前連接的節點上,該節點會返回一個重定向響應(MOVED 錯誤),告訴客戶端正確的節點地址。客戶端可以根據這個信息更新自己的映射表,并將請求重發至正確的節點。
示例
假設我們有一個包含三個主節點(A, B, C)和相應從節點的 Redis 集群:
Node A:負責哈希槽 0 到 5460
Node B:負責哈希槽 5461 到 10922
Node C:負責哈希槽 10923 到 16383
如果我們想要存儲一個鍵 “user:1000”:
計算 “user:1000” 的哈希值并對其取模 16384,假設計算結果為 3456。
根據上述分配規則,3456 屬于 Node A 負責的范圍(0-5460),因此這個鍵將會被存儲在 Node A 上。
如果我們要查詢這個鍵,客戶端會重復上述步驟,找到負責哈希槽 3456 的節點(即 Node A),然后直接與該節點通信獲取數據。
總結
哈希槽是 Redis 集群實現數據分片的核心機制。通過將整個鍵空間均勻地劃分成 16384 個哈希槽,并將其分配給不同的節點,Redis 集群不僅能夠有效地分散負載,還提供了良好的可擴展性和高可用性。這種設計使得 Redis 集群非常適合處理大規模數據集和高并發訪問場景。
參考鏈接:https://zhuanlan.zhihu.com/p/663851226