回答上這些Redis面試題,20K穩(wěn)了

##### 1、緩存雪崩
1. 舉例
* 雙十一期間,所有用戶一打開淘寶就是進(jìn)入首頁(yè),首頁(yè)的壓力非常大,為了提高并發(fā),將網(wǎng)站首頁(yè)數(shù)據(jù)都緩存到redis 里,所有的redis key 失效時(shí)間都是3小時(shí)。
* 雙十一當(dāng)天大量用戶 剁手狂歡,這時(shí)候3個(gè)小時(shí)過(guò)去了,redis里首頁(yè)的key 緩存全部失效,這時(shí)候redis里查詢不到數(shù)據(jù)了,只能去 數(shù)據(jù)庫(kù) 中查詢,造成數(shù)據(jù)庫(kù)無(wú)法響應(yīng) 掛掉。用戶進(jìn)不去首頁(yè)沒法剁手了,馬爸爸就 不開心了,把這個(gè)程序員外派到 非洲了
2. 一句話總結(jié)
* 在高并發(fā)下,大量緩存key在 同一時(shí)間失效,大量請(qǐng)求直接落在數(shù)據(jù)庫(kù)上,導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)。
3. 解決方案
* 隨機(jī)設(shè)置key失效時(shí)間,避免大量key集體失效。
+ setRedis (Key, value, time + Math.random() * 10000);
* 若是集群部署,可將熱點(diǎn)數(shù)據(jù)均勻分布在不同的Redis庫(kù)中也能夠避免key全部失效問題o
* 不設(shè)置過(guò)期時(shí)間(不推薦)
* 跑定時(shí)任務(wù),在緩存失效前刷進(jìn)新的緩存(不推薦)
##### 2、緩存穿透
1. 舉例
* 老哥做了一個(gè)網(wǎng)站火了,動(dòng)了別人的蛋糕,于是開始 瘋狂攻擊老哥的網(wǎng)站,由于老哥 網(wǎng)絡(luò)安全方面學(xué)藝不精被人鉆了空子。
* 某人用腳本瘋狂的給老哥發(fā)送請(qǐng)求,查詢 id = - 的數(shù)據(jù),redis并沒有這樣的數(shù)據(jù),這時(shí)候就穿透redis,直按打到了 數(shù)據(jù)庫(kù) 上。
* 半夜老哥在睡覺并沒有察覺,他瘋狂攻擊老哥一晚上,結(jié)果把 數(shù)據(jù)庫(kù) 搞掛了,然后老哥的 網(wǎng)站也掛了
2. 一句話總結(jié)
* redis級(jí)存和 數(shù)據(jù)庫(kù)中沒有相關(guān)數(shù)據(jù)(例用戶直接攜帶 d<= 的參數(shù)不斷發(fā)起請(qǐng)求),redis中沒有這樣的數(shù)據(jù),無(wú)法進(jìn)行攔截,直接被穿透到 數(shù)據(jù)庫(kù) ,導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大宕機(jī)。
3. 解決方案
* 對(duì)不存在的數(shù)據(jù)緩存到redis中,設(shè)置key,value值為null(不管是數(shù)據(jù)未null還是系統(tǒng)bug問題),并設(shè)置一個(gè)短期過(guò)期時(shí)間段,避免過(guò)期時(shí)間過(guò)長(zhǎng)影響正常用戶使用。
* 拉黑該IP地址。
* 對(duì)參數(shù)進(jìn)行校驗(yàn),不合法參數(shù)進(jìn)行攔截
* 布隆過(guò)濾器 將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap(位圖)中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被 這個(gè)bitmap攔截掉,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力。
##### 3、緩存擊穿
1. 舉例
* 雙十一馬爸爸突發(fā)奇想,想拍賣自己穿了20年的老布鞋,并且附帶本人簽名,程序員將該鞋的信息存到了redis中,設(shè)置了3小時(shí)過(guò)期。尋思3小時(shí)夠他們搶了吧,但他低估了馬爸爸的魅力。
* 該商品引起了一千萬(wàn)人關(guān)注,這些人不斷的競(jìng)拍這雙鞋,價(jià)格越拍越高,馬爸爸樂開了花,競(jìng)拍了2小時(shí)59 分,馬上要拍到一個(gè)億了,突然這雙鞋在redis里的key數(shù)據(jù) 過(guò)期了,導(dǎo)致該key的大量請(qǐng)求,都打到了數(shù)據(jù)庫(kù),直接導(dǎo)致數(shù)據(jù)庫(kù)掛掉了,服務(wù)無(wú)法響應(yīng)。
* 競(jìng)拍到此結(jié)束,鞋沒賣出去,馬爸爸又不開心了,把這個(gè)程序員也 外派到非洲了.
2. 一句話總結(jié)
* 某一個(gè)熱點(diǎn)key,在不停地扛著高并發(fā),當(dāng)這個(gè)熱點(diǎn)key在 失效的一瞬間,持續(xù)的高并發(fā)訪問就 擊破緩存直接訪問數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)。
3. 解決方案
* 設(shè)置熱點(diǎn)數(shù)據(jù)"永不過(guò)期"。
* 加上瓦斥鎖:上面的現(xiàn)象是多個(gè)線程同時(shí)去查詢數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請(qǐng)求上使用一個(gè)互斥鎖來(lái)鎖住它
* 其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(shù)據(jù),然后將數(shù)據(jù)放到redis緩存起來(lái)。后面的線程進(jìn)來(lái)發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存
4. 最后總結(jié)
* 雪崩是大面積的key緩存失效; 穿透是redis里不存在這個(gè)緩存key; 擊穿是redis 某一個(gè)熱點(diǎn)key突然失效,最終的受害者都是數(shù)據(jù)庫(kù)。
5. 思考
* 未雨綢繆: 將redis、MySQL等搭建成高可用的集群,防止單點(diǎn)
* 亡羊補(bǔ)牢: 服務(wù)中進(jìn)行限流 + 降級(jí),防止MySQL被打崩潰。
* 重振旗鼓: Redis 持久化 RDB+AOF,宕機(jī)重啟,自動(dòng)從磁盤上加載數(shù)據(jù),快速恢復(fù)緩存數(shù)據(jù)
#### 4、單線程的 Redis 為什么這么快?
1. Redis 有多快 官方給出的答案是讀寫速度 10萬(wàn)/秒,如果說(shuō)這是在單線程情況下跑出來(lái)的成績(jī),你會(huì)不會(huì)驚訝?為什么單線程的 Redis 速度這么快? 原因有以下幾點(diǎn)
純內(nèi)存操作:
* Redis 是完全基于內(nèi)存的,所以讀寫效率非常的高,當(dāng)然 Redis 存在持久化操作,在持久化操作是都是 fork了進(jìn)程和利用 Linux 系統(tǒng)的頁(yè)緩存技術(shù)來(lái)完成,并不
會(huì)影響 Redis 的性能。
* 單線程操作:單線程并不是壞事,單線程可以避免了頻繁的上下文切換,頻繁的上下文切挽也會(huì)影響件能的
* 合理高效的數(shù)據(jù)結(jié)構(gòu)0
* 采用了非陽(yáng)塞10 多路復(fù)用機(jī)制: 多路1/0復(fù)用模型是利用 select、pol、epoll 可以同時(shí)監(jiān)察多個(gè)流的10 事的能力,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有 0 事件時(shí),就從阻塞態(tài)中喚醒,于是程字就會(huì)輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作。
#### 5、說(shuō)一說(shuō) Redis 的數(shù)據(jù)過(guò)期淘汰策略?
1. Redis 中數(shù)據(jù)過(guò)期策略采用定期刪除+惰性刪除策略。
* 定期刪除、惰性刪除策略是什么?
+ 定期刪除策略: Redis 啟用一個(gè)定時(shí)器定時(shí)監(jiān)視所有的 key,判斷key是否過(guò)期,過(guò)期的話就刪除。這種策略可以保證過(guò)期的 key 最終都會(huì)被刪除,但是也存在嚴(yán)重的缺點(diǎn): 每次都遍歷內(nèi)存中所有的數(shù)據(jù),非常消耗 CPU資源,并且當(dāng) key 已過(guò)期,但是定時(shí)器還處于未喚起狀態(tài),這段時(shí)間內(nèi) key 仍然可以用。
+ 情性刪除策略:在獲取 key 時(shí),先判斷 ey 是否過(guò)期,如果過(guò)期則刪除。這種方式存在一個(gè)缺點(diǎn): 如果這個(gè)key 一直未被使用,那么它一直在內(nèi)存中,其實(shí)它已經(jīng)過(guò)期了,會(huì)浪費(fèi)大量的空間。I
2. 定期刪除+惰性刪除策略是如何工作的?
* 這兩種策略天然的互補(bǔ),結(jié)合起來(lái)之后,定時(shí)刪除策略就發(fā)生了一些改變,不在是每次掃描全部的 key 了,而是隨機(jī)抽取一部分 key 進(jìn)行檢查,這樣就降低了對(duì) CPU 資源的損耗,惰性刪除策略互補(bǔ)了為檢查到的key,基本上滿足了所有要求。
* 但是有時(shí)候就是那么的巧,既沒有被定時(shí)器抽取到,又沒有被使用,這些數(shù)據(jù)又如何從內(nèi)存中消失? 沒關(guān)系,還有內(nèi)存淘汰機(jī)制,當(dāng)內(nèi)存不夠用時(shí),內(nèi)存淘汰機(jī)制就會(huì)上場(chǎng)。Redis 內(nèi)存淘汰機(jī)制有以下幾種策略:
+ volatile-ru: 從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集 (server.dbl.expires) 中挑選最近最少使用的數(shù)據(jù)淘汰
+ volatile-t: 從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集 (server.dbl.expires) 中挑選將要過(guò)期的數(shù)據(jù)淘汰。
+ volatile-random: 從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集 (server.db[.expires) 中任意選擇數(shù)據(jù)淘汰·allkeys-ru: 當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的key (這個(gè)是最常用的)
+ allkeys-random: 從數(shù)據(jù)集 (server.db[i].dict) 中任意選擇數(shù)據(jù)淘汰
+ no-eviction: 禁止驅(qū)逐數(shù)據(jù),永不過(guò)期,也就是說(shuō)當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),新寫入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒人使用吧! (默認(rèn)值)
+ 4.0版本后增加以下兩種:。
- volatile-fu: 從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db.expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
- allkeys-Ifu: 當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最不經(jīng)常使用的key
#### 6、redis 和 memcached 的區(qū)別?
1. 存儲(chǔ)方式不同: memcache 把數(shù)據(jù)全部存在內(nèi)存之中,斷電后會(huì)掛掉,數(shù)據(jù)不能超過(guò)內(nèi)存大小; Redis 有部份存在硬盤上,這樣能保證數(shù)據(jù)的持久性。
2. 數(shù)據(jù)支持類型: memcache 對(duì)數(shù)據(jù)類型支持相對(duì)簡(jiǎn)單;Redis 有復(fù)雜的數(shù)據(jù)類型
3. 使用底層模型不同: 它們之間底層實(shí)現(xiàn)方式,以及與客戶端之間通信的應(yīng)用協(xié)議不一樣,Redis 自己構(gòu)建了 vm機(jī)制,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求,
4. value 值大小不同: Redis 最大可以達(dá)到1gb; memcache 只有 1mb。
#### 7、redis 的線程模型是怎么樣的?
1. redis 內(nèi)部使用文件事件處理器 fle event handler,這個(gè)文件事件處理器是單線程的,所以redis 才叫做單線程的模型。它采用10 多路復(fù)用機(jī)制同時(shí)監(jiān)聽多個(gè) socket,根據(jù) socket 上的事件來(lái)選擇對(duì)應(yīng)的事件處理器進(jìn)行處理。
2. 文件事件處理器的結(jié)構(gòu)包含 4 個(gè)部分
* 多個(gè) socket
* I0 多路復(fù)用程序
* 文件事件分派器
* 事件處理器 (連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)多個(gè) socket 可能會(huì)并發(fā)產(chǎn)生不同的操作,每個(gè)操作對(duì)應(yīng)不同的文件事件,但是 0 多路復(fù)用程序會(huì)監(jiān)聽多人socket,會(huì)將 socket 產(chǎn)生的事件放入隊(duì)列中排隊(duì),事件分派器每次從隊(duì)列中取出一個(gè)事件,把該事件交給對(duì)應(yīng)的事件處理器進(jìn)行處理。
#### 8、為什么是用Redis做緩存,不用MapGuava?
1. 緩存分為本地緩存和分布式緩存。以Java 為例,使用自帶的 map 或者 guava 實(shí)現(xiàn)的是本地緩存,最主要的特點(diǎn)是輕量以及快速,生命周期隨著 vm 的銷毀而結(jié)束,并且在多實(shí)例的情況下,每個(gè)實(shí)例都需要各自保存一份緩存,緩存不具有一致性。
2. 使用 redis 或 memcached 之類的稱為分布式緩存,在多實(shí)例的情況下,各實(shí)例共用一份緩存數(shù)據(jù),緩存具有致性。缺點(diǎn)是需要保持 redis 或 memcached服務(wù)的高可用,整個(gè)程序架構(gòu)上較為復(fù)雜。
#### 9、Redis哨兵
1. sentine1 (哨兵)是Redis 高可用的解決方案,可以運(yùn)行多個(gè)Sentinel組成一個(gè)哨兵分布式系統(tǒng)
* Sentinel哨兵職責(zé)如下:
+ 監(jiān)控(Monitoring): Sentinel 會(huì)不斷地定期檢查你的主服務(wù)器和從服務(wù)器是否運(yùn)作正常
+ 提醒(Notification):當(dāng)被監(jiān)控的某個(gè) Redis 服務(wù)器出現(xiàn)問題時(shí),Sentinel 可以通過(guò) AP1向管理員或者其他應(yīng)用程序發(fā)送通知。
+ 自動(dòng)故噴遷移(Automaticfailover): 當(dāng)一個(gè)主服務(wù)器 不能正常工作時(shí),Sentinel 會(huì)開始一次自動(dòng)故障遷移操作它會(huì)將失效主服務(wù)器的其中一個(gè)從服務(wù)器 升級(jí) 為新的主服務(wù)器,并讓失效主服務(wù)器的其他從服務(wù)器改為復(fù)制新的主服務(wù)器;當(dāng)客戶端試圖連接失效的主服務(wù)器時(shí),集群也會(huì)向客戶端返回新主服務(wù)器的地址,使得集群可以使用新主服務(wù)器代替失效服務(wù)器。
+ 統(tǒng)一的配置管理: 連接者詢問sentinel取得主從的地址
#### 10. 如何實(shí)現(xiàn) redis 事務(wù)?
1. Redis 通過(guò) MULTI、EXEC、WATCH 等命令來(lái)實(shí)現(xiàn)事務(wù)(transaction)功能。事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包然后一次性、按順序地執(zhí)行多個(gè)命令的機(jī)制,并且在事務(wù)執(zhí)行期間,服務(wù)器不會(huì)中斷事務(wù)而改去執(zhí)行其他客戶端的命令請(qǐng)求,它會(huì)將事務(wù)中的所有命令都執(zhí)行完畢,然后才去處理其他客戶端的命令請(qǐng)求。
2. 在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫(kù)中,常常用 ACID 性質(zhì)來(lái)檢驗(yàn)事務(wù)功能的可靠性和安全性。在 Redis 中,事務(wù)總是具有原子性 (Atomicity) 、一致性(Consistency) 和隔離性 (lsolation),并且當(dāng) Redis 運(yùn)行在某種特定的持久化模式下時(shí),事務(wù)也具有持久性 (Durability)。