JAVA知識十連問
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)過程。
爲什(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)數據;
假設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à):
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ǐ):
提交一個(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)工
阻塞隊列比作需求池
提交任務比作提需求
當産品提個(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内存結構圖:
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)示意圖哈,
ThreadLocalMap中使用(yòng)的(de) key 爲 ThreadLocal 的(de)弱引用(yòng),如下(xià)
弱引用(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)。
爲了(le)保證消息的(de)順序性,可(kě)以将将M1、M2發送到同一個(gè)Server上,當M1發送完收到ack後,M2再發送。如圖:
這(zhè)樣還(hái)是可(kě)能會有問題,因爲從MQ服務器到服務端,可(kě)能存在網絡延遲,雖然M1先發送,但是它比M2晚到。
那還(hái)能怎麽辦才能保證消息的(de)順序性呢(ne)?将M1和(hé)M2發往同一個(gè)消費者,且發送M1後,等到消費端ACK成功後,才發送M2就得(de)了(le)。
消息隊列保證順序性整體思路就是這(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)履職期稱之爲一屆任期
領導選舉過程
在初始時(shí),集群中所有的(de)節點都是Follower狀态,都被設定一個(gè)随機選舉超時(shí)時(shí)間(一般150ms-300ms):
如果Follower在規定的(de)超時(shí)時(shí)間,都沒有收到來(lái)自Leader的(de)心跳,它就發起選舉:将自己的(de)狀态切爲 Candidate,增加自己的(de)任期編号,然後向集群中的(de)其它Follower節點發送請求,詢問其是否選舉自己成爲Leader:
其他(tā)節點收到候選人(rén)A的(de)請求投票(piào)消息後,如果在編号爲1的(de)這(zhè)屆任期内還(hái)沒有進行過投票(piào),那麽它将把選票(piào)投給節點A,并增加自己的(de)任期編号:
當收到來(lái)自集群中過半節點的(de)接受投票(piào)後,A節點即成爲本屆任期内 Leader,他(tā)将周期性地發送心跳消息,通(tōng)知其他(tā)節點我是Leader,阻止Follower發起新的(de)選舉:
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階段
TCC的(de)Confirm階段:
訂單狀态更新爲已支付
更新用(yòng)戶餘額爲90,可(kě)凍結爲0
用(yòng)戶禮物(wù)數量更新爲15,預增加爲0
Confirm過程發生任何異常,均進入Cancel階段
Confirm過程執行成功,則該事務結束
TCC的(de)Cancel階段:
修改訂單狀态爲已取消
更新用(yòng)戶餘額回100
更新用(yòng)戶禮物(wù)數量爲5
TCC的(de)優點是可(kě)以自定義數據庫操作的(de)粒度,降低了(le)鎖沖突,可(kě)以提升性能
TCC的(de)缺點是應用(yòng)侵入性強,需要根據網絡、系統故障等不同失敗原因實現不同的(de)回滾策略,實現難度大(dà),一般借助TCC開源框架,ByteTCC,TCC-transaction,Himly。
掃一掃,關注我們
相關新聞
- JAVA開發之hashMap時(shí)間複雜(zá)度分(fēn)析
- 微信小程序開發偶發性獲取手機号失敗解決方案
- 使用(yòng)JAVA開發小程序時(shí),如何防止接口被頻(pín)繁請求
- app開發制作過程中,使用(yòng)JAVA注解方式,實現權限功能開發
- springBoot小程序開發的(de)項目,後台如何優雅的(de)停止進程
- JAVA知識十連問
- JAVA語言小程序開發之hashMap原理(lǐ)詳解
- mysql常見錯誤詳解
- 小程序open-data組件将于2022年2月(yuè)21日24時(shí)起···
- 爲什(shén)麽重寫了(le)equals方法,就必須重寫hashCode