JAVA知識十連問

發布時(shí)間:2022-03-29 23:09:36 作者:King 來(lái)源:本站 浏覽量(3579) 點贊(145)
摘要:Redis的(de)key和(hé)value可(kě)以存儲的(de)最大(dà)值分(fēn)别是多(duō)少?怎麽利用(yòng)Redis實現數據的(de)去重?Redis什(shén)麽時(shí)候需要序列化(huà)?Redis序列化(huà)的(de)方式有哪些?MySQL的(de)B+樹的(de)高(gāo)度怎麽計算(suàn)?線程池的(de)狀态有哪些?獲取多(duō)線程并發執行結果的(de)方式有哪些?線程池原理(lǐ)?各個(gè)參數的(de)作用(yòng)。ThreadLocal的(de)使用(yòng)場(chǎng)景有哪些?原理(lǐ)?内存洩漏?kafka是如何保證消息的(de)
  • Redis的(de)key和(hé)value可(kě)以存儲的(de)最大(dà)值分(fēn)别是多(duō)少?

  • 怎麽利用(yòng)Redis實現數據的(de)去重?

  • Redis什(shén)麽時(shí)候需要序列化(huà)?Redis序列化(huà)的(de)方式有哪些?

  • MySQL的(de)B+樹的(de)高(gāo)度怎麽計算(suàn)?

  • 線程池的(de)狀态有哪些?獲取多(duō)線程并發執行結果的(de)方式有哪些?

  • 線程池原理(lǐ)?各個(gè)參數的(de)作用(yòng)。

  • ThreadLocal的(de)使用(yòng)場(chǎng)景有哪些?原理(lǐ)?内存洩漏?

  • kafka是如何保證消息的(de)有序性?

  • Nacos的(de)選舉機制了(le)解嘛?說下(xià)Raft算(suàn)法?

  • 聊一聊TCC補償機制

  • 公衆号:撿田螺的(de)小男(nán)孩

1、Redis的(de)key和(hé)value可(kě)以存儲的(de)最大(dà)值分(fēn)别是多(duō)少?

  • 雖然Key的(de)大(dà)小上限爲512M,但是一般建議(yì)key的(de)大(dà)小不要超過1KB,這(zhè)樣既可(kě)以節約存儲空間,又有利于Redis進行檢索。

  • value的(de)最大(dà)值也(yě)是512M。對(duì)于String類型的(de)value值上限爲512M,而集合、鏈表、哈希等key類型,單個(gè)元素的(de)value上限也(yě)爲512M

2. 怎麽利用(yòng)Redis實現數據的(de)去重?

  • Redis的(de)set:它可(kě)以去除重複元素,也(yě)可(kě)以快(kuài)速判斷某一個(gè)元素是否存在于集合中,如果元素很多(duō)(比如上億的(de)計數),消占用(yòng)内存很大(dà)

  • Redis的(de)bit:它可(kě)以用(yòng)來(lái)實現比set内存高(gāo)度壓縮的(de)計數,它通(tōng)過一個(gè)bit設置爲1或者0,表示存儲某個(gè)元素是否存在信息。例如網站唯一訪客計數,可(kě)以把user_id作爲 bit 的(de)偏移量 offset,如設置爲1表示有訪問,使用(yòng)1 MB的(de)空間就可(kě)以存放800多(duō)萬用(yòng)戶的(de)一天訪問計數情況。

  • HyperLogLog:實現超大(dà)數據量精确的(de)唯一計數都是比較困難的(de),HyperLogLog可(kě)以僅僅使用(yòng) 12 k左右的(de)内存,實現上億的(de)唯一計數,而且誤差控制在百分(fēn)之一左右。

  • bloomfilter布隆過濾器:布隆過濾器是一種占用(yòng)空間很小的(de)數據結構,它由一個(gè)很長(cháng)的(de)二進制向量和(hé)一組Hash映射函數組成,它用(yòng)于檢索一個(gè)元素是否在一個(gè)集合中

對(duì)于布隆過濾器,大(dà)家有興趣可(kě)以看我這(zhè)篇文章(zhāng)哈,面試必備:布隆過濾器是什(shén)麽?有什(shén)麽用(yòng)?

3. Redis什(shén)麽時(shí)候需要序列化(huà)?Redis序列化(huà)的(de)方式有哪些?

大(dà)家先回憶下(xià)Java序列化(huà),什(shén)麽時(shí)候需要序列化(huà)?

  • 序列化(huà):将 Java 對(duì)象轉換成字節流的(de)過程。

  • 反序列化(huà):将字節流轉換成 Java 對(duì)象的(de)過程。

JAVA知識十連問

爲什(shén)麽需要序列化(huà)呢(ne)?

打個(gè)比喻:作爲大(dà)城(chéng)市漂泊的(de)碼農,搬家是常态。當我們搬書(shū)桌時(shí),桌子太大(dà)了(le)就通(tōng)不過比較小的(de)門,因此我們需要把它拆開再搬過去,這(zhè)個(gè)拆桌子的(de)過程就是序列化(huà)。而我們把書(shū)桌複原回來(lái)(安裝)的(de)過程就是反序列化(huà)啦。

  • 比如想把内存中的(de)對(duì)象狀态保存到一個(gè)文件中或者數據庫中的(de)時(shí)候(最常用(yòng),如保存到redis);

  • 再比喻想用(yòng)套接字在網絡上傳送對(duì)象的(de)時(shí)候,都需要序列化(huà)。

RedisSerializer接口 是 Redis 序列化(huà)接口,用(yòng)于 Redis KEY 和(hé) VALUE 的(de)序列化(huà)

  • JDK 序列化(huà)方式 (默認)

  • String 序列化(huà)方式

  • JSON 序列化(huà)方式

  • XML 序列化(huà)方式

4. MySQL的(de)B+樹的(de)高(gāo)度怎麽計算(suàn)?(比如有100w的(de)數據,字段爲int類型)

InnoDB存儲引擎最小儲存單元是頁,一頁大(dà)小就是16k。

B+樹葉子存的(de)是數據,内部節點存的(de)是鍵值+指針。索引組織表通(tōng)過非葉子節點的(de)二分(fēn)查找法以及指針确定數據在哪個(gè)頁中,進而再去數據頁中找到需要的(de)數據;

JAVA知識十連問

假設B+樹的(de)高(gāo)度爲2的(de)話(huà),即有一個(gè)根結點和(hé)若幹個(gè)葉子結點。這(zhè)棵B+樹的(de)存放總記錄數爲=根結點指針數*單個(gè)葉子節點記錄行數。

  • 如果一行記錄的(de)數據大(dà)小爲1k,那麽單個(gè)葉子節點可(kě)以存的(de)記錄數 =16k/1k =16.

  • 非葉子節點内存放多(duō)少指針呢(ne)?我們假設主鍵ID爲bigint類型,長(cháng)度爲8字節(面試官問你int類型,一個(gè)int就是32位,4字節),而指針大(dà)小在InnoDB源碼中設置爲6字節,所以就是8+6=14字節,16k/14B =16*1024B/14B = 1170

因此,一棵高(gāo)度爲2的(de)B+樹,能存放1170 * 16=18720條這(zhè)樣的(de)數據記錄。同理(lǐ)一棵高(gāo)度爲3的(de)B+樹,能存放1170 *1170 *16 =21902400,也(yě)就是說,可(kě)以存放兩千萬左右的(de)記錄。B+樹高(gāo)度一般爲1-3層,已經滿足千萬級别的(de)數據存儲。

5、線程池的(de)狀态有哪些?獲取多(duō)線程并發執行結果的(de)方式有哪些?

線程池和(hé)線程的(de)狀态是不一樣的(de)哈,線程池有這(zhè)幾個(gè)狀态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED

//線程池狀态 private static final int RUNNING    = -1 << COUNT_BITS; 
private static final int SHUTDOWN   =  0 << COUNT_BITS; 
private static final int STOP       =  1 << COUNT_BITS; 
private static final int TIDYING    =  2 << COUNT_BITS; private static final int TERMINATED =  3 << COUNT_BITS; 複制代碼


線程池各個(gè)狀态切換狀态圖如下(xià):

JAVA知識十連問

RUNNING

  • 該狀态的(de)線程池會接收新任務,并處理(lǐ)阻塞隊列中的(de)任務;

  • 調用(yòng)線程池的(de)shutdown()方法,可(kě)以切換到SHUTDOWN狀态;

  • 調用(yòng)線程池的(de)shutdownNow()方法,可(kě)以切換到STOP狀态;

SHUTDOWN

  • 該狀态的(de)線程池不會接收新任務,但會處理(lǐ)阻塞隊列中的(de)任務;

  • 隊列爲空,并且線程池中執行的(de)任務也(yě)爲空,進入TIDYING狀态;

STOP

  • 該狀态的(de)線程不會接收新任務,也(yě)不會處理(lǐ)阻塞隊列中的(de)任務,而且會中斷正在運行的(de)任務;

  • 線程池中執行的(de)任務爲空,進入TIDYING狀态;

TIDYING

  • 該狀态表明(míng)所有的(de)任務已經運行終止,記錄的(de)任務數量爲0。

  • terminated()執行完畢,進入TERMINATED狀态

TERMINATED

  • 該狀态表示線程池徹底終止

6. 線程池原理(lǐ)?各個(gè)參數的(de)作用(yòng)。

ThreadPoolExecutor的(de)構造函數:

public ThreadPoolExecutor(
    int corePoolSize, 
    int maximumPoolSize,
    long keepAliveTime,TimeUnit unit,    
    BlockingQueue<Runnable> workQueue,    
    ThreadFactory threadFactory,    
    RejectedExecutionHandler handler
)  複制代碼


幾個(gè)核心參數的(de)作用(yòng):

  • corePoolSize: 線程池核心線程數最大(dà)值

  • maximumPoolSize: 線程池最大(dà)線程數大(dà)小

  • keepAliveTime: 線程池中非核心線程空閑的(de)存活時(shí)間大(dà)小

  • unit: 線程空閑存活時(shí)間單位

  • workQueue: 存放任務的(de)阻塞隊列

  • threadFactory: 用(yòng)于設置創建線程的(de)工廠,可(kě)以給創建的(de)線程設置有意義的(de)名字,可(kě)方便排查問題。

  • handler:  線城(chéng)池的(de)飽和(hé)策略事件,主要有四種類型。

四種飽和(hé)拒絕策略

  • AbortPolicy(抛出一個(gè)異常,默認的(de))

  • DiscardPolicy(直接丢棄任務)

  • DiscardOldestPolicy(丢棄隊列裏最老的(de)任務,将當前這(zhè)個(gè)任務繼續提交給線程池)

  • CallerRunsPolicy(交給線程池調用(yòng)所在的(de)線程進行處理(lǐ))

線程池原理(lǐ):

JAVA知識十連問

  • 提交一個(gè)任務,線程池裏存活的(de)核心線程數小于線程數corePoolSize時(shí),線程池會創建一個(gè)核心線程去處理(lǐ)提交的(de)任務。

  • 如果線程池核心線程數已滿,即線程數已經等于corePoolSize,一個(gè)新提交的(de)任務,會被放進任務隊列workQueue排隊等待執行。

  • 當線程池裏面存活的(de)線程數已經等于corePoolSize了(le),并且任務隊列workQueue也(yě)滿,判斷線程數是否達到maximumPoolSize,即最大(dà)線程數是否已滿,如果沒到達,創建一個(gè)非核心線程執行提交的(de)任務。

  • 如果當前的(de)線程數達到了(le)maximumPoolSize,還(hái)有新的(de)任務過來(lái)的(de)話(huà),直接采用(yòng)拒絕策略處理(lǐ)。

爲了(le)形象描述線程池執行,我打個(gè)比喻:

  • 核心線程比作公司正式員(yuán)工

  • 非核心線程比作外包員(yuán)工

  • 阻塞隊列比作需求池

  • 提交任務比作提需求

JAVA知識十連問

  • 當産品提個(gè)需求,正式員(yuán)工(核心線程)先接需求(執行任務)

  • 如果正式員(yuán)工都有需求在做(zuò),即核心線程數已滿),産品就把需求先放需求池(阻塞隊列)。

  • 如果需求池(阻塞隊列)也(yě)滿了(le),但是這(zhè)時(shí)候産品繼續提需求,怎麽辦呢(ne)?那就請外包(非核心線程)來(lái)做(zuò)。

  • 如果所有員(yuán)工(最大(dà)線程數也(yě)滿了(le))都有需求在做(zuò)了(le),那就執行拒絕策略。

  • 如果外包員(yuán)工把需求做(zuò)完了(le),它經過一段(keepAliveTime)空閑時(shí)間,就離開公司了(le)。

7. ThreadLocal的(de)使用(yòng)場(chǎng)景有哪些?原理(lǐ)?内存洩漏?

ThreadLocal,即線程本地變量。如果你創建了(le)一個(gè)ThreadLocal變量,那麽訪問這(zhè)個(gè)變量的(de)每個(gè)線程都會有這(zhè)個(gè)變量的(de)一個(gè)本地拷貝,多(duō)個(gè)線程操作這(zhè)個(gè)變量的(de)時(shí)候,實際是操作自己本地内存裏面的(de)變量,從而起到線程隔離的(de)作用(yòng),避免了(le)線程安全問題。

ThreadLocal的(de)應用(yòng)場(chǎng)景

  • 數據庫連接池

  • 會話(huà)管理(lǐ)中使用(yòng)

ThreadLocal内存結構圖:

JAVA知識十連問

ThreadLocal原理(lǐ)

  • Thread對(duì)象中持有一個(gè)ThreadLocal.ThreadLocalMap的(de)成員(yuán)變量。

  • ThreadLocalMap内部維護了(le)Entry數組,每個(gè)Entry代表一個(gè)完整的(de)對(duì)象,key是ThreadLocal本身,value是ThreadLocal的(de)泛型值。

  • 每個(gè)線程在往ThreadLocal裏設置值的(de)時(shí)候,都是往自己的(de)ThreadLocalMap裏存,讀也(yě)是以某個(gè)ThreadLocal作爲引用(yòng),在自己的(de)map裏找對(duì)應的(de)key,從而實現了(le)線程隔離。

ThreadLocal 内存洩露問題

先看看一下(xià)的(de)TreadLocal的(de)引用(yòng)示意圖哈,

JAVA知識十連問

ThreadLocalMap中使用(yòng)的(de) key 爲 ThreadLocal 的(de)弱引用(yòng),如下(xià)

JAVA知識十連問

弱引用(yòng):隻要垃圾回收機制一運行,不管JVM的(de)内存空間是否充足,都會回收該對(duì)象占用(yòng)的(de)内存。

弱引用(yòng)比較容易被回收。因此,如果ThreadLocal(ThreadLocalMap的(de)Key)被垃圾回收器回收了(le),但是因爲ThreadLocalMap生命周期和(hé)Thread是一樣的(de),它這(zhè)時(shí)候如果不被回收,就會出現這(zhè)種情況:ThreadLocalMap的(de)key沒了(le),value還(hái)在,這(zhè)就會造成了(le)内存洩漏問題

如何解決内存洩漏問題?使用(yòng)完ThreadLocal後,及時(shí)調用(yòng)remove()方法釋放内存空間。

8、kafka是如何保證消息的(de)有序性?

kafka這(zhè)樣保證消息有序性的(de):

  • 一個(gè) topic,一個(gè) partition,一個(gè) consumer,内部單線程消費,單線程吞吐量太低,一般不會用(yòng)這(zhè)個(gè)。(全局有序性

  • 寫 N 個(gè)内存 queue,具有相同 key 的(de)數據都到同一個(gè)内存 queue;然後對(duì)于 N 個(gè)線程,每個(gè)線程分(fēn)别消費一個(gè)内存 queue 即可(kě),這(zhè)樣就能保證順序性。

大(dà)家可(kě)以看下(xià)消息隊列的(de)有序性是怎麽推導的(de)哈:

消息的(de)有序性,就是指可(kě)以按照(zhào)消息的(de)發送順序來(lái)消費。有些業務對(duì)消息的(de)順序是有要求的(de),比如先下(xià)單再付款,最後再完成訂單,這(zhè)樣等。假設生産者先後産生了(le)兩條消息,分(fēn)别是下(xià)單消息(M1),付款消息(M2),M1比M2先産生,如何保證M1比M2先被消費呢(ne)。

JAVA知識十連問

爲了(le)保證消息的(de)順序性,可(kě)以将将M1、M2發送到同一個(gè)Server上,當M1發送完收到ack後,M2再發送。如圖:

JAVA知識十連問

這(zhè)樣還(hái)是可(kě)能會有問題,因爲從MQ服務器到服務端,可(kě)能存在網絡延遲,雖然M1先發送,但是它比M2晚到

JAVA知識十連問

那還(hái)能怎麽辦才能保證消息的(de)順序性呢(ne)?将M1和(hé)M2發往同一個(gè)消費者,且發送M1後,等到消費端ACK成功後,才發送M2就得(de)了(le)。

JAVA知識十連問

消息隊列保證順序性整體思路就是這(zhè)樣啦。比如Kafka的(de)全局有序消息,就是這(zhè)種思想的(de)體現: 就是生産者發消息時(shí),1個(gè)Topic隻能對(duì)應1個(gè)Partition,一個(gè) Consumer,内部單線程消費。

但是這(zhè)樣吞吐量太低,一般保證消息局部有序即可(kě)。在發消息的(de)時(shí)候指定Partition Key,Kafka對(duì)其進行Hash計算(suàn),根據計算(suàn)結果決定放入哪個(gè)Partition。這(zhè)樣Partition Key相同的(de)消息會放在同一個(gè)Partition。然後多(duō)消費者單線程消費指定的(de)Partition。

9、Nacos的(de)選舉機制了(le)解嘛?說下(xià)Raft算(suàn)法?

Nacos作爲配置中心的(de)功能是基于Raft算(suàn)法來(lái)實現的(de)。

Raft 算(suàn)法是分(fēn)布式系統開發首選的(de)共識算(suàn)法,它通(tōng)過“一切以領導者爲準”的(de)方式,實現一系列值的(de)共識和(hé)各節點日志的(de)一緻。

Raft選舉規程 涉及三種角色和(hé)任期(Term),

  • Follower:默默地接收和(hé)處理(lǐ)來(lái)自Leader的(de)消息,當等待Leader心跳信息超時(shí)的(de)時(shí)候,就主動站出來(lái),推薦自己當Candidate。

  • Candidate:向其他(tā)節點發送投票(piào)請求,通(tōng)知其他(tā)節點來(lái)投票(piào),如果赢得(de)了(le)大(dà)多(duō)數(N/2+1)選票(piào),就晉升Leader。

  • Leader:負責處理(lǐ)客戶端請求,進行日志複制等操作,每一輪選舉的(de)目标就是選出一個(gè)領導者;領導者會不斷地發送心跳信息,通(tōng)知其他(tā)節點“我是領導者,我還(hái)活著(zhe),你們不要發起新的(de)選舉,不用(yòng)找個(gè)新領導者來(lái)替代我。”

  • Term:這(zhè)跟民主社會的(de)選舉很像,每一屆新的(de)履職期稱之爲一屆任期 

領導選舉過程

  1. 在初始時(shí),集群中所有的(de)節點都是Follower狀态,都被設定一個(gè)随機選舉超時(shí)時(shí)間(一般150ms-300ms):

JAVA知識十連問

  1. 如果Follower在規定的(de)超時(shí)時(shí)間,都沒有收到來(lái)自Leader的(de)心跳,它就發起選舉:将自己的(de)狀态切爲 Candidate,增加自己的(de)任期編号,然後向集群中的(de)其它Follower節點發送請求,詢問其是否選舉自己成爲Leader:

JAVA知識十連問

  1. 其他(tā)節點收到候選人(rén)A的(de)請求投票(piào)消息後,如果在編号爲1的(de)這(zhè)屆任期内還(hái)沒有進行過投票(piào),那麽它将把選票(piào)投給節點A,并增加自己的(de)任期編号:

JAVA知識十連問

  1. 當收到來(lái)自集群中過半節點的(de)接受投票(piào)後,A節點即成爲本屆任期内 Leader,他(tā)将周期性地發送心跳消息,通(tōng)知其他(tā)節點我是Leader,阻止Follower發起新的(de)選舉:

JAVA知識十連問

10、聊一聊TCC補償機制

TCC是分(fēn)布式事務的(de)一種解決方案。它采用(yòng)了(le)補償機制,其核心思想是:針對(duì)每個(gè)操作,都要注冊一個(gè)與其對(duì)應的(de)确認和(hé)補償(撤銷)操作。TCC(Try-Confirm-Cancel)包括三段流程:

  • try階段:嘗試去執行,完成所有業務的(de)一緻性檢查,預留必須的(de)業務資源。

  • Confirm階段:該階段對(duì)業務進行确認提交,不做(zuò)任何檢查,因爲try階段已經檢查過了(le),默認Confirm階段是不會出錯的(de)。

  • Cancel 階段:若業務執行失敗,則進入該階段,它會釋放try階段占用(yòng)的(de)所有業務資源,并回滾Confirm階段執行的(de)所有操作。

下(xià)面再拿用(yòng)戶下(xià)單購(gòu)買禮物(wù)作爲例子來(lái)模拟TCC實現分(fēn)布式事務的(de)過程:

假設用(yòng)戶A餘額爲100金币,擁有的(de)禮物(wù)爲5朵。A花了(le)10個(gè)金币,下(xià)訂單,購(gòu)買10朵玫瑰。餘額、訂單、禮物(wù)都在不同數據庫。

TCC的(de)Try階段:

  • 生成一條訂單記錄,訂單狀态爲待确認。

  • 将用(yòng)戶A的(de)賬戶金币中餘額更新爲90,凍結金币爲10(預留業務資源)

  • 将用(yòng)戶的(de)禮物(wù)數量爲5,預增加數量爲10。

  • Try成功之後,便進入Confirm階段

  • Try過程發生任何異常,均進入Cancel階段

JAVA知識十連問

TCC的(de)Confirm階段:

  • 訂單狀态更新爲已支付

  • 更新用(yòng)戶餘額爲90,可(kě)凍結爲0

  • 用(yòng)戶禮物(wù)數量更新爲15,預增加爲0

  • Confirm過程發生任何異常,均進入Cancel階段

  • Confirm過程執行成功,則該事務結束

JAVA知識十連問

TCC的(de)Cancel階段:

  • 修改訂單狀态爲已取消

  • 更新用(yòng)戶餘額回100

  • 更新用(yòng)戶禮物(wù)數量爲5

JAVA知識十連問

  • TCC的(de)優點是可(kě)以自定義數據庫操作的(de)粒度,降低了(le)鎖沖突,可(kě)以提升性能

  • TCC的(de)缺點是應用(yòng)侵入性強,需要根據網絡、系統故障等不同失敗原因實現不同的(de)回滾策略,實現難度大(dà),一般借助TCC開源框架,ByteTCC,TCC-transaction,Himly。



微信

掃一掃,關注我們

感興趣嗎?

歡迎聯系我們,我們願意爲您解答(dá)任何有關網站疑難問題!

【如有開發需求】那就聯系我們吧

搜索千萬次不如咨詢1次

承接:網站建設,手機網站,響應式網站,小程序開發,原生android開發等業務

立即咨詢 16605125102