- 面試題1:為什么要用 Redis ?業(yè)務(wù)在哪塊兒用到的?
- 追問(wèn)1:Redis里有哪些數(shù)據(jù)類(lèi)型?
- 追問(wèn)2:Redis與Memcached有哪些區(qū)別?
- 追問(wèn)3:那Redis怎樣防止異常數(shù)據(jù)不丟失的?如何持久化?
- 面試題2:Redis為啥是單線(xiàn)程的?
- 追問(wèn)1:?jiǎn)尉€(xiàn)程只使用了單核CPU,太浪費(fèi),有什么辦法發(fā)揮多核CPU的性能嘛?
- 面試題3:聊一下對(duì)緩存穿透、緩存擊穿、緩存雪崩的理解吧?
- 追問(wèn)1:那你說(shuō)一下針對(duì)緩存擊穿的解決方法?
面試題1:為什么要用 Redis ?業(yè)務(wù)在哪塊兒用到的?
正經(jīng)回答:
Redis是眼下最為人熟知的緩解高并發(fā)、提升高可用能力的手段之一,在提升服務(wù)器性能方面效果顯著。
這里不得不提到高并發(fā)場(chǎng)景,我們知道,并發(fā)場(chǎng)景下核心點(diǎn)在數(shù)據(jù)庫(kù),引入緩存(以及引入任何負(fù)載均衡、集群等策略)的目的都是在減輕數(shù)據(jù)庫(kù)壓力,讓更多原本打到DB上的請(qǐng)求,在中間被攔截處理掉。就像你請(qǐng)個(gè)假屁大點(diǎn)兒事還要大老板簽字一樣?
通俗易懂點(diǎn)兒說(shuō),高并發(fā)對(duì)服務(wù)器來(lái)說(shuō),就好比你被人錘一拳,這拳頭可是硬得很,光著膀子的話(huà)一拳就給我干吐血。。那么我為了承受住這一拳?穿棉襖、穿護(hù)墊、穿…是吧,只要夠厚,我都以為你在給我撓癢癢~同理,Redis就是一件又厚又彈的棉襖。
話(huà)說(shuō)回來(lái),它有多厚多彈呢?操作緩存就是直接操作內(nèi)存,速度相當(dāng)快,直接操作緩存能夠承受的請(qǐng)求數(shù)是遠(yuǎn)遠(yuǎn)大于直接訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的。
Redis優(yōu)勢(shì):
- 讀寫(xiě)性能優(yōu)異, Redis能讀的速度是110000次/s,寫(xiě)的速度是81000次/s。
- 支持?jǐn)?shù)據(jù)持久化,支持AOF和RDB兩種持久化方式。
- 支持事務(wù),Redis的所有操作都是原子性的,同時(shí)Redis還支持對(duì)幾個(gè)操作合并后的原子性執(zhí)行。
- 數(shù)據(jù)結(jié)構(gòu)豐富,除了支持string類(lèi)型的value外還支持hash、set、zset、list等數(shù)據(jù)結(jié)構(gòu)。
- 支持主從復(fù)制,主機(jī)會(huì)自動(dòng)將數(shù)據(jù)同步到從機(jī),可以進(jìn)行讀寫(xiě)分離。
- 支持大量集群節(jié)點(diǎn)。
假如用戶(hù)第一次訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)中的某些數(shù)據(jù)。這個(gè)過(guò)程會(huì)比較慢,因?yàn)槭菑挠脖P(pán)上讀取的。將該用戶(hù)訪(fǎng)問(wèn)的數(shù)據(jù)存在數(shù)Redis中,這樣下一次再訪(fǎng)問(wèn)這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了。同樣,我們可以把數(shù)據(jù)庫(kù)中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶(hù)的一部分請(qǐng)求會(huì)直接打到緩存而不是數(shù)據(jù)庫(kù)(即半路攔截掉了)。如果數(shù)據(jù)庫(kù)中的對(duì)應(yīng)數(shù)據(jù)改變的之后,同步改變緩存中相應(yīng)的數(shù)據(jù)即可!
在我們業(yè)務(wù)中,包括熱點(diǎn)詞查詢(xún)、一些實(shí)時(shí)排行榜數(shù)據(jù)、訪(fǎng)問(wèn)量點(diǎn)贊量統(tǒng)計(jì)、Session共享等等都可以引入Redis來(lái)處理。

深入追問(wèn):
追問(wèn)1:Redis里有哪些數(shù)據(jù)類(lèi)型?
豐富的數(shù)據(jù)類(lèi)型,Redis有8種數(shù)據(jù)類(lèi)型,當(dāng)然常用的主要是 String、Hash、List、Set、 SortSet 這5種類(lèi)型,他們都是基于鍵值的方式組織數(shù)據(jù)。每一種數(shù)據(jù)類(lèi)型提供了非常豐富的操作命令,可以滿(mǎn)足絕大部分需求,如果有特殊需求還能自己通過(guò) lua 腳本自己創(chuàng)建新的命令(具備原子性);

追問(wèn)2:Redis與Memcached有哪些區(qū)別?
兩者都是非關(guān)系型內(nèi)存鍵值數(shù)據(jù)庫(kù),現(xiàn)在公司一般都是用 Redis 來(lái)實(shí)現(xiàn)緩存,為什么不用Memcached呢?
- memcached所有的值均是簡(jiǎn)單的字符串,redis作為其替代者,支持更為豐富的數(shù)據(jù)類(lèi)型
- redis的速度比memcached快很多
- redis可以持久化數(shù)據(jù)到磁盤(pán),這個(gè)很關(guān)鍵,宕機(jī)斷電不再是硬傷。
追問(wèn)3:那Redis怎樣防止異常數(shù)據(jù)不丟失的?如何持久化?
RDB 持久化 (快照)
- 將某個(gè)時(shí)間點(diǎn)的所有數(shù)據(jù)生成快照,存放到硬盤(pán)上。當(dāng)數(shù)據(jù)量很大時(shí),會(huì)很慢。
- 可以將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本。
- 如果系統(tǒng)發(fā)生故障,將會(huì)丟失最后一次創(chuàng)建快照之后的數(shù)據(jù)。
AOF 持久化(即時(shí)更新)
- 將寫(xiě)命令添加到 AOF 文件(Append Only File)的末尾。
- 使用 AOF 持久化需要設(shè)置同步選項(xiàng),從而確保寫(xiě)命令同步到磁盤(pán)文件上的時(shí)機(jī)。這是因?yàn)閷?duì)文件進(jìn)行寫(xiě)入并不會(huì)馬上將內(nèi)容同步到磁盤(pán)上,而是先存儲(chǔ)到緩沖區(qū),然后由操作系統(tǒng)決定什么時(shí)候同步到磁盤(pán)。
有以下同步選項(xiàng)(同步頻率): always 每個(gè)寫(xiě)命令都同步;everysec 每秒同步一次;no 讓操作系統(tǒng)來(lái)決定何時(shí)同步。
everysec 選項(xiàng)比較合適,可以保證系統(tǒng)崩潰時(shí)只會(huì)丟失一秒左右的數(shù)據(jù),并且 Redis 每秒執(zhí)行一次同步對(duì)服務(wù)器性能幾乎沒(méi)有任何影響
面試題2:Redis為啥是單線(xiàn)程的?
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)給的解釋?zhuān)ü俜轿臋n鏈接),翻譯后簡(jiǎn)單說(shuō),因?yàn)镽edis的瓶頸不是CPU的運(yùn)行速度,而往往是網(wǎng)絡(luò)帶寬和機(jī)器的內(nèi)存大小。再說(shuō)了,單線(xiàn)程切換開(kāi)銷(xiāo)小,容易實(shí)現(xiàn)。既然單線(xiàn)程容易實(shí)現(xiàn),而且CPU不會(huì)成為瓶頸,那就順理成章地采用單線(xiàn)程的方案,當(dāng)然了,也是為了避免多線(xiàn)程存在的很多坑。對(duì)了,一個(gè)節(jié)點(diǎn)是一個(gè)單線(xiàn)程。
深入追問(wèn):
追問(wèn)1:?jiǎn)尉€(xiàn)程只使用了單核CPU,太浪費(fèi),有什么辦法發(fā)揮多核CPU的性能嘛?
我們可以通過(guò)在單機(jī)開(kāi)多個(gè)Redis 實(shí)例,我們一直在強(qiáng)調(diào)的單線(xiàn)程,只是在處理我們的網(wǎng)絡(luò)請(qǐng)求的時(shí)候只有一個(gè)線(xiàn)程來(lái)處理。實(shí)際上,一個(gè)正式的Redis Server運(yùn)行的時(shí)候肯定是不止一個(gè)線(xiàn)程的,都是集群形式,多少多少個(gè)節(jié)點(diǎn),所以實(shí)際環(huán)境中大家不用擔(dān)心這種問(wèn)題。
面試題3:聊一下對(duì)緩存穿透、緩存擊穿、緩存雪崩的理解吧
正經(jīng)回答:
- 緩存穿透:指緩存和數(shù)據(jù)庫(kù)中都沒(méi)有的數(shù)據(jù),導(dǎo)致所有的請(qǐng)求都打到數(shù)據(jù)庫(kù)上,然后數(shù)據(jù)庫(kù)還查不到(如null),造成數(shù)據(jù)庫(kù)短時(shí)間線(xiàn)程數(shù)被打滿(mǎn)而導(dǎo)致其他服務(wù)阻塞,最終導(dǎo)致線(xiàn)上服務(wù)不可用,這種情況一般來(lái)自黑客同學(xué)。
- 緩存擊穿:指緩存中沒(méi)有但數(shù)據(jù)庫(kù)中有的數(shù)據(jù)(一般是熱點(diǎn)數(shù)據(jù)緩存時(shí)間到期),這時(shí)由于并發(fā)用戶(hù)特別多,同時(shí)讀緩存沒(méi)讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫(kù)去查,引起數(shù)據(jù)庫(kù)壓力瞬間增大,線(xiàn)上系統(tǒng)卡住。
- 緩存雪崩:指緩存同一時(shí)間大面積的失效,緩存擊穿升級(jí)版。
深入追問(wèn):
追問(wèn)1:那你說(shuō)一下針對(duì)緩存擊穿的解決方法?
[圖片上傳失敗...(image-331f61-1626851066095)]
- 根據(jù)實(shí)際業(yè)務(wù)情況,在Redis中維護(hù)一個(gè)熱點(diǎn)數(shù)據(jù)表,批量設(shè)為永不過(guò)期(如top1000),并定時(shí)更新top1000數(shù)據(jù)。
- 加互斥鎖(mutex key)
互斥鎖
??緩存擊穿后,多個(gè)線(xiàn)程會(huì)同時(shí)去查詢(xún)數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢(xún)數(shù)據(jù)的請(qǐng)求上使用一個(gè)互斥鎖來(lái)鎖住它。
??其他的線(xiàn)程走到這一步拿不到鎖就等著,等第一個(gè)線(xiàn)程查詢(xún)到了數(shù)據(jù),然后做緩存。后面的線(xiàn)程進(jìn)來(lái)發(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ù)庫(kù)后寫(xiě)入緩存");
// 從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù)
result = getDataFromDB();
// 將查詢(xún)到的數(shù)據(jù)寫(xiě)入緩存
setDataToCache(result);
} finally {
reenLock.unlock();// 釋放鎖
}
} else {
result = getDataFromCache();// 先查一下緩存
if (result.isEmpty()) {
System.out.println("我沒(méi)拿到鎖,緩存也沒(méi)數(shù)據(jù),先小憩一下");
Thread.sleep(100);// 小憩一會(huì)兒
return getData04();// 重試
}
}
}
return result;
}
小結(jié)
今天我們復(fù)習(xí)了面試中??嫉腞edis三個(gè)問(wèn)題,你做到心中有數(shù)了么?對(duì)了,如果你的朋友也在準(zhǔn)備面試,請(qǐng)將這個(gè)系列扔給他,如果他認(rèn)真對(duì)待,肯定會(huì)感謝你的!!