圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
這個(gè)月看到不少同學(xué)在面 OPPO 公司的秋招,也有不少同學(xué)好奇 OPPO 面試會(huì)問(wèn)什么?
OPPO 主營(yíng)業(yè)務(wù)是手機(jī),雖然公司業(yè)務(wù)偏向手機(jī)硬件業(yè)務(wù)偏多,但是也還是會(huì)有一些后端開(kāi)發(fā)的崗位,面試風(fēng)格也是八股+項(xiàng)目+算法,跟互聯(lián)網(wǎng)沒(méi)太大的區(qū)別。
OPPO 校招薪資還是很不錯(cuò)的,跟互聯(lián)網(wǎng)公司基本差不多,去年 OPPO 校招薪資,普通檔的是 22k*15,整體上也接近 35w 年薪了。
這次,就來(lái)分享一位同學(xué)的 OPPO 校招后端開(kāi)發(fā)的面經(jīng),考察了 Java 并發(fā)+MySQL+Redis+Kafka 這幾個(gè)方面的知識(shí),整體面試總共面了 40 分鐘。
同學(xué)反饋面試體驗(yàn)很不錯(cuò),面試官人還是不錯(cuò),會(huì)給時(shí)間思考,也有在引導(dǎo)回答,很細(xì)自己準(zhǔn)備不充分,只能回答淺層的問(wèn)題,深問(wèn)一點(diǎn)就不會(huì)了。
大家覺(jué)得難度如何呢?
Java
Java中的線程狀態(tài)有哪些?
源自《Java并發(fā)編程藝術(shù)》 java.lang.Thread.State枚舉類中定義了六種線程的狀態(tài),可以調(diào)用線程Thread中的getState()方法獲取當(dāng)前線程的狀態(tài)。
線程狀態(tài) | 解釋 |
---|---|
NEW | 尚未啟動(dòng)的線程狀態(tài),即線程創(chuàng)建,還未調(diào)用start方法 |
RUNNABLE | 就緒狀態(tài)(調(diào)用start,等待調(diào)度)+正在運(yùn)行 |
BLOCKED | 等待監(jiān)視器鎖時(shí),陷入阻塞狀態(tài) |
WAITING | 等待狀態(tài)的線程正在等待另一線程執(zhí)行特定的操作(如notify) |
TIMED_WAITING | 具有指定等待時(shí)間的等待狀態(tài) |
TERMINATED | 線程完成執(zhí)行,終止?fàn)顟B(tài) |
線程池的參數(shù)有哪些?
線程池的構(gòu)造函數(shù)有7個(gè)參數(shù):
corePoolSize:線程池核心線程數(shù)量。默認(rèn)情況下,線程池中線程的數(shù)量如果 <= corePoolSize,那么即使這些線程處于空閑狀態(tài),那也不會(huì)被銷毀。
maximumPoolSize:線程池中最多可容納的線程數(shù)量。當(dāng)一個(gè)新任務(wù)交給線程池,如果此時(shí)線程池中有空閑的線程,就會(huì)直接執(zhí)行,如果沒(méi)有空閑的線程且當(dāng)前線程池的線程數(shù)量小于corePoolSize,就會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù),否則就會(huì)將該任務(wù)加入到阻塞隊(duì)列中,如果阻塞隊(duì)列滿了,就會(huì)創(chuàng)建一個(gè)新線程,從阻塞隊(duì)列頭部取出一個(gè)任務(wù)來(lái)執(zhí)行,并將新任務(wù)加入到阻塞隊(duì)列末尾。如果當(dāng)前線程池中線程的數(shù)量等于maximumPoolSize,就不會(huì)創(chuàng)建新線程,就會(huì)去執(zhí)行拒絕策略。
keepAliveTime:當(dāng)線程池中線程的數(shù)量大于corePoolSize,并且某個(gè)線程的空閑時(shí)間超過(guò)了keepAliveTime,那么這個(gè)線程就會(huì)被銷毀。
unit:就是keepAliveTime時(shí)間的單位。
workQueue:工作隊(duì)列。當(dāng)沒(méi)有空閑的線程執(zhí)行新任務(wù)時(shí),該任務(wù)就會(huì)被放入工作隊(duì)列中,等待執(zhí)行。
threadFactory:線程工廠??梢杂脕?lái)給線程取名字等等
handler:拒絕策略。當(dāng)一個(gè)新任務(wù)交給線程池,如果此時(shí)線程池中有空閑的線程,就會(huì)直接執(zhí)行,如果沒(méi)有空閑的線程,就會(huì)將該任務(wù)加入到阻塞隊(duì)列中,如果阻塞隊(duì)列滿了,就會(huì)創(chuàng)建一個(gè)新線程,從阻塞隊(duì)列頭部取出一個(gè)任務(wù)來(lái)執(zhí)行,并將新任務(wù)加入到阻塞隊(duì)列末尾。如果當(dāng)前線程池中線程的數(shù)量等于maximumPoolSize,就不會(huì)創(chuàng)建新線程,就會(huì)去執(zhí)行拒絕策略
任務(wù)丟進(jìn)線程池的流程?
線程池是為了減少頻繁的創(chuàng)建線程和銷毀線程帶來(lái)的性能損耗,線程池的工作原理如下圖:
線程池分為核心線程池,線程池的最大容量,還有等待任務(wù)的隊(duì)列,提交一個(gè)任務(wù),如果核心線程沒(méi)有滿,就創(chuàng)建一個(gè)線程,如果滿了,就是會(huì)加入等待隊(duì)列,如果等待隊(duì)列滿了,就會(huì)增加線程,如果達(dá)到最大線程數(shù)量,如果都達(dá)到最大線程數(shù)量,就會(huì)按照一些丟棄的策略進(jìn)行處理。
Java中的synchronized和ReentrantLock的區(qū)別
synchronized 和 ReentrantLock 都是 Java 中提供的可重入鎖:
用法不同:synchronized 可用來(lái)修飾普通方法、靜態(tài)方法和代碼塊,而 ReentrantLock 只能用在代碼塊上。
獲取鎖和釋放鎖方式不同:synchronized 會(huì)自動(dòng)加鎖和釋放鎖,當(dāng)進(jìn)入 synchronized 修飾的代碼塊之后會(huì)自動(dòng)加鎖,當(dāng)離開(kāi) synchronized 的代碼段之后會(huì)自動(dòng)釋放鎖。而 ReentrantLock 需要手動(dòng)加鎖和釋放鎖
鎖類型不同:synchronized 屬于非公平鎖,而 ReentrantLock 既可以是公平鎖也可以是非公平鎖。
響應(yīng)中斷不同:ReentrantLock 可以響應(yīng)中斷,解決死鎖的問(wèn)題,而 synchronized 不能響應(yīng)中斷。
底層實(shí)現(xiàn)不同:synchronized 是 JVM 層面通過(guò)監(jiān)視器實(shí)現(xiàn)的,而 ReentrantLock 是基于 AQS 實(shí)現(xiàn)的
ReentrantLock怎么實(shí)現(xiàn)可重入?
ReentrantLock的實(shí)現(xiàn)基于隊(duì)列同步器(AbstractQueuedSynchronizer,后面簡(jiǎn)稱AQS),ReentrantLock的可重入功能基于AQS的同步狀態(tài):state。
其原理大致為:當(dāng)某一線程獲取鎖后,將state值+1,并記錄下當(dāng)前持有鎖的線程,再有線程來(lái)獲取鎖時(shí),判斷這個(gè)線程與持有鎖的線程是否是同一個(gè)線程,如果是,將state值再+1,如果不是,阻塞線程。
當(dāng)線程釋放鎖時(shí),將state值-1,當(dāng)state值減為0時(shí),表示當(dāng)前線程徹底釋放了鎖,然后將記錄當(dāng)前持有鎖的線程的那個(gè)字段設(shè)置為null,并喚醒其他線程,使其重新競(jìng)爭(zhēng)鎖。
//?acquires的值是1
final?boolean?nonfairTryAcquire(int?acquires)?{
//?獲取當(dāng)前線程
final?Thread?current?=?Thread.currentThread();
//?獲取state的值
int?c?=?getState();
//?如果state的值等于0,表示當(dāng)前沒(méi)有線程持有鎖
//?嘗試將state的值改為1,如果修改成功,則成功獲取鎖,并設(shè)置當(dāng)前線程為持有鎖的線程,返回true
if?(c?==?0)?{
????if?(compareAndSetState(0,?acquires))?{
????????setExclusiveOwnerThread(current);
????????return?true;
????}
}
????//?state的值不等于0,表示已經(jīng)有其他線程持有鎖
????//?判斷當(dāng)前線程是否等于持有鎖的線程,如果等于,將state的值+1,并設(shè)置到state上,獲取鎖成功,返回true
????//?如果不是當(dāng)前線程,獲取鎖失敗,返回false
else?if?(current?==?getExclusiveOwnerThread())?{
????int?nextc?=?c?+?acquires;
????if?(nextc?<?0)?//?overflow
????????throw?new?Error("Maximum?lock?count?exceeded");
????setState(nextc);
????return?true;
}
return?false;
}
AQS底層原理是什么?
AQS全稱為AbstractQueuedSynchronizer,是Java中的一個(gè)抽象類。AQS是一個(gè)用于構(gòu)建鎖、同步器、協(xié)作工具類的工具類(框架)。
AQS核心思想是,如果被請(qǐng)求的共享資源空閑,那么就將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,將共享資源設(shè)置為鎖定狀態(tài);如果共享資源被占用,就需要一定的阻塞等待喚醒機(jī)制來(lái)保證鎖分配。這個(gè)機(jī)制主要用的是CLH隊(duì)列的變體實(shí)現(xiàn)的,將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。
CLH:Craig、Landin and Hagersten隊(duì)列,是單向鏈表,AQS中的隊(duì)列是CLH變體的虛擬雙向隊(duì)列(FIFO),AQS是通過(guò)將每條請(qǐng)求共享資源的線程封裝成一個(gè)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖的分配。
主要原理圖如下:
AQS使用一個(gè)Volatile的int類型的成員變量來(lái)表示同步狀態(tài),通過(guò)內(nèi)置的FIFO隊(duì)列來(lái)完成資源獲取的排隊(duì)工作,通過(guò)CAS完成對(duì)State值的修改。
AQS廣泛用于控制并發(fā)流程的類,如下圖:
其中Sync
是這些類中都有的內(nèi)部類,其結(jié)構(gòu)如下:
可以看到:Sync
是AQS
的實(shí)現(xiàn)。AQS
主要完成的任務(wù):
- 同步狀態(tài)(比如說(shuō)計(jì)數(shù)器)的原子性管理;線程的阻塞和解除阻塞;隊(duì)列的管理。
AQS原理
AQS最核心的就是三大部分:
- 狀態(tài):state;控制線程搶鎖和配合的FIFO隊(duì)列(雙向鏈表);期望協(xié)作工具類去實(shí)現(xiàn)的獲取/釋放等重要方法(重寫(xiě))。
狀態(tài)state
- 這里state的具體含義,會(huì)根據(jù)具體實(shí)現(xiàn)類的不同而不同:比如在Semapore里,他表示剩余許可證的數(shù)量;在CountDownLatch里,它表示還需要倒數(shù)的數(shù)量;在ReentrantLock中,state用來(lái)表示“鎖”的占有情況,包括可重入計(jì)數(shù),當(dāng)state的值為0的時(shí)候,標(biāo)識(shí)該Lock不被任何線程所占有。state是volatile修飾的,并被并發(fā)修改,所以修改state的方法都需要保證線程安全,比如getState、setState以及compareAndSetState操作來(lái)讀取和更新這個(gè)狀態(tài)。這些方法都依賴于unsafe類。
FIFO隊(duì)列
- 這個(gè)隊(duì)列用來(lái)存放“等待的線程,AQS就是“排隊(duì)管理器”,當(dāng)多個(gè)線程爭(zhēng)用同一把鎖時(shí),必須有排隊(duì)機(jī)制將那些沒(méi)能拿到鎖的線程串在一起。當(dāng)鎖釋放時(shí),鎖管理器就會(huì)挑選一個(gè)合適的線程來(lái)占有這個(gè)剛剛釋放的鎖。AQS會(huì)維護(hù)一個(gè)等待的線程隊(duì)列,把線程都放到這個(gè)隊(duì)列里,這個(gè)隊(duì)列是雙向鏈表形式。
實(shí)現(xiàn)獲取/釋放等方法
-
- 這里的獲取和釋放方法,是利用AQS的協(xié)作工具類里最重要的方法,是由協(xié)作類自己去實(shí)現(xiàn)的,并且含義各不相同;獲取方法:獲取操作會(huì)以來(lái)state變量,經(jīng)常會(huì)阻塞(比如獲取不到鎖的時(shí)候)。在Semaphore中,獲取就是acquire方法,作用是獲取一個(gè)許可證;而在CountDownLatch里面,獲取就是await方法,作用是等待,直到倒數(shù)結(jié)束;釋放方法:在Semaphore中,釋放就是release方法,作用是釋放一個(gè)許可證;在CountDownLatch里面,獲取就是countDown方法,作用是將倒數(shù)的數(shù)減一;需要每個(gè)實(shí)現(xiàn)類重寫(xiě)tryAcquire和tryRelease等方法。
MySQL
MySQL聚簇索引和非聚簇索引的區(qū)別?
數(shù)據(jù)存儲(chǔ):在聚簇索引中,數(shù)據(jù)行按照索引鍵值的順序存儲(chǔ),也就是說(shuō),索引的葉子節(jié)點(diǎn)包含了實(shí)際的數(shù)據(jù)行。這意味著索引結(jié)構(gòu)本身就是數(shù)據(jù)的物理存儲(chǔ)結(jié)構(gòu)。非聚簇索引的葉子節(jié)點(diǎn)不包含完整的數(shù)據(jù)行,而是包含指向數(shù)據(jù)行的指針或主鍵值。數(shù)據(jù)行本身存儲(chǔ)在聚簇索引中。
索引與數(shù)據(jù)關(guān)系:由于數(shù)據(jù)與索引緊密相連,當(dāng)通過(guò)聚簇索引查找數(shù)據(jù)時(shí),可以直接從索引中獲得數(shù)據(jù)行,而不需要額外的步驟去查找數(shù)據(jù)所在的位置。當(dāng)通過(guò)非聚簇索引查找數(shù)據(jù)時(shí),首先在非聚簇索引中找到對(duì)應(yīng)的主鍵值,然后通過(guò)這個(gè)主鍵值回溯到聚簇索引中查找實(shí)際的數(shù)據(jù)行,這個(gè)過(guò)程稱為“回表”。
唯一性:聚簇索引通常是基于主鍵構(gòu)建的,因此每個(gè)表只能有一個(gè)聚簇索引,因?yàn)閿?shù)據(jù)只能有一種物理排序方式。一個(gè)表可以有多個(gè)非聚簇索引,因?yàn)樗鼈儾恢苯佑绊憯?shù)據(jù)的物理存儲(chǔ)位置。
效率:對(duì)于范圍查詢和排序查詢,聚簇索引通常更有效率,因?yàn)樗苊饬祟~外的尋址開(kāi)銷。非聚簇索引在使用覆蓋索引進(jìn)行查詢時(shí)效率更高,因?yàn)樗恍枰x取完整的數(shù)據(jù)行。但是需要進(jìn)行回表的操作,使用非聚簇索引效率比較低,因?yàn)樾枰M(jìn)行額外的回表操作。
為什么MySQL索引使用B+樹(shù)?
B 樹(shù)和 B+ 都是通過(guò)多叉樹(shù)的方式,會(huì)將樹(shù)的高度變矮,所以這兩個(gè)數(shù)據(jù)結(jié)構(gòu)非常適合檢索存于磁盤(pán)中的數(shù)據(jù)。但是 MySQL 默認(rèn)的存儲(chǔ)引擎 InnoDB 采用的是 B+ 作為索引的數(shù)據(jù)結(jié)構(gòu),原因有:
-
- B+ 樹(shù)的非葉子節(jié)點(diǎn)不存放實(shí)際的記錄數(shù)據(jù),僅存放索引,因此數(shù)據(jù)量相同的情況下,相比存儲(chǔ)即存索引又存記錄的 B 樹(shù),B+樹(shù)的非葉子節(jié)點(diǎn)可以存放更多的索引,因此 B+ 樹(shù)可以比 B 樹(shù)更「矮胖」,查詢底層節(jié)點(diǎn)的磁盤(pán) I/O次數(shù)會(huì)更少。B+ 樹(shù)有大量的冗余節(jié)點(diǎn)(所有非葉子節(jié)點(diǎn)都是冗余索引),這些冗余索引讓 B+ 樹(shù)在插入、刪除的效率都更高,比如刪除根節(jié)點(diǎn)的時(shí)候,不會(huì)像 B 樹(shù)那樣會(huì)發(fā)生復(fù)雜的樹(shù)的變化;B+ 樹(shù)葉子節(jié)點(diǎn)之間用鏈表連接了起來(lái),有利于范圍查詢,而 B 樹(shù)要實(shí)現(xiàn)范圍查詢,因此只能通過(guò)樹(shù)的遍歷來(lái)完成范圍查詢,這會(huì)涉及多個(gè)節(jié)點(diǎn)的磁盤(pán) I/O 操作,范圍查詢效率不如 B+ 樹(shù)。
Redis
Redis有哪些數(shù)據(jù)結(jié)構(gòu)
Redis 提供了豐富的數(shù)據(jù)類型,常見(jiàn)的有五種數(shù)據(jù)類型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
隨著 Redis 版本的更新,后面又支持了四種數(shù)據(jù)類型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五種數(shù)據(jù)類型的應(yīng)用場(chǎng)景:
- String 類型的應(yīng)用場(chǎng)景:緩存對(duì)象、常規(guī)計(jì)數(shù)、分布式鎖、共享 session 信息等。List 類型的應(yīng)用場(chǎng)景:消息隊(duì)列(但是有兩個(gè)問(wèn)題:1. 生產(chǎn)者需要自行實(shí)現(xiàn)全局唯一 ID;2. 不能以消費(fèi)組形式消費(fèi)數(shù)據(jù))等。Hash 類型:緩存對(duì)象、購(gòu)物車等。Set 類型:聚合計(jì)算(并集、交集、差集)場(chǎng)景,比如點(diǎn)贊、共同關(guān)注、抽獎(jiǎng)活動(dòng)等。Zset 類型:排序場(chǎng)景,比如排行榜、電話和姓名排序等。
Redis 后續(xù)版本又支持四種數(shù)據(jù)類型,它們的應(yīng)用場(chǎng)景如下:
- BitMap(2.2 版新增):二值狀態(tài)統(tǒng)計(jì)的場(chǎng)景,比如簽到、判斷用戶登陸狀態(tài)、連續(xù)簽到用戶總數(shù)等;HyperLogLog(2.8 版新增):海量數(shù)據(jù)基數(shù)統(tǒng)計(jì)的場(chǎng)景,比如百萬(wàn)級(jí)網(wǎng)頁(yè) UV 計(jì)數(shù)等;GEO(3.2 版新增):存儲(chǔ)地理位置信息的場(chǎng)景,比如滴滴叫車;Stream(5.0 版新增):消息隊(duì)列,相比于基于 List 類型實(shí)現(xiàn)的消息隊(duì)列,有這兩個(gè)特有的特性:自動(dòng)生成全局唯一消息ID,支持以消費(fèi)組形式消費(fèi)數(shù)據(jù)。
使用redis實(shí)現(xiàn)布隆過(guò)濾器?講一下布隆過(guò)濾器的原理?
布隆過(guò)濾器由「初始值都為 0 的位圖數(shù)組」和「 N 個(gè)哈希函數(shù)」兩部分組成。當(dāng)我們?cè)趯?xiě)入數(shù)據(jù)庫(kù)數(shù)據(jù)時(shí),在布隆過(guò)濾器里做個(gè)標(biāo)記,這樣下次查詢數(shù)據(jù)是否在數(shù)據(jù)庫(kù)時(shí),只需要查詢布隆過(guò)濾器,如果查詢到數(shù)據(jù)沒(méi)有被標(biāo)記,說(shuō)明不在數(shù)據(jù)庫(kù)中。
布隆過(guò)濾器會(huì)通過(guò) 3 個(gè)操作完成標(biāo)記:
- 第一步,使用 N 個(gè)哈希函數(shù)分別對(duì)數(shù)據(jù)做哈希計(jì)算,得到 N 個(gè)哈希值;第二步,將第一步得到的 N 個(gè)哈希值對(duì)位圖數(shù)組的長(zhǎng)度取模,得到每個(gè)哈希值在位圖數(shù)組的對(duì)應(yīng)位置。第三步,將每個(gè)哈希值在位圖數(shù)組的對(duì)應(yīng)位置的值設(shè)置為 1;
舉個(gè)例子,假設(shè)有一個(gè)位圖數(shù)組長(zhǎng)度為 8,哈希函數(shù) 3 個(gè)的布隆過(guò)濾器。
在數(shù)據(jù)庫(kù)寫(xiě)入數(shù)據(jù) x 后,把數(shù)據(jù) x 標(biāo)記在布隆過(guò)濾器時(shí),數(shù)據(jù) x 會(huì)被 3 個(gè)哈希函數(shù)分別計(jì)算出 3 個(gè)哈希值,然后在對(duì)這 3 個(gè)哈希值對(duì) 8 取模,假設(shè)取模的結(jié)果為 1、4、6,然后把位圖數(shù)組的第 1、4、6 位置的值設(shè)置為 1。當(dāng)應(yīng)用要查詢數(shù)據(jù) x 是否數(shù)據(jù)庫(kù)時(shí),通過(guò)布隆過(guò)濾器只要查到位圖數(shù)組的第 1、4、6 位置的值是否全為 1,只要有一個(gè)為 0,就認(rèn)為數(shù)據(jù) x 不在數(shù)據(jù)庫(kù)中。
布隆過(guò)濾器由于是基于哈希函數(shù)實(shí)現(xiàn)查找的,高效查找的同時(shí)存在哈希沖突的可能性,比如數(shù)據(jù) x 和數(shù)據(jù) y 可能都落在第 1、4、6 位置,而事實(shí)上,可能數(shù)據(jù)庫(kù)中并不存在數(shù)據(jù) y,存在誤判的情況。
所以,查詢布隆過(guò)濾器說(shuō)數(shù)據(jù)存在,并不一定證明數(shù)據(jù)庫(kù)中存在這個(gè)數(shù)據(jù),但是查詢到數(shù)據(jù)不存在,數(shù)據(jù)庫(kù)中一定就不存在這個(gè)數(shù)據(jù)。
Redis的高可用體現(xiàn)在哪里?
Redis 主從架構(gòu)
Redis 多副本,采用主從(replication)部署結(jié)構(gòu),相較于單副本而言最大的特點(diǎn)就是主從實(shí)例間數(shù)據(jù)實(shí)時(shí)同步,并且提供數(shù)據(jù)持久化和備份策略。主從實(shí)例部署在不同的物理服務(wù)器上,根據(jù)公司的基礎(chǔ)環(huán)境配置,可以實(shí)現(xiàn)同時(shí)對(duì)外提供服務(wù)和讀寫(xiě)分離策略。
優(yōu)點(diǎn):讀寫(xiě)分離策略:從節(jié)點(diǎn)可以擴(kuò)展主庫(kù)節(jié)點(diǎn)的讀能力,有效應(yīng)對(duì)大并發(fā)量的讀操作。
缺點(diǎn):故障恢復(fù)復(fù)雜,如果沒(méi)有 RedisHA 系統(tǒng)(需要開(kāi)發(fā)),當(dāng)主庫(kù)節(jié)點(diǎn)出現(xiàn)故障時(shí),需要手動(dòng)將一個(gè)從節(jié)點(diǎn)晉升為主節(jié)點(diǎn),同時(shí)需要通知業(yè)務(wù)方變更配置,并且需要讓其它從庫(kù)節(jié)點(diǎn)去復(fù)制新主庫(kù)節(jié)點(diǎn),整個(gè)過(guò)程需要人為干預(yù),比較繁瑣;
Redis 哨兵機(jī)制
Redis Sentinel 集群是由若干 Sentinel 節(jié)點(diǎn)組成的分布式集群,可以實(shí)現(xiàn)故障發(fā)現(xiàn)、故障自動(dòng)轉(zhuǎn)移、配置中心和客戶端通知。Redis Sentinel 的節(jié)點(diǎn)數(shù)量要滿足 2n+1(n>=1)的奇數(shù)個(gè)。
優(yōu)點(diǎn):
- Redis Sentinel 集群,能夠解決 Redis 主從模式下的高可用切換問(wèn)題;
Redis Cluster
Redis Cluster 是社區(qū)版推出的 Redis 分布式集群解決方案,主要解決 Redis 分布式方面的需求,比如,當(dāng)遇到單機(jī)內(nèi)存,并發(fā)和流量等瓶頸的時(shí)候,Redis Cluster 能起到很好的負(fù)載均衡的目的。Redis Cluster 集群節(jié)點(diǎn)最小配置 6 個(gè)節(jié)點(diǎn)以上(3 主 3 從),其中主節(jié)點(diǎn)提供讀寫(xiě)操作,從節(jié)點(diǎn)作為備用節(jié)點(diǎn),不提供請(qǐng)求,只作為故障轉(zhuǎn)移使用。Redis Cluster 采用虛擬槽分區(qū),所有的鍵根據(jù)哈希函數(shù)映射到 0~16383 個(gè)整數(shù)槽內(nèi),每個(gè)節(jié)點(diǎn)負(fù)責(zé)維護(hù)一部分槽以及槽所印映射的鍵值數(shù)據(jù)。
優(yōu)點(diǎn):
- 無(wú)中心架構(gòu),數(shù)據(jù)按照 slot 存儲(chǔ)分布在多個(gè)節(jié)點(diǎn),節(jié)點(diǎn)間數(shù)據(jù)共享,可動(dòng)態(tài)調(diào)整數(shù)據(jù)分布;可擴(kuò)展性:可線性擴(kuò)展到 1000 多個(gè)節(jié)點(diǎn),節(jié)點(diǎn)可動(dòng)態(tài)添加或刪除;高可用性:部分節(jié)點(diǎn)不可用時(shí),集群仍可用。通過(guò)增加 Slave 做 standby 數(shù)據(jù)副本,能夠?qū)崿F(xiàn)故障自動(dòng) failover,節(jié)點(diǎn)之間通過(guò) gossip 協(xié)議交換狀態(tài)信息,用投票機(jī)制完成 Slave 到 Master 的角色提升;
Redis集群分區(qū)為什么使用散列插槽而不是用一致性哈希?
- 當(dāng)發(fā)生擴(kuò)容時(shí)候,Redis可配置映射表的方式讓哈希槽更靈活,可更方便組織映射到新增server上面的slot數(shù),比一致性hash的算法更靈活方便。在數(shù)據(jù)遷移時(shí),一致性hash 需要重新計(jì)算key在新增節(jié)點(diǎn)的數(shù)據(jù),然后遷移這部分?jǐn)?shù)據(jù),哈希槽則直接將一個(gè)slot對(duì)應(yīng)的數(shù)據(jù)全部遷移,實(shí)現(xiàn)更簡(jiǎn)單??梢造`活的分配槽位,比如性能更好的節(jié)點(diǎn)分配更多槽位,性能相對(duì)較差的節(jié)點(diǎn)可以分配較少的槽位。
場(chǎng)景
redis如何實(shí)現(xiàn)防止超賣,加鎖加的是什么鎖?
使用redis 實(shí)現(xiàn)分布式鎖,同一個(gè)鎖key,同一時(shí)間只能有一個(gè)客戶端拿到鎖,其他客戶端會(huì)陷入無(wú)限的等待來(lái)嘗試獲取那個(gè)鎖,只有獲取到鎖的客戶端才能執(zhí)行下面的業(yè)務(wù)邏輯。
這種方案的缺點(diǎn)是同一個(gè)商品在多用戶同時(shí)下單的情況下,會(huì)基于分布式鎖串行化處理,導(dǎo)致沒(méi)法同時(shí)處理同一個(gè)商品的大量下單的請(qǐng)求。
如果不使用redis鎖,在并發(fā)的情況下,單獨(dú)依靠mysql怎么保證線程安全,防止超賣?
可以使用樂(lè)觀鎖的方案,更新數(shù)據(jù)庫(kù)減庫(kù)存的時(shí)候,進(jìn)行庫(kù)存限制條件
update?goods?set?stock?=?stock?-?1?where goods_id =??and stock >0
消息隊(duì)列
Kafka怎么保證數(shù)據(jù)不丟失?
使用一個(gè)消息隊(duì)列,其實(shí)就分為三大塊:生產(chǎn)者、中間件、消費(fèi)者,所以要保證消息就是保證三個(gè)環(huán)節(jié)都不能丟失數(shù)據(jù)。
消息生產(chǎn)階段:生產(chǎn)者會(huì)不會(huì)丟消息,取決于生產(chǎn)者對(duì)于異常情況的處理是否合理。從消息被生產(chǎn)出來(lái),然后提交給 MQ 的過(guò)程中,只要能正常收到 ( MQ 中間件) 的 ack 確認(rèn)響應(yīng),就表示發(fā)送成功,所以只要處理好返回值和異常,如果返回異常則進(jìn)行消息重發(fā),那么這個(gè)階段是不會(huì)出現(xiàn)消息丟失的。
消息存儲(chǔ)階段:Kafka 在使用時(shí)是部署一個(gè)集群,生產(chǎn)者在發(fā)布消息時(shí),隊(duì)列中間件通常會(huì)寫(xiě)「多個(gè)節(jié)點(diǎn)」,也就是有多個(gè)副本,這樣一來(lái),即便其中一個(gè)節(jié)點(diǎn)掛了,也能保證集群的數(shù)據(jù)不丟失。
消息消費(fèi)階段:消費(fèi)者接收消息+消息處理之后,才回復(fù) ack 的話,那么消息階段的消息不會(huì)丟失。不能收到消息就回 ack,否則可能消息處理中途掛掉了,消息就丟失了。
Kafka為什么一個(gè)分區(qū)只能由消費(fèi)者組的一個(gè)消費(fèi)者消費(fèi)?這樣設(shè)計(jì)的意義是什么?
同一時(shí)刻,一條消息只能被組中的一個(gè)消費(fèi)者實(shí)例消費(fèi)
如果兩個(gè)消費(fèi)者負(fù)責(zé)同一個(gè)分區(qū),那么就意味著兩個(gè)消費(fèi)者同時(shí)讀取分區(qū)的消息,由于消費(fèi)者自己可以控制讀取消息的offset,就有可能C1才讀到2,而C1讀到1,C1還沒(méi)處理完,C2已經(jīng)讀到3了,則會(huì)造成很多浪費(fèi),因?yàn)檫@就相當(dāng)于多線程讀取同一個(gè)消息,會(huì)造成消息處理的重復(fù),且不能保證消息的順序。