前言
分享了一份面試真題,整理了一下答案給大家。如果有不正確的,歡迎指出哈,一起進步。
- Redis的key和value可以存儲的最大值分別是多少?
- 怎么利用Redis實現(xiàn)數(shù)據(jù)的去重?
- Redis什么時候需要序列化?Redis序列化的方式有哪些?
- MySQL的B+樹的高度怎么計算?
- 線程池的狀態(tài)有哪些?獲取多線程并發(fā)執(zhí)行結(jié)果的方式有哪些?
- 線程池原理?各個參數(shù)的作用。
- ThreadLocal的使用場景有哪些?原理?內(nèi)存泄漏?
- kafka是如何保證消息的有序性?
- Nacos的選舉機制了解嘛?說下Raft算法?
- 聊一聊TCC補償機制
1、Redis的key和value可以存儲的最大值分別是多少?
-
雖然Key的大小上限為
512M
,但是一般建議key的大小不要超過1KB
,這樣既可以節(jié)約存儲空間,又有利于Redis進行檢索。 -
value的最大值也是
512M
。對于String類型的value值上限為512M
,而集合、鏈表、哈希等key類型,單個元素的value上限也為512M
。
2. 怎么利用Redis實現(xiàn)數(shù)據(jù)的去重?
-
Redis的
set
:它可以去除重復元素,也可以快速判斷某一個元素是否存在于集合中,如果元素很多(比如上億的計數(shù)),占用內(nèi)存很大。 -
Redis的
bit
:它可以用來實現(xiàn)比set內(nèi)存高度壓縮的計數(shù),它通過一個bit設(shè)置為1或者0,表示存儲某個元素是否存在信息。例如網(wǎng)站唯一訪客計數(shù),可以把user_id
作為 bit 的偏移量 offset,如設(shè)置為1表示有訪問,使用1 MB的空間就可以存放800多萬用戶的一天訪問計數(shù)情況。 -
HyperLogLog
:實現(xiàn)超大數(shù)據(jù)量精確的唯一計數(shù)都是比較困難的,HyperLogLog
可以僅僅使用 12 k左右的內(nèi)存,實現(xiàn)上億的唯一計數(shù),而且誤差控制在百分之一左右。 -
bloomfilter
布隆過濾器:布隆過濾器是一種占用空間很小的數(shù)據(jù)結(jié)構(gòu),它由一個很長的二進制向量和一組Hash映射函數(shù)組成,它用于檢索一個元素是否在一個集合中
關(guān)于布隆過濾器,關(guān)注數(shù)據(jù)分析與開發(fā)公號,回復布隆過濾器即可獲取。
3. Redis什么時候需要序列化?Redis序列化的方式有哪些?
大家先回憶下Java序列化,什么時候需要序列化?
- 序列化:將 Java 對象轉(zhuǎn)換成字節(jié)流的過程。
- 反序列化:將字節(jié)流轉(zhuǎn)換成 Java 對象的過程。

為什么需要序列化呢?
打個比喻:作為大城市漂泊的碼農(nóng),搬家是常態(tài)。當我們搬書桌時,桌子太大了就通不過比較小的門,因此我們需要把它拆開再搬過去,這個拆桌子的過程就是序列化。而我們把書桌復原回來(安裝)的過程就是反序列化啦。
- 比如想把內(nèi)存中的對象狀態(tài)保存到一個文件中或者數(shù)據(jù)庫中的時候(最常用,如保存到redis);
- 再比喻想用套接字在網(wǎng)絡(luò)上傳送對象的時候,都需要序列化。
RedisSerializer接口 是 Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化
- JDK 序列化方式 (默認)
- String 序列化方式
- JSON 序列化方式
- XML 序列化方式
4. MySQL的B+樹的高度怎么計算?(比如有100w的數(shù)據(jù),字段為int類型)
InnoDB存儲引擎最小儲存單元是頁,一頁大小就是16k。
B+樹葉子存的是數(shù)據(jù),內(nèi)部節(jié)點存的是鍵值+指針。索引組織表通過非葉子節(jié)點的二分查找法以及指針確定數(shù)據(jù)在哪個頁中,進而再去數(shù)據(jù)頁中找到需要的數(shù)據(jù);

假設(shè)B+樹的高度為2的話,即有一個根結(jié)點和若干個葉子結(jié)點。這棵B+樹的存放總記錄數(shù)為=根結(jié)點指針數(shù)*單個葉子節(jié)點記錄行數(shù)。
- 如果一行記錄的數(shù)據(jù)大小為1k,那么單個葉子節(jié)點可以存的記錄數(shù) =16k/1k =16.
- 非葉子節(jié)點內(nèi)存放多少指針呢?我們假設(shè)主鍵ID為bigint類型,長度為8字節(jié)(面試官問你int類型,一個int就是32位,4字節(jié)),而指針大小在InnoDB源碼中設(shè)置為6字節(jié),所以就是8+6=14字節(jié),16k/14B =16*1024B/14B = 1170
因此,一棵高度為2的B+樹,能存放1170 * 16=18720條這樣的數(shù)據(jù)記錄。同理一棵高度為3的B+樹,能存放1170 *1170 *16 =21902400,也就是說,可以存放兩千萬左右的記錄。B+樹高度一般為1-3層,已經(jīng)滿足千萬級別的數(shù)據(jù)存儲。
5、線程池的狀態(tài)有哪些?獲取多線程并發(fā)執(zhí)行結(jié)果的方式有哪些?
線程池和線程的狀態(tài)是不一樣的哈,線程池有這幾個狀態(tài):RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
。
//線程池狀態(tài)
privatestaticfinalintRUNNING=-1<
線程池各個狀態(tài)切換狀態(tài)圖如下:

RUNNING
- 該狀態(tài)的線程池會接收新任務(wù),并處理阻塞隊列中的任務(wù);
-
調(diào)用線程池的
shutdown()
方法,可以切換到SHUTDOWN狀態(tài); -
調(diào)用線程池的
shutdownNow()
方法,可以切換到STOP狀態(tài);
SHUTDOWN
- 該狀態(tài)的線程池不會接收新任務(wù),但會處理阻塞隊列中的任務(wù);
- 隊列為空,并且線程池中執(zhí)行的任務(wù)也為空,進入TIDYING狀態(tài);
STOP
- 該狀態(tài)的線程不會接收新任務(wù),也不會處理阻塞隊列中的任務(wù),而且會中斷正在運行的任務(wù);
- 線程池中執(zhí)行的任務(wù)為空,進入TIDYING狀態(tài);
TIDYING
- 該狀態(tài)表明所有的任務(wù)已經(jīng)運行終止,記錄的任務(wù)數(shù)量為0。
-
terminated()
執(zhí)行完畢,進入TERMINATED狀態(tài)
TERMINATED
- 該狀態(tài)表示線程池徹底終止
6. 線程池原理?各個參數(shù)的作用。
ThreadPoolExecutor的構(gòu)造函數(shù):
publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,
BlockingQueueworkQueue,
ThreadFactorythreadFactory,
RejectedExecutionHandlerhandler)
幾個核心參數(shù)的作用:
- corePoolSize:線程池核心線程數(shù)最大值
- maximumPoolSize:線程池最大線程數(shù)大小
- keepAliveTime:線程池中非核心線程空閑的存活時間大小
- unit:線程空閑存活時間單位
- workQueue:存放任務(wù)的阻塞隊列
- threadFactory:用于設(shè)置創(chuàng)建線程的工廠,可以給創(chuàng)建的線程設(shè)置有意義的名字,可方便排查問題。
- handler:線城池的飽和策略事件,主要有四種類型。
四種飽和拒絕策略
- AbortPolicy(拋出一個異常,默認的)
- DiscardPolicy(直接丟棄任務(wù))
- DiscardOldestPolicy(丟棄隊列里最老的任務(wù),將當前這個任務(wù)繼續(xù)提交給線程池)
- CallerRunsPolicy(交給線程池調(diào)用所在的線程進行處理)
線程池原理:

- 提交一個任務(wù),線程池里存活的核心線程數(shù)小于線程數(shù)corePoolSize時,線程池會創(chuàng)建一個核心線程去處理提交的任務(wù)。
- 如果線程池核心線程數(shù)已滿,即線程數(shù)已經(jīng)等于corePoolSize,一個新提交的任務(wù),會被放進任務(wù)隊列workQueue排隊等待執(zhí)行。
- 當線程池里面存活的線程數(shù)已經(jīng)等于corePoolSize了,并且任務(wù)隊列workQueue也滿,判斷線程數(shù)是否達到maximumPoolSize,即最大線程數(shù)是否已滿,如果沒到達,創(chuàng)建一個非核心線程執(zhí)行提交的任務(wù)。
- 如果當前的線程數(shù)達到了maximumPoolSize,還有新的任務(wù)過來的話,直接采用拒絕策略處理。
為了形象描述線程池執(zhí)行,我打個比喻:
- 核心線程比作公司正式員工
- 非核心線程比作外包員工
- 阻塞隊列比作需求池
- 提交任務(wù)比作提需求

- 當產(chǎn)品提個需求,正式員工(核心線程)先接需求(執(zhí)行任務(wù))
- 如果正式員工都有需求在做,即核心線程數(shù)已滿),產(chǎn)品就把需求先放需求池(阻塞隊列)。
- 如果需求池(阻塞隊列)也滿了,但是這時候產(chǎn)品繼續(xù)提需求,怎么辦呢?那就請外包(非核心線程)來做。
- 如果所有員工(最大線程數(shù)也滿了)都有需求在做了,那就執(zhí)行拒絕策略。
- 如果外包員工把需求做完了,它經(jīng)過一段(keepAliveTime)空閑時間,就離開公司了。
7. ThreadLocal的使用場景有哪些?原理?內(nèi)存泄漏?
ThreadLocal,即線程本地變量。如果你創(chuàng)建了一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個本地拷貝,多個線程操作這個變量的時候,實際是操作自己本地內(nèi)存里面的變量,從而起到線程隔離的作用,避免了線程安全問題。
ThreadLocal的應(yīng)用場景
- 數(shù)據(jù)庫連接池
- 會話管理中使用
ThreadLocal內(nèi)存結(jié)構(gòu)圖:

ThreadLocal原理
- Thread對象中持有一個ThreadLocal.ThreadLocalMap的成員變量。
- ThreadLocalMap內(nèi)部維護了Entry數(shù)組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
- 每個線程在往ThreadLocal里設(shè)置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應(yīng)的key,從而實現(xiàn)了線程隔離。
ThreadLocal 內(nèi)存泄露問題
先看看一下的TreadLocal的引用示意圖哈,

ThreadLocalMap中使用的 key 為 ThreadLocal 的弱引用,如下

弱引用:只要垃圾回收機制一運行,不管JVM的內(nèi)存空間是否充足,都會回收該對象占用的內(nèi)存。
弱引用比較容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因為ThreadLocalMap生命周期和Thread是一樣的,它這時候如果不被回收,就會出現(xiàn)這種情況:ThreadLocalMap的key沒了,value還在,這就會造成了內(nèi)存泄漏問題。
如何解決內(nèi)存泄漏問題?使用完ThreadLocal后,及時調(diào)用remove()方法釋放內(nèi)存空間。
8、kafka是如何保證消息的有序性?
kafka這樣保證消息有序性的:
- 一個 topic,一個 partition,一個 consumer,內(nèi)部單線程消費,單線程吞吐量太低,一般不會用這個。(全局有序性)
- 寫 N 個內(nèi)存 queue,具有相同 key 的數(shù)據(jù)都到同一個內(nèi)存 queue;然后對于 N 個線程,每個線程分別消費一個內(nèi)存 queue 即可,這樣就能保證順序性。
大家可以看下消息隊列的有序性是怎么推導的哈:
消息的有序性,就是指可以按照消息的發(fā)送順序來消費。有些業(yè)務(wù)對消息的順序是有要求的,比如先下單再付款,最后再完成訂單,這樣等。假設(shè)生產(chǎn)者先后產(chǎn)生了兩條消息,分別是下單消息(M1),付款消息(M2),M1比M2先產(chǎn)生,如何保證M1比M2先被消費呢。

為了保證消息的順序性,可以將將M1、M2發(fā)送到同一個Server上,當M1發(fā)送完收到ack后,M2再發(fā)送。如圖:

這樣還是可能會有問題,因為從MQ服務(wù)器到服務(wù)端,可能存在網(wǎng)絡(luò)延遲,雖然M1先發(fā)送,但是它比M2晚到。

那還能怎么辦才能保證消息的順序性呢?將M1和M2發(fā)往同一個消費者,且發(fā)送M1后,等到消費端ACK成功后,才發(fā)送M2就得了。

消息隊列保證順序性整體思路就是這樣啦。比如Kafka的全局有序消息,就是這種思想的體現(xiàn): 就是生產(chǎn)者發(fā)消息時,1個Topic
只能對應(yīng)1個Partition
,一個Consumer
,內(nèi)部單線程消費。
但是這樣吞吐量太低,一般保證消息局部有序即可。在發(fā)消息的時候指定Partition Key
,Kafka對其進行Hash計算,根據(jù)計算結(jié)果決定放入哪個Partition
。這樣Partition Key相同的消息會放在同一個Partition。然后多消費者單線程消費指定的Partition。
9、Nacos的選舉機制了解嘛?說下Raft算法?
Nacos作為配置中心的功能是基于Raft算法來實現(xiàn)的。
Raft 算法是分布式系統(tǒng)開發(fā)首選的共識算法,它通過“一切以領(lǐng)導者為準”的方式,實現(xiàn)一系列值的共識和各節(jié)點日志的一致。
Raft選舉過程涉及三種角色和任期(Term):
- Follower:默默地接收和處理來自Leader的消息,當?shù)却齃eader心跳信息超時的時候,就主動站出來,推薦自己當Candidate。
- Candidate:向其他節(jié)點發(fā)送投票請求,通知其他節(jié)點來投票,如果贏得了大多數(shù)(N/2+1)選票,就晉升Leader。
- Leader:負責處理客戶端請求,進行日志復制等操作,每一輪選舉的目標就是選出一個領(lǐng)導者;領(lǐng)導者會不斷地發(fā)送心跳信息,通知其他節(jié)點“我是領(lǐng)導者,我還活著,你們不要發(fā)起新的選舉,不用找個新領(lǐng)導者來替代我?!?/li>
- Term:這跟民主社會的選舉很像,每一屆新的履職期稱之為一屆任期
領(lǐng)導選舉過程
- 在初始時,集群中所有的節(jié)點都是Follower狀態(tài),都被設(shè)定一個隨機選舉超時時間(一般150ms-300ms):

- 如果Follower在規(guī)定的超時時間,都沒有收到來自Leader的心跳,它就發(fā)起選舉:將自己的狀態(tài)切為 Candidate,增加自己的任期編號,然后向集群中的其它Follower節(jié)點發(fā)送請求,詢問其是否選舉自己成為Leader:

- 其他節(jié)點收到候選人A的請求投票消息后,如果在編號為1的這屆任期內(nèi)還沒有進行過投票,那么它將把選票投給節(jié)點A,并增加自己的任期編號:

- 當收到來自集群中過半節(jié)點的接受投票后,A節(jié)點即成為本屆任期內(nèi) Leader,他將周期性地發(fā)送心跳消息,通知其他節(jié)點我是Leader,阻止Follower發(fā)起新的選舉:

10、聊一聊TCC補償機制
TCC是分布式事務(wù)的一種解決方案。它采用了補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應(yīng)的確認和補償(撤銷)操作。TCC(Try-Confirm-Cancel)包括三段流程:
- try階段:嘗試去執(zhí)行,完成所有業(yè)務(wù)的一致性檢查,預(yù)留必須的業(yè)務(wù)資源。
- Confirm階段:該階段對業(yè)務(wù)進行確認提交,不做任何檢查,因為try階段已經(jīng)檢查過了,默認Confirm階段是不會出錯的。
- Cancel 階段:若業(yè)務(wù)執(zhí)行失敗,則進入該階段,它會釋放try階段占用的所有業(yè)務(wù)資源,并回滾Confirm階段執(zhí)行的所有操作。
下面再拿用戶下單購買禮物作為例子來模擬TCC實現(xiàn)分布式事務(wù)的過程:
假設(shè)用戶A余額為100金幣,擁有的禮物為5朵。A花了10個金幣,下訂單,購買10朵玫瑰。余額、訂單、禮物都在不同數(shù)據(jù)庫。
TCC的Try階段:
- 生成一條訂單記錄,訂單狀態(tài)為待確認。
- 將用戶A的賬戶金幣中余額更新為90,凍結(jié)金幣為10(預(yù)留業(yè)務(wù)資源)
- 將用戶的禮物數(shù)量為5,預(yù)增加數(shù)量為10。
- Try成功之后,便進入Confirm階段
- Try過程發(fā)生任何異常,均進入Cancel階段

TCC的Confirm階段:
- 訂單狀態(tài)更新為已支付
- 更新用戶余額為90,可凍結(jié)為0
- 用戶禮物數(shù)量更新為15,預(yù)增加為0
- Confirm過程發(fā)生任何異常,均進入Cancel階段
- Confirm過程執(zhí)行成功,則該事務(wù)結(jié)束

TCC的Cancel階段:
- 修改訂單狀態(tài)為已取消
- 更新用戶余額回100
- 更新用戶禮物數(shù)量為5

- TCC的優(yōu)點是可以自定義數(shù)據(jù)庫操作的粒度,降低了鎖沖突,可以提升性能
- TCC的缺點是應(yīng)用侵入性強,需要根據(jù)網(wǎng)絡(luò)、系統(tǒng)故障等不同失敗原因?qū)崿F(xiàn)不同的回滾策略,實現(xiàn)難度大,一般借助TCC開源框架,ByteTCC,TCC-transaction等。
審核編輯 :李倩
-
存儲
+關(guān)注
關(guān)注
13文章
4535瀏覽量
87497 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4381瀏覽量
64945 -
MySQL
+關(guān)注
關(guān)注
1文章
861瀏覽量
27962
原文標題:小廠后端十連問(附答案)
文章出處:【微信號:DBDevs,微信公眾號:數(shù)據(jù)分析與開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
【硬件方向】名企面試筆試真題:大疆創(chuàng)新校園招聘筆試題
硬件工程師面試/筆試經(jīng)典 100 題

硬件經(jīng)典面試100題(附參考答案)
開關(guān)電源的基礎(chǔ)知識題目及答案
求一份在STM32F407的CS1239的驅(qū)動程序
求一份evl-32px10的資料
求一份evl-32px10評估板的資料
【面試題】人工智能工程師高頻面試題匯總:機器學習深化篇(題目+答案)

評論