Redis 面試問題
Redis有哪些數據結構?
- 字符串String
- 字典Hash
- 列表List
- 集合Set
- 有序集合SortedSet (zset)。
如果 你是Redis中高級用戶,還需要加上下面幾種數據結構
- HyperLogLog
- Geo
- Pub/Sub
如果你說還玩過Redis Module,像
- BloomFilter
- RedisSearch
- Redis-ML 面試官得眼睛就開始發亮了。

Redis有哪些應用
1. 緩存 (string)
2. 共享session (string)
3. 限速 (string)

4. 計數器應用 (string) :
- 影片播放次數
5. 用戶訊息 (hash)
hmset user:1 name kimi age 10 city taipei
6. 消息對列系統 (list)
- lpush + brpop
7. 文章列表 (list, hash)
- 每篇文章使用
Hash存儲, 例如每篇文章有3個屬性 title、timestamp、content - 向用户文章列表添加文章 user {id} articles 作為用户文章列表的鍵
- 分頁 取用户文章列表 例如下面偽代碼 取用户id=1的前10篇文章
hmset acticle:1 title xx timestamp 1476536196 content xxxx
lpush user:1:acticles article:1 article:3
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
8. 排行榜系統 ( list, zset)
# 添加用戶讚數
zadd user:ranking:2016_03_15 mike 3
zincrby user:ranking:2016_03_15 mike 1
# 取消用戶讚數
zrem user:ranking:2016_03_15 mike
# 展示用户信息以及用戶分數
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike
zrank user:ranking:2016_03_15 mike
tag (zset)
- 用戶喜好的tag, 可以找出共同喜好
- 用戶和tag的關係維護應該在一個事物內執行, 防止部分命令失敗造成數據不一致
社交網路
- 讚, 粉絲, 共投好友/喜愛, 推送, 下拉刷新
- 由於社交網站的訪問量通常比較大, 傳統的關聯數據不太適合
紀錄總數 (bitmaps, HyperLogLog)
- 如果不需要個別內容, 且接受誤差 使用HyperLogLog
聊天室, 公告, 服務之間的消息傳遞 (publish subscribe)
搜尋功能(set,zset)
- 反向索引 (inverted indexes), 反向索引會從每一個被索引的文黨裡面提出一些單詞, 並創建表格來記錄每篇文章都包含哪些單詞

smembers , lrange, hgetall都屬於比較重的命令, 如果元素過多存在阻塞的可能性, 這時候可以使用sscan來完成
什麼是 bigkey
Bigkey是指當Redis 的字串型別過大,非字串型別元素過多
- 字符串:一般認為超過 10 KB 就是 Bigkey
- 非字符串:hash, list, set, zset 元素個是過多, 比如超過5000個
bigkey 帶來了什麼危害?
- Redis 阻塞:因為 Redis 單執行緒特性,如果操作某個 Bigkey 耗時比較久,則後面的請求會被阻塞。
- 記憶體空間不均勻:比如在 Redis cluster 或者 codis 中,會造成節點的記憶體使用不均勻。
- 過期時可能阻塞 :如果 Bigkey 設定了過期時間,當過期後,這個 key 會被刪除,假如沒有使用 Redis 4.0 的過期非同步刪除,就會存在阻塞 Redis 的可能性,並且慢查詢中查不到(因為這個刪除是內部迴圈事件)。
- 導致傾斜:某個例項上正好儲存了 bigkey。bigkey 的 value 值很大(String 型別),或者是 bigkey 儲存了大量集合元素(集合型別),會導致這個例項的資料量增加,記憶體資源消耗也相應增加。例項的處理壓力就會增大,速度變慢,甚至還可能會引起這個例項的記憶體資源耗盡,從而崩潰。
解法
- 設置一個閾值,當value的長度超過閾值時,對內容啟動壓縮,降低kv的大小
- 評估大key所佔的比例,由於很多框架採用池化技术,如:Memcache,可以預先分配大對象空間。真正業務請求時,直接拿來即用。
- 顆粒劃分,將大key拆分為多個小key,獨立維護,成本會降低不少
- 大key要設置合理的過期時間,盡量不淘汰那些大key
IO多路复用
多路復用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態,比如調用select和poll函數,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。得到就緒狀態後進行真正的操作可以在同一個線程裡執行,也可以啟動線程執行(比如使用線程池)。

select/poll/epoll
select

缺點
- 1024 bitmap
- fDset 不可重用
- 用戶態 與 內核 切換的開銷
- O(n) 再次遍歷
poll

缺點
1024 bitmap :解法->用了數組解決此問題fDset 不可重用:解法->只要歸零 revents- 用戶態 與 內核 切換的開銷
- O(n) 再次遍歷
epoll

epoll模式,并没有共享用户态和内核态的数据,而是在epoll_ctl的时候把要监控的数据拷贝到内核态了,一次拷贝,终生受用,而select,poll每次调用都要拷贝这部分参数。epfd实际上是用户态的一块地址描述符。
缺點
用戶態 與 內核 切換的開銷: 解決->epoll确实没有使用mmap的内存映射技术,也就是没有内存共享。 ctl方法中是将文件描述符拷贝到内核态(以红黑树的结构存储),所以await方法不再需要做用户态到内核态的数据拷贝动作。 await 方法在内核中检测到有fd数据准备完成时,会把完成的fd塞到链表中(readyList),需要将链表拷贝回到用户态。 用户态再去处理数据。O(n) 再次遍歷 : 解決 變成O(1)- [比較詳細] https://blog.csdn.net/XueyinGuo/article/details/113096163
- https://network.51cto.com/art/202102/645464.htm
- https://www.zhihu.com/question/28594409/answer/74003996
- https://zhuanlan.zhihu.com/p/115220699
- https://www.bilibili.com/video/BV1qJ411w7du?from=search\&seid=9150114057878808215
緩存穿透、緩存擊穿、緩存雪崩
1.緩存穿透
駭客攻擊
緩存穿透是指緩存服務器中沒有緩存數據,數據庫中也沒有符合條件的數據,導致業務系統每次都繞過緩存服務器查詢下游的數據庫,緩存服務器完全失去了其應用的作用。
解法
- 為這些key對應的值設置為null並放到緩存中,
- 這樣再出現查詢這個key的請求的時候,直接返回null即可, 但是還需要注意的就是需要有一個失效時間 . set key val ex 5nx
- BloomFilter
- 緩存穿透是因為有很多惡意流量的請求,這些請求可能隨機生成很多Key來請求查詢,這些肯定在緩存和數據庫中都沒有,那就很容易導致緩存穿透
- 比如如果有一群人經常來門店問一些根本不存在的色號,比如五彩斑斕的黑,這些色號該品牌根本沒生產過的話,店員就可以直接告訴顧客不存在就行了,也不需要驚動總部。
- 布隆過濾器是一種比較巧妙的概率性數據結構,它可以告訴你數據一定不存在或可能存在,相比Map、Set、List等傳統數據結構它佔用內存少、結構更高效。
對於緩存穿透,我們可以將查詢的數據條件都哈希到一個足夠大的布隆過濾器中,用戶發送的請求會先被布隆過濾器攔截,一定不存在的數據就直接攔截返回了,從而避免下一步對數據庫的壓力。
2.緩存擊穿

緩存擊穿是指當某一key的緩存過期時大並發量的請求同時訪問此key,瞬間擊穿緩存服務器直接訪問數據庫,讓數據庫處於負載的情況。
常發生在秒殺系統, 搶購
解法
- 異步定時更新
- 在緩存處理上,同理,比如某一個熱點數據的過期時間是1小時,那麼每59分鐘,通過定時任務去更新這個熱點key,並重新設置其過期時間。
- 互斥鎖
- 在緩存處理上,通常使用一個互斥鎖來解決緩存擊穿的問題。簡單來說就是當Redis中根據key獲得的value值為空時,先鎖上,然後從數據庫加載,加載完畢,釋放鎖。若其他線程也在請求該key時,發現獲取鎖失敗,則先阻塞。. 只有一個線程去後端查詢
- 但是會降低吞吐量
3.緩存雪崩

緩存雪崩是指當大量緩存同時過期或緩存服務當機,所有請求的都直接訪問數據庫,造成數據庫高負載,影響性能,甚至數據庫當機。
解法
- 不同的過期時間
- 為了避免大量的緩存在同一時間過期,可以把不同的key過期時間設置成不同的, 並且通過定時刷新的方式更新過期時間。
- 集群 : 存增加多個副本,當緩存異常時,再讀取其他緩存副本
- 為了避免門店出問題導致大量顧客直接打電話到總部,可以考慮開更多的門店,將用戶分流到多個店鋪中。
- 在緩存雪崩問題防治上面,一個比較典型的技術就是採用集群方式部署,使用集群可以避免服務單點故障。
- 為了保證副本的可用性,盡量將多個緩存副本部署在不同機架上,降低風險。
- 增加實時監控,及時預警。通過機器替換、各種故障自動轉移策略,快速恢復緩存對外的服務能力
4.緩存預熱
所謂緩存預熱就是將一些可能經常使用數據在系統啟動的時候預先設置到緩存中,這樣可以避免在使用到的時候先去數據庫中查詢。 這就是緩存預熱,名氣高大上,實際上很簡單沒有,這個緩存預熱我在實際場景是經常使用的。 還有一種方式就是添加一個緩存刷新頁,這樣通過人工干預的方式將一些可能為熱點的key添加到緩存中。
5.緩存降級
當訪問量突然劇增(例如下班的點,大家都在地鐵上刷手機呢)、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。 系統可以根據一些關鍵數據進行自動降級,降級的最終目的是保證核心服務可用,即使是有損的。但是有的一些業務的核心服務是不能降級的。這是一種丟卒保帥的思想。
6. 緩存熱點
對於突發事件,大量用戶同時去訪問熱點信息,這個突發熱點信息所在的緩存節點就很容易出現過載和卡頓現象,甚至Crash,我們稱之為緩存熱點。 這個在新浪微博經常遇到,某大V明星出軌、結婚、離婚,瞬間引發數百千萬的吃瓜群眾圍觀,訪問同一個key,流量集中打在一個緩存節點機器,很容易打爆網卡、帶寬、CPU的上限,最終導致緩存不可用。
解法
- 首先能先找到這個熱key來,比如通過Spark實時流分析,及時發現新的熱點key。
- 將集中化流量打散,避免一個緩存節點過載。由於只有一個key,我們可以在key的後面拼上有序编号,比如key#01、key#02。。。key#10多個副本,這些加工後的key位於多個緩存節點上。
- 每次請求時,客戶端隨機訪問一個即可
可以設計一個緩存服務治理管理後台,實時監控緩存的SLA,並打通分佈式配置中心,對於一些hot key可以快速、動態擴容。
Reference
使用過Redis分佈式鎖麼,它是什麼回事?
先拿setnx來爭搶鎖,搶到之後,再用expire給鎖加一個過期時間防止鎖忘記了釋放。
https://blog.csdn.net/lihao21/article/details/49104695
這時候對方會告訴你說你回答得不錯,然後接著問如果在setnx之後執行expire之前進程意外crash或者要重啟維護了,那會怎麼樣?
這時候你要給予驚訝的反饋:唉,是喔,這個鎖就永遠得不到釋放了。緊接著你需要抓一抓自己得腦袋,故作思考片刻,好像接下來的結果是你主動思考出來的,然後回答:我記得set指令有非常複雜的參數,這個應該是可以同時把setnx和expire合成一條指令來用的!對方這時會顯露笑容,心裡開始默念:摁,這小子還不錯。
SET resource-name anystring NX EX max-lock-time
三種解法: https://my.oschina.net/wuchanghao/blog/1827226my.oschina.net
Golang 解法: https://blog.csdn.net/liyunlong41/article/details/89817552
package redis
import (
"github.com/garyburd/redigo/redis"
)
const (
redisLockTimeout = 10 // 10 seconds
)
func Lock(key string) (isLock bool, err error) {
// 從連接池中取一個con連接,pool可以自己定義
con := pool.Get()
defer con.Close()
// 這裏需要redis.String包一下,才能返回redis.ErrNil
_, err = redis.String(con.Do("set", key, 1, "ex", redisLockTimeout, "nx"))
if err != nil {
if err == redis.ErrNil {
err = nil
return
}
return
}
isLock = true
return
}
func Unlock(key string) (err error) {
con := pool.Get()
defer con.Close()
_, err = con.Do("del", key)
if err != nil {
return
}
return
}
如何找出以某個固定的已知的前綴開頭的 key?比如,10 億個裡面找出 10w 個
假如Redis裡面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如果將它們全部找出來?使用keys指令可以掃出指定模式的key列表。對方接著追問:如果這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?這個時候你要回答redis關鍵的一個特性:redis的單線程的。keys指令會導致線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。
使用過Redis做異步隊列嗎,你是怎麼用的?
- 一般使用list結構作為隊列,rpush生產消息,lpop消費消息。當lpop沒有消息的時候,要適當sleep一會再重試。
如果對方追問可不可以不用sleep呢?
- list還有個指令叫blpop(Blpop 命令移出並獲取列表的第一個元素),在沒有消息的時候,它會阻塞住直到消息到來。
如果對方追問能不能生產一次消費多次呢?
- 使用pub/sub主題訂閱者模式,可以實現1:N的消息隊列。
如果對方追問pub/sub有什麼缺點?
在消費者下線的情況下,生產的消息會丟失,得使用專業的消息隊列如rabbitmq等。
如果對方追問redis如何實現延時隊列?我估計現在你很想把面試官一棒打死如果你手上有一根棒球棍的話,怎麼問的這麼詳細。但是你很克制,然後神態自若的回答道:
- 使用sortedset(有序集和)=zset,拿時間戳作為score,消息內容作為key調用zadd來生產消息,消費者用zrangebyscore指令獲取N秒之前的數據輪詢進行處理。到這裡,面試官暗地裡已經對你豎起了大拇指。但是他不知道的是此刻你卻豎起了中指,在椅子背後。
如果有大量的key需要設置同一時間過期,一般需要注意什麼?
如果大量的key過期時間設置的過於集中,到過期的那個時間點,redis可能會出現短暫的卡頓現象。一般需要在時間上加一個隨機值,使得過期時間分散一些。
Redis如何做持久化的?
bgsave做鏡像全量持久化,aof做增量持久化。 因為bgsave會耗費較長時間,不夠實時,在停機的時候會導致大量丟失數據,所以需要aof來配合使用。在redis實例重啟時,優先使用aof來恢復內存的狀態,如果沒有aof日誌,就會使用rdb文件來恢復。
https://www.cnblogs.com/wdliu/p/9377278.html
如果再問aof文件過大恢復時間過長怎麼辦?
你告訴面試官,Redis會定期做aof重寫,壓縮aof文件日誌大小。如果面試官不夠滿意,再拿出殺手鐧答案,Redis4.0之後有了混合持久化的功能,將bgsave的全量和aof的增量做了融合處理,這樣既保證了恢復的效率又兼顧了數據的安全性。這個功能甚至很多面試官都不知道,他們肯定會對你刮目相看。如果對方追問那如果突然機器掉電會怎樣?取決於aof日誌sync屬性的配置,如果不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失數據。但是在高性能的要求下每次都sync是不現實的,一般都使用定時sync,比如1s1次,這個時候最多就會丟失1s的數據。

Pipeline有什麼好處,為什麼要用pipeline?
可以將多次IO往返的時間縮減為一次,前提是pipeline執行的指令之間沒有因果相關性。使用redis-benchmark進行壓測的時候可以發現影響redis的QPS峰值的一個重要因素是pipeline批次指令的數目。
是否使用過Redis集群,集群的原理是什麼?
Redis Sentinal著眼於高可用,在master宕機時會自動將slave提升為master,繼續提供服務。 Redis Cluster著眼於擴展性,在單個redis內存不足時,使用Cluster進行分片存儲。
Redis 相比 memcached 有哪些優勢?
Memcached 所有的值均是簡單的字符串,Redis 作為其替代者,支持更為豐富的數據類型;Redis 的速度比 Memcached 要快;Redis 可以持久化其數據,其他的有點在上面的第一題中也有提及,我就不在重複寫了。
Redis 的幾種數據淘汰策略?
- noeviction:禁止淘汰數據。
- allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。
- volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存放。
- allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。
- volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。
- volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。
一個字符串類型的值能存儲最大容量是多少?
512M
如果有大量的 key 需要設置同一時間過期,一般需要注意什麼?
如果大量的 Key 過期時間設置的過於集中,到過期的那個時間點,Redis 可能會出現短暫的卡頓現象。一般需要在時間上加一個隨機值,使得過期時間分散一些。
Redis 集群方案應該怎麼做?都有哪些方案?
codis 是開源的,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在節點數量改變情況下,舊節點數據可恢復到新 hash 節點。
redis cluster 3.0 自帶的集群,特點在於他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持節點設置從節點。
在業務代碼層實現,起幾個毫無關聯的 redis 實例,在代碼層,對 key 進行 hash 計算,然後去對應的 redis 實例操作數據。這種方式對 hash 層代碼要求比較高,考慮部分包括,節點失效後的替代算法方案,數據震盪後的自動腳本恢復,實例的監控等等。
bgsave 做鏡像全量持久化的原理是什麼?
fork 和 cow。fork 是指 redis 通過創建子進程來進行 bgsave 操作,cow 指的是 copy on write,子進程創建後,父子進程共享數據段,父進程繼續提供讀寫服務,寫髒的頁面數據會逐漸和子進程分離開來。
Redis 的同步機制了解麼?
Redis 可以使用主從同步,從從同步。第一次同步時,主節點做一次 bgsave,並同時將後續修改操作記錄到內存 buffer,待完成後將 rdb 文件全量同步到複製節點,複製節點接受完成後將 rdb 鏡像加載到內存。加載完成後,再通知主節點將期間修改的操作記錄同步到複製節點進行重放就完成了同步過程。
什麼是緩存穿透?如何避免?什麼是緩存雪崩?何如避免?
緩存穿透:一般的緩存系統,都是按照 key 去緩存查詢,如果不存在對應的 value,就應該去後端系統查找(比如DB)。一些惡意的請求會故意查詢不存在的 key,請求量很大,就會對後端系統造成很大的壓力。這就叫做緩存穿透。
避免方法:
- 對查詢結果為空的情況也進行緩存,緩存時間設置短一點,或者該 key 對應的數據 insert 了之後清理緩存。
- 對一定不存在的 key 進行過濾。可以把所有的可能存在的 key 放到一個大的 Bitmap 中,查詢時通過該 bitmap 過濾。
緩存雪崩:當緩存伺服器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給後端系統帶來很大壓力。導致系統崩潰。
避免方法:
- 在緩存失效後,通過加鎖或者隊列來控制讀資料庫寫緩存的線程數量。比如對某個 key 只允許一個線程查詢數據和寫緩存,其他線程等待。
- 做二級緩存,A1 為原始緩存,A2 為拷貝緩存,A1 失效時,可以訪問 A2,A1 緩存失效時間設置為短期,A2 設置為長期。
- 不同的 key,設置不同的過期時間,讓緩存失效的時間點儘量均勻
說說 Redis 哈希槽的概念?
Redis 集群沒有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 個哈希槽,每個 key 通過 CRC16 校驗後對 16384 取模來決定放置哪個槽,集群的每個節點負責一部分 hash 槽。
Redis 集群會有寫操作丟失嗎?
Redis 並不能保證數據的強一致性,這意味這在實際中集群在特定的條件下可能會丟失寫操作。
Redis 集群如何選擇資料庫?
Redis 集群目前無法做資料庫選擇,默認在 0 資料庫。
怎麼理解 Redis 事務?
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。
通常使用 MULTI、EXEC、DISCARD、WATCH 這幾個命令操作事務。
Jedis 與 Redisson 對比有什麼優缺點?
Jedis 是 Redis 的 Java 實現的客戶端,其 API 提供了比較全面的 Redis 命令的支持;
Redisson 實現了分布式和可擴展的 Java 數據結構,和 Jedis 相比,功能較為簡單,不支持字符串操作,不支持排序、事務、管道、分區等 Redis 特性。Redisson 的宗旨是促進使用者對 Redis 的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。