Redis 常用的五種數據結構
字符串 String
- 概念:字符串主要用於管理 Redis 字符串值。
- 容量:最大為數據長度是 512M
列表 List
- 概念:列表是簡單的字符串列表,按照插入順序排序,可以從列表的頭部或尾部插入一個元素。
- 容量:一個列表最多可以包含 2^32 - 1 個元素 (4294967295, 每個列表超過40億個元素)。
集合 Set
- 概念:集合是 String 的無序集合,集合元素是唯一的。
- 容量:集合中最大可存儲的元素數量為 2^32 - 1 (40 多億)。
哈希 Hash
- 概念:哈希是一個 String 類型的 field 和 value 的映射表,適合存儲對象。
- 容量:每個 Hash 可以存儲 2^32 - 1 鍵值對 (40 多億)。
有序集合 Sorted Set
- 概念:有序集合也是 String 的集合,但每個元素會關聯一個 double 類型的分數 (score) 且集合中的元素是從小到大排序的。
- 容量:有序集合中最大可存儲的元素數量為 2^32 - 1 (40 多億)。
Redis 常見問題
說一說 Redis 哈希槽的概念 ?
Redis
集群沒有使用一致性 hash
,而是引入了哈希槽的概念。
Redis
集群中有 16384
個哈希槽,每個 ke
y 通過 CRC16
校驗后對 16384
取模來決定存儲在那個槽。
集群的每個節點負責一部分 hash
槽。
Redis 為什么選擇單線程?
- 多線程會頻繁切換上下文消耗大量的 CPU。
- 單線程支持原子操作,不會存在鎖競爭問題。
為什么不使用 Redis 的事務?
- 不支持回滾操作,不能滿足原子性。
Redis 集群的利弊
好處
- 容量增加,處理能力增強。
- 可按需擴容與縮容。
問題
1、存儲數據時如何選擇節點、查詢數據時如何選擇節點 ?
2、新增節點時如何拉取數據、剔除節點時如何轉移數據 ?
解決方式
為了解決數據與節點直接的映射關系,Redis 引入了槽。
引入槽之后,節點上放置的是槽,槽當中存儲的是數據。
一個集群只能有 16384 個槽,編號為 0 - 16383。這些槽會分配給集群中的所有主節點,分配策略無要求。
可以指定哪些編號的槽分配到哪一個主節點。集群會記錄節點和槽的對應關系。
如何計算 Key 在那個槽?
對 Key 求哈希值,然后對 16384 取余,余數的值就是 Key 對應的槽的編號。
即:CRC16 (Key) % 16384
Redis 為什么需要管道技術?
Redis 提供了一種管道技術,可以讓客戶端一次發送多條命令。
期間不需要等待服務器端的響應,等所有的命令發送完成后,再依次響應。
這樣節省了時間,提升了效率。
Redis 分布式鎖是什么 ?
- 先拿
setnx
來爭搶鎖,搶到之后,再用expire
給鎖加一個過期時間防止鎖忘記了釋放。 - 如果在
setnx
之后,執行expire
之前進程意外crash
或重啟維護, 那么就需要把setnx
和expire
合成一條指令來用。
假如 Redis 里面有一億個 key,其中 10 萬個 key 是以某個固定的已知前綴開頭的,如何將它們全部找出來?
- 使用keys指令可以掃出指定模式的key列表。
- 如果這個redis正在給線上的業務提供服務,那么使用key指令會導致線程阻塞。(redis是單線程的,執行key指令期間,線上服務會卡頓,直到指令執行完成,服務才會恢復)。在這種場景下,就可以使用scan指令,該指令可以無阻塞的提取出指定模式的key列表,但是會有一定重復的概率,可以在客戶端做一次去重就好了, 但是整體花費的時間會比直接使用keys指令長。
如何實現 Redis 異步隊列 ?
使用 list
結構作為隊列,rpush
生產消息,lpop
消費消息。
當 lpop
沒有消息的時候,要適當 sleep
一會再重試。
如果不用 sleep
, 還有個指令 blpop
, 在沒有消息的時候, 他會阻塞住直到有消息。
如果要生產一次消費多次,則需要使用 pub/sub
主題訂閱者模式,可以實現 1:N
的消息隊列。
在消費者下線的情況下,生產的消息會丟失。
在這種情況下,就得使用更專業的消息隊列,例如 RabbitMQ。
如何實現 Redis 延時隊列 ?
使用 SortedSet
,拿時間戳作為 Score
, 消息內容作為 key
調用 zadd
來生產消息,
消費者用 zrangebysocre
指令獲取N秒之前的數據輪詢進行處理。
如果有大量的 key 需要設置統一時間過期,需要注意什么 ?
如果有大量的 key
過期時間設置過於集中,到過期的那個時間點。
redis
可能會出現短暫的卡頓現象。一般需要在時間生加上一個隨機值, 使得過期時間分散一些。
Redis 如何做持久化 ?
bgsave
做鏡像全量持久化, aof
做增量持久化。
因為 bgsave
會耗費較長時間, 不夠實時, 在停機的時候會導致大量丟失數據, 所以aof來配合使用。
在 redis
實例重啟時, 會使用 bgsave
持久化文件重新構建內存, 在使用 aof
重放近期的操作指令來實現完整恢復重啟之前的狀態。
如果不要求性能, 在每條寫指令是都 sync
一下磁盤, 就不會丟失數據。
但是在高性能的要求下每次都 sync
是不現實的, 一般都使用定時 sync
, 比如1s1次, 這個時候最大就會丟失1s的數據。
bgsave
的原理是, fork
和 cow
。fork
是指 redis
通過創建子進程來進行 bgsave
操作。
cow
指的是 copy on write
, 子進程創建后, 父進程通過共享數據段。
父進程繼續提供讀寫服務, 寫臟的頁面數據會逐漸和子進程分離開來。
Pipeline 有什么好處?
可以將多次 IO
往返的時間縮減為一次, 前提是 pipeline
執行的指令質檢沒有因果相關性。
使用 redis-benchmark
進行壓測的時候可以發現影響 redis
的 QPS
峰值的一個重要因素是 piepline
批次指令的數目。
Redis 的同步機制是如何操作的?
redis
可以使用主從同步, 從從同步。
第一次同步時, 主節點做一次 bgsave
, 並同時將后續修改操作記錄到內存buffer。
待完成后將 rdb
文件全量同步到復制節點, 復制節點接受完成后, 將rdb鏡像加載到內存。
加載完成后, 再通知主節點將修改期間的操作 記錄同步到復制節點進行重放就完成了同步過程。
Redis 集群的原理?
redis sentinal
着眼於高可用, 在 master
宕機時會自動將 slave
提升為 master
, 繼續提供服務。
redis cluster
着眼於擴展性, 在單個 redis
內存不足時, 使用 cluster
進行分片存儲。
緩存穿透
現象
查詢不存在的數據,緩存中沒有數據,數據庫也沒有數據。
因此所有的請求都訪問到了數據庫,給數據庫造成了壓力。
解決方法
1、采用布隆過濾器,將所有可能存在的數據,哈希到一個很大的 bitmap 中,
一個一定不存在的數據會被 bitmap 攔截調,從而避免了對數據庫的查詢壓力。
2、如果查詢的數據為空,那么直接將空數據也緩存起來並設置較短的過期時間。
這樣下次訪問的時候,就直接返回空值。
緩存預熱
系統上線之后,將需要緩存的數據直接加載到內存。
實現思路:
1、直接寫個緩存刷新命令,上線時手動操作。
2、項目啟動時自動加載緩存到內存。
3、定時刷新緩存。
Redis 的三種刪除策略
定時刪除
在設置鍵的過期時間的同時,創建一個定時任務。當鍵達到過期時間時,立即執行對鍵的刪除操作。
- 優點
對內存友好,定時刪除策略可以保證過期鍵會盡可能的快被刪除,並且釋放過期鍵所占用的內存。
- 缺點
對 CPU 時間片不友好,在過期鍵比較多的情況下。刪除任務會占用很大一部分 CPU 時間片,在內存不緊張但是 CPU 時間緊張時.將 CPU 事件用在刪除和當前任務無關的過期鍵上,會影響服務器的響應時間和吞吐量。
惰性刪除
- 優點
對 cpu 時間友好,在每次從鍵空間獲取鍵時進行過期鍵檢查並是否刪除,刪除目標也僅限當前處理的鍵,這個策略不會在其他無關的刪除任務上花費任何 cpu 時間。
- 缺點
對內存不友好,過期鍵過期也可能不會被刪除,導致所占的內存也不會釋放。甚至可能會出現內存泄露的現象,當存在很多過期鍵,而這些過期鍵又沒有被訪問到,這會可能導致它們會一直保存在內存中,造成內存泄露。
定期刪除
由於定時刪除會占用太多的 CPU 時間片,影響服務器的響應時間和吞吐量。並且惰性刪除浪費太多的內存,有內存泄漏的風險。因此定期刪除策略是這兩策略的折衷策略。
- 優點
定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響。
定時刪除策略有效地減少了因為過期鍵帶來的內存浪費。
什么是緩存穿透?
緩存穿透是指緩存和數據庫中都沒有數據的情況下,客戶端不斷發起請求,導致數據庫壓力過大。
什么是緩存擊穿?
緩存擊穿是指緩存過期之后,瞬時間並發客戶端特別多查詢同一條數據的情況下,導致數據庫壓力過大。
什么是緩存雪崩?
緩存雪崩是指緩存中大量的不同數據同時過期,此時查詢大量的數據,導致數據庫壓力過大。