Java面試題沖刺第二天--Redis篇
面試題1:為什么要用 Redis ?業(yè)務(wù)在哪塊兒用到的?
正經(jīng)回答:
Redis是眼下最為人熟知的緩解高并發(fā)、提升高可用能力的手段之一,再提升服務(wù)器性能方面效果顯著。
這里不得不提到高并發(fā)場景,我們知道,并發(fā)場景下核心點(diǎn)在數(shù)據(jù)庫,引入緩存(以及引入任何負(fù)載均衡、集群等策略)的目的都是在減輕數(shù)據(jù)庫壓力,讓更多原本打到DB上的請求,在中間被攔截處理掉。就像你請個(gè)假屁大點(diǎn)兒事還要大老板簽字一樣?
通俗易懂點(diǎn)兒說,高并發(fā)對服務(wù)器來說,就好比你被人錘一拳,這拳頭可是硬的很,光著膀子的話一拳就給我干吐血。。那么我為了承受住這一拳?穿棉襖、穿護(hù)墊、穿…是吧,只要夠厚,我都以為你在給我撓癢癢~同理,Redis就是一件又厚又彈的棉襖。
話說回來,它有多厚多彈呢?操作緩存就是直接操作內(nèi)存,速度相當(dāng)快,直接操作緩存能夠承受的請求數(shù)是遠(yuǎn)遠(yuǎn)大于直接訪問數(shù)據(jù)庫的。
Redis優(yōu)勢:
- 讀寫性能優(yōu)異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
- 支持?jǐn)?shù)據(jù)持久化,支持AOF和RDB兩種持久化方式。
- 支持事務(wù),Redis的所有操作都是原子性的,同時(shí)Redis還支持對幾個(gè)操作合并后的原子性執(zhí)行。
- 數(shù)據(jù)結(jié)構(gòu)豐富,除了支持string類型的value外還支持hash、set、zset、list等數(shù)據(jù)結(jié)構(gòu)。
- 支持主從復(fù)制,主機(jī)會(huì)自動(dòng)將數(shù)據(jù)同步到從機(jī),可以進(jìn)行讀寫分離。
- 支持大量集群節(jié)點(diǎn)。
假如用戶第一次訪問數(shù)據(jù)庫中的某些數(shù)據(jù)。這個(gè)過程會(huì)比較慢,因?yàn)槭菑挠脖P上讀取的。將該用戶訪問的數(shù)據(jù)存在數(shù)Redis中,這樣下一次再訪問這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了。同樣,我們可以把數(shù)據(jù)庫中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請求會(huì)直接打到緩存而不是數(shù)據(jù)庫(即半路攔截掉了)。如果數(shù)據(jù)庫中的對應(yīng)數(shù)據(jù)改變的之后,同步改變緩存中相應(yīng)的數(shù)據(jù)即可!
在我們業(yè)務(wù)中,包括熱點(diǎn)詞查詢、一些實(shí)時(shí)排行榜數(shù)據(jù)、訪問量點(diǎn)贊量統(tǒng)計(jì)、Session共享等等都可以引入Redis來處理。
深入追問: 追問1:Redis里有哪些數(shù)據(jù)類型?
豐富的數(shù)據(jù)類型,Redis有8種數(shù)據(jù)類型,當(dāng)然常用的主要是 String、Hash、List、Set、 SortSet 這5種類型,他們都是基于鍵值的方式組織數(shù)據(jù)。每一種數(shù)據(jù)類型提供了非常豐富的操作命令,可以滿足絕大部分需求,如果有特殊需求還能自己通過 lua 腳本自己創(chuàng)建新的命令(具備原子性);
追問2:Redis與Memcached有哪些區(qū)別?
兩者都是非關(guān)系型內(nèi)存鍵值數(shù)據(jù)庫
,現(xiàn)在公司一般都是用 Redis 來實(shí)現(xiàn)緩存,為什么不用Memcached呢?
參數(shù) | Redis | Memcached |
---|---|---|
類型 | 1. 支持內(nèi)存 2. 非關(guān)系型數(shù)據(jù)庫 | 1. 支持內(nèi)存 2. 鍵值對形式 3. 緩存形式 |
數(shù)據(jù)存儲(chǔ)類型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set | 1. 文本型 2. 二進(jìn)制類型 |
附加功能 | 1. 發(fā)布/訂閱模式 2. 主從分區(qū) 3. 序列化支持 4. 腳本支持【Lua腳本】 | 多線程服務(wù)支持 |
網(wǎng)絡(luò)IO模型 | 單線程的多路 IO 復(fù)用模型 | 多線程,非阻塞IO模式 |
持久化支持 | 1. RDB 2. AOF | 不支持 |
集群模式 | 原生支持 cluster 模式,可以實(shí)現(xiàn)主從復(fù)制,讀寫分離 | 沒有原生的集群模式,需要依靠客戶端來實(shí)現(xiàn)往集群中分片寫入數(shù)據(jù) |
內(nèi)存管理機(jī)制 | 在 Redis 中,并不是所有數(shù)據(jù)都一直存儲(chǔ)在內(nèi)存中,可以將一些很久沒用的 value 交換到磁盤 | Memcached 的數(shù)據(jù)則會(huì)一直在內(nèi)存中,Memcached 將內(nèi)存分割成特定長度的塊來存儲(chǔ)數(shù)據(jù),以完全解決內(nèi)存碎片的問題。 |
適用場景 | 復(fù)雜數(shù)據(jù)結(jié)構(gòu),有持久化,高可用需求,value存儲(chǔ)內(nèi)容較大,最大512M | 純key-value,數(shù)據(jù)量非常大,并發(fā)量非常大的業(yè)務(wù) |
- memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數(shù)據(jù)類型
- redis的速度比memcached快很多
- redis可以持久化數(shù)據(jù)到磁盤,這個(gè)很關(guān)鍵,宕機(jī)斷電不再是硬傷。
追問3:那Redis怎樣防止異常數(shù)據(jù)不丟失的?如何持久化?
RDB 持久化 (快照)
- 將某個(gè)時(shí)間點(diǎn)的所有數(shù)據(jù)生成快照,存放到硬盤上。當(dāng)數(shù)據(jù)量很大時(shí),會(huì)很慢。
- 可以將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本。
- 如果系統(tǒng)發(fā)生故障,將會(huì)丟失最后一次創(chuàng)建快照之后的數(shù)據(jù)。
AOF 持久化(即時(shí)更新)
- 將寫命令添加到 AOF 文件(Append Only File)的末尾。
- 使用 AOF 持久化需要設(shè)置同步選項(xiàng),從而確保寫命令同步到磁盤文件上的時(shí)機(jī)。這是因?yàn)閷ξ募M(jìn)行寫入并不會(huì)馬上將內(nèi)容同步到磁盤上,而是先存儲(chǔ)到緩沖區(qū),然后由操作系統(tǒng)決定什么時(shí)候同步到磁盤。
有以下同步選項(xiàng)(同步頻率):
always
每個(gè)寫命令都同步;everysec
每秒同步一次;no
讓操作系統(tǒng)來決定何時(shí)同步。everysec
選項(xiàng)比較合適,可以保證系統(tǒng)崩潰時(shí)只會(huì)丟失一秒左右的數(shù)據(jù),并且 Redis 每秒執(zhí)行一次同步對服務(wù)器性能幾乎沒有任何影響;
面試題2:Redis為啥是單線程的?
Redis is single threaded. How can I exploit multiple CPU / cores? It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU. However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier. You can find more information about using multiple Redis instances in the Partitioning page. However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.
正經(jīng)回答:
上面是Redis官網(wǎng)給的解釋(官方文檔鏈接),翻譯后簡單說,因?yàn)镽edis的瓶頸不是CPU的運(yùn)行速度,而往往是網(wǎng)絡(luò)帶寬和機(jī)器的內(nèi)存大小。
再說了,單線程切換開銷小,容易實(shí)現(xiàn)。既然單線程容易實(shí)現(xiàn),而且CPU不會(huì)成為瓶頸,那就順理成章地采用單線程的方案,當(dāng)然了,也是為了避免多線程存在的很多坑。對了,一個(gè)節(jié)點(diǎn)是一個(gè)單線程。
深入追問:
追問1:單線程只使用了單核CPU,太浪費(fèi),有什么辦法發(fā)揮多核CPU的性能嘛?
我們可以通過在單機(jī)開多個(gè)Redis 實(shí)例,我們一直在強(qiáng)調(diào)的單線程,只是在處理我們的網(wǎng)絡(luò)請求的時(shí)候只有一個(gè)線程來處理。實(shí)際上,一個(gè)正式的Redis Server運(yùn)行的時(shí)候肯定是不止一個(gè)線程的,都是集群形式,多少多少個(gè)節(jié)點(diǎn),所以實(shí)際環(huán)境中大家不用擔(dān)心這種問題。
面試題3:聊一下對緩存穿透、緩存擊穿、緩存雪崩的理解吧
正經(jīng)回答:
- 緩存穿透:指
緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù)
,導(dǎo)致所有的請求都打到數(shù)據(jù)庫上,然后數(shù)據(jù)庫還查不到(如null),造成數(shù)據(jù)庫短時(shí)間線程數(shù)被打滿而導(dǎo)致其他服務(wù)阻塞,最終導(dǎo)致線上服務(wù)不可用,這種情況一般來自黑客同學(xué)。 - 緩存擊穿:指
緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù)
(一般是熱點(diǎn)數(shù)據(jù)緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多,同時(shí)讀緩存沒讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫去查,引起數(shù)據(jù)庫壓力瞬間增大,線上系統(tǒng)卡住。 - 緩存雪崩:
指緩存同一時(shí)間大面積的失效
,緩存擊穿升級版。
深入追問:
追問1:那你說一下針對緩存擊穿的解決方法
- 根據(jù)實(shí)際業(yè)務(wù)情況,在Redis中維護(hù)一個(gè)熱點(diǎn)數(shù)據(jù)表,批量設(shè)為永不過期(如top1000),并定時(shí)更新top1000數(shù)據(jù)。
- 加互斥鎖(mutex key)
互斥鎖 緩存擊穿后,多個(gè)線程會(huì)同時(shí)去查詢數(shù)據(jù)庫的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請求上使用一個(gè)互斥鎖來鎖住它。其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(shù)據(jù),然后做緩存。后面的線程進(jìn)來發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存。
static Lock reenLock = new ReentrantLock(); public List<String> getData04() throws InterruptedException { List<String> result = new ArrayList<String>(); // 從緩存讀取數(shù)據(jù) result = getDataFromCache(); if (result.isEmpty()) { if (reenLock.tryLock()) { try { System.out.println("拿到鎖了,從DB獲取數(shù)據(jù)庫后寫入緩存"); // 從數(shù)據(jù)庫查詢數(shù)據(jù) result = getDataFromDB(); // 將查詢到的數(shù)據(jù)寫入緩存 setDataToCache(result); } finally { reenLock.unlock();// 釋放鎖 } } else { result = getDataFromCache();// 先查一下緩存 if (result.isEmpty()) { System.out.println("我沒拿到鎖,緩存也沒數(shù)據(jù),先小憩一下"); Thread.sleep(100);// 小憩一會(huì)兒 return getData04();// 重試 } } } return result; }
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java實(shí)現(xiàn)HashMap排序方法的示例詳解
這篇文章主要通過一些示例為大家介紹了Java對HashMap進(jìn)行排序的方法,幫助大家更好的理解和使用Java,感興趣的朋友可以了解一下2022-05-05mybatis 插件: 打印 sql 及其執(zhí)行時(shí)間實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猰ybatis 插件: 打印 sql 及其執(zhí)行時(shí)間實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06詳細(xì)介紹使用Java調(diào)用Python的四種方法
這篇文章主要給大家介紹了關(guān)于使用Java調(diào)用Python的四種方法,每種方法根據(jù)實(shí)際項(xiàng)目需求有其適用場景,其中,推薦使用Runtime.getRuntime()方法,因?yàn)樗鼮楹啙嵡乙子趯?shí)現(xiàn),需要的朋友可以參考下2024-10-10一文搞懂SpringBoot如何利用@Async實(shí)現(xiàn)異步調(diào)用
異步調(diào)用幾乎是處理高并發(fā),解決性能問題常用的手段,如何開啟異步調(diào)用?SpringBoot中提供了非常簡單的方式,就是一個(gè)注解@Async。今天我們重新認(rèn)識(shí)一下@Async,以及注意事項(xiàng)2022-09-09Springboot項(xiàng)目全局異常統(tǒng)一處理案例代碼
最近在做項(xiàng)目時(shí)需要對異常進(jìn)行全局統(tǒng)一處理,主要是一些分類入庫以及記錄日志等,因?yàn)轫?xiàng)目是基于Springboot的,所以去網(wǎng)絡(luò)上找了一些博客文檔,然后再結(jié)合項(xiàng)目本身的一些特殊需求做了些許改造,現(xiàn)在記錄下來便于以后查看2023-01-01