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