JAVA知(zhī)識十連問
Redis的key和value可以存儲的最大值分别是(shì)多少?
怎麽利用Redis實現(xiàn)數據的去(qù)重?
Redis什麽時候需要序列化?Redis序列化的方式有哪些?
MySQL的B+樹(shù)的高度怎麽計算?
線(xiàn)程池的狀态有哪些?獲取多線(xiàn)程并發執行結果的方式有哪些?
線(xiàn)程池原理?各個參數的作用。
ThreadLocal的使用場景有哪些?原理?内存洩漏?
kafka是(shì)如何保證消息的有序性?
Nacos的選舉機制了解嘛?說下Raft算法?
聊一聊TCC補償機制
公衆号:撿田螺的小男孩
1、Redis的key和value可以存儲的最大值分别是(shì)多少?
雖然Key的大小上限爲
512M
,但(dàn)是(shì)一般建議(yì)key的大小不要超過1KB
,這樣既可以節約存儲空間,又(yòu)有利于Redis進行檢索。value的最大值也是(shì)
512M
。對于String類型的value值上限爲512M
,而集合、鏈表、哈希等key類型,單個元素的value上限也爲512M
。
2. 怎麽利用Redis實現(xiàn)數據的去(qù)重?
Redis的
set
:它可以去(qù)除重複元素,也可以快速判斷某一個元素是(shì)否存在于集合中,如果元素很多(比如上億的計數),消占用内存很大Redis的
bit
:它可以用來實現(xiàn)比set内存高度壓縮的計數,它通過一個bit設置爲1或者0,表示存儲某個元素是(shì)否存在信息。例如網站唯一訪客計數,可以把user_id
作爲 bit 的偏移量 offset,如設置爲1表示有訪問,使用1 MB的空間就可以存放(fàng)800多萬用戶的一天訪問計數情況。HyperLogLog:實現(xiàn)超大數據量精确的唯一計數都是(shì)比較困難的,
HyperLogLog
可以僅僅使用 12 k左右的内存,實現(xiàn)上億的唯一計數,而且誤差控制在百分之一左右。bloomfilter布隆過濾器:布隆過濾器是(shì)一種占用空間很小的數據結構,它由一個很長的二進制向量和一組Hash映射函數組成,它用于檢索一個元素是(shì)否在一個集合中
對于布隆過濾器,大家有興趣可以看我這篇文章哈,面試必備:布隆過濾器是(shì)什麽?有什麽用?
3. Redis什麽時候需要序列化?Redis序列化的方式有哪些?
大家先回憶下Java序列化,什麽時候需要序列化?
序列化:将 Java 對象轉換成字節流的過程。
反序列化:将字節流轉換成 Java 對象的過程。
爲什麽需要序列化呢?
打個比喻:作爲大城市漂泊的碼農,搬家是(shì)常态。當我們搬書(shū)桌時,桌子太大了就通不過比較小的門,因此我們需要把它拆開再搬過去(qù),這個拆桌子的過程就是(shì)序列化。而我們把書(shū)桌複原回來(安裝)的過程就是(shì)反序列化啦。
比如想把内存中的對象狀态保存到一個文件中或者數據庫中的時候(最常用,如保存到redis);
再比喻想用套接字在網絡上傳送對象的時候,都需要序列化。
RedisSerializer接口 是(shì) Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化
JDK 序列化方式 (默認)
String 序列化方式
JSON 序列化方式
XML 序列化方式
4. MySQL的B+樹(shù)的高度怎麽計算?(比如有100w的數據,字段爲int類型)
InnoDB存儲引擎最小儲存單元是(shì)頁,一頁大小就是(shì)16k。
B+樹(shù)葉子存的是(shì)數據,内部節點存的是(shì)鍵值+指針。索引組織表通過非葉子節點的二分查找法以及指針确定數據在哪個頁中,進而再去(qù)數據頁中找到需要的數據;
假設B+樹(shù)的高度爲2的話(huà),即有一個根結點和若幹個葉子結點。這棵B+樹(shù)的存放(fàng)總記錄數爲=根結點指針數*單個葉子節點記錄行數。
如果一行記錄的數據大小爲1k,那麽單個葉子節點可以存的記錄數 =16k/1k =16.
非葉子節點内存放(fàng)多少指針呢?我們假設主鍵ID爲bigint類型,長度爲8字節(面試官問你int類型,一個int就是(shì)32位,4字節),而指針大小在InnoDB源碼中設置爲6字節,所以就是(shì)8+6=14字節,16k/14B =16*1024B/14B = 1170
因此,一棵高度爲2的B+樹(shù),能存放(fàng)1170 * 16=18720條這樣的數據記錄。同理一棵高度爲3的B+樹(shù),能存放(fàng)1170 *1170 *16 =21902400,也就是(shì)說,可以存放(fàng)兩千萬左右的記錄。B+樹(shù)高度一般爲1-3層,已經滿足千萬級别的數據存儲。
5、線(xiàn)程池的狀态有哪些?獲取多線(xiàn)程并發執行結果的方式有哪些?
線(xiàn)程池和線(xiàn)程的狀态是(shì)不一樣的哈,線(xiàn)程池有這幾個狀态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
。
//線(xiàn)程池狀态 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; 複制代碼
線(xiàn)程池各個狀态切換狀态圖如下:
RUNNING
該狀态的線(xiàn)程池會接收新任務,并處理阻塞隊列中的任務;
調用線(xiàn)程池的shutdown()方法,可以切換到SHUTDOWN狀态;
調用線(xiàn)程池的shutdownNow()方法,可以切換到STOP狀态;
SHUTDOWN
該狀态的線(xiàn)程池不會接收新任務,但(dàn)會處理阻塞隊列中的任務;
隊列爲空,并且線(xiàn)程池中執行的任務也爲空,進入TIDYING狀态;
STOP
該狀态的線(xiàn)程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
線(xiàn)程池中執行的任務爲空,進入TIDYING狀态;
TIDYING
該狀态表明所有的任務已經運行終止,記錄的任務數量爲0。
terminated()
執行完畢,進入TERMINATED狀态
TERMINATED
該狀态表示線(xiàn)程池徹底終止
6. 線(xiàn)程池原理?各個參數的作用。
ThreadPoolExecutor的構造函數:
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler ) 複制代碼
幾個核心參數的作用:
corePoolSize: 線(xiàn)程池核心線(xiàn)程數最大值
maximumPoolSize: 線(xiàn)程池最大線(xiàn)程數大小
keepAliveTime: 線(xiàn)程池中非核心線(xiàn)程空閑的存活時間大小
unit: 線(xiàn)程空閑存活時間單位
workQueue: 存放(fàng)任務的阻塞隊列
threadFactory: 用于設置創建線(xiàn)程的工廠,可以給創建的線(xiàn)程設置有意義的名字,可方便排查問題。
handler: 線(xiàn)城池的飽和策略事件,主要有四種類型。
四種飽和拒絕策略
AbortPolicy(抛出一個異常,默認的)
DiscardPolicy(直接丢棄任務)
DiscardOldestPolicy(丢棄隊列裏最老的任務,将當前這個任務繼續提交給線(xiàn)程池)
CallerRunsPolicy(交給線(xiàn)程池調用所在的線(xiàn)程進行處理)
線(xiàn)程池原理:
提交一個任務,線(xiàn)程池裏存活的核心線(xiàn)程數小于線(xiàn)程數corePoolSize時,線(xiàn)程池會創建一個核心線(xiàn)程去(qù)處理提交的任務。
如果線(xiàn)程池核心線(xiàn)程數已滿,即線(xiàn)程數已經等于corePoolSize,一個新提交的任務,會被放(fàng)進任務隊列workQueue排隊等待執行。
當線(xiàn)程池裏面存活的線(xiàn)程數已經等于corePoolSize了,并且任務隊列workQueue也滿,判斷線(xiàn)程數是(shì)否達到maximumPoolSize,即最大線(xiàn)程數是(shì)否已滿,如果沒到達,創建一個非核心線(xiàn)程執行提交的任務。
如果當前的線(xiàn)程數達到了maximumPoolSize,還有新的任務過來的話(huà),直接采用拒絕策略處理。
爲了形象描述線(xiàn)程池執行,我打個比喻:
核心線(xiàn)程比作公司正式員(yuán)工
非核心線(xiàn)程比作外包員(yuán)工
阻塞隊列比作需求池
提交任務比作提需求
當産品提個需求,正式員(yuán)工(核心線(xiàn)程)先接需求(執行任務)
如果正式員(yuán)工都有需求在做,即核心線(xiàn)程數已滿),産品就把需求先放(fàng)需求池(阻塞隊列)。
如果需求池(阻塞隊列)也滿了,但(dàn)是(shì)這時候産品繼續提需求,怎麽辦呢?那就請外包(非核心線(xiàn)程)來做。
如果所有員(yuán)工(最大線(xiàn)程數也滿了)都有需求在做了,那就執行拒絕策略。
如果外包員(yuán)工把需求做完了,它經過一段(keepAliveTime)空閑時間,就離(lí)開公司了。
7. ThreadLocal的使用場景有哪些?原理?内存洩漏?
ThreadLocal,即線(xiàn)程本地變量。如果你創建了一個ThreadLocal變量,那麽訪問這個變量的每個線(xiàn)程都會有這個變量的一個本地拷貝,多個線(xiàn)程操作這個變量的時候,實際是(shì)操作自己本地内存裏面的變量,從而起到線(xiàn)程隔離(lí)的作用,避免了線(xiàn)程安全問題。
ThreadLocal的應用場景
數據庫連接池
會話(huà)管理中使用
ThreadLocal内存結構圖:
ThreadLocal原理
Thread對象中持有一個ThreadLocal.ThreadLocalMap的成員(yuán)變量。
ThreadLocalMap内部維護了Entry數組,每個Entry代表一個完整的對象,key是(shì)ThreadLocal本身,value是(shì)ThreadLocal的泛型值。
每個線(xiàn)程在往ThreadLocal裏設置值的時候,都是(shì)往自己的ThreadLocalMap裏存,讀也是(shì)以某個ThreadLocal作爲引用,在自己的map裏找對應的key,從而實現(xiàn)了線(xiàn)程隔離(lí)。
ThreadLocal 内存洩露問題
先看看一下的TreadLocal的引用示意圖哈,
ThreadLocalMap中使用的 key 爲 ThreadLocal 的弱引用,如下
弱引用:隻要垃圾回收機制一運行,不管JVM的内存空間是(shì)否充足,都會回收該對象占用的内存。
弱引用比較容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但(dàn)是(shì)因爲ThreadLocalMap生命周期和Thread是(shì)一樣的,它這時候如果不被回收,就會出現(xiàn)這種情況:ThreadLocalMap的key沒了,value還在,這就會造成了内存洩漏問題。
如何解決内存洩漏問題?使用完ThreadLocal後,及時調用remove()方法釋放(fàng)内存空間。
8、kafka是(shì)如何保證消息的有序性?
kafka這樣保證消息有序性的:
一個 topic,一個 partition,一個 consumer,内部單線(xiàn)程消費(fèi),單線(xiàn)程吞吐量太低,一般不會用這個。(全局有序性)
寫 N 個内存 queue,具有相(xiàng)同 key 的數據都到同一個内存 queue;然後對于 N 個線(xiàn)程,每個線(xiàn)程分别消費(fèi)一個内存 queue 即可,這樣就能保證順序性。
大家可以看下消息隊列的有序性是(shì)怎麽推導的哈:
消息的有序性,就是(shì)指可以按照消息的發送順序來消費(fèi)。有些業務對消息的順序是(shì)有要求的,比如先下單再付款,最後再完成訂單,這樣等。假設生産者先後産生了兩條消息,分别是(shì)下單消息(M1),付款消息(M2),M1比M2先産生,如何保證M1比M2先被消費(fèi)呢。
爲了保證消息的順序性,可以将将M1、M2發送到同一個Server上,當M1發送完收到ack後,M2再發送。如圖:
這樣還是(shì)可能會有問題,因爲從MQ服務器到服務端,可能存在網絡延遲,雖然M1先發送,但(dàn)是(shì)它比M2晚到。
那還能怎麽辦才能保證消息的順序性呢?将M1和M2發往同一個消費(fèi)者,且發送M1後,等到消費(fèi)端ACK成功後,才發送M2就得了。
消息隊列保證順序性整體思路就是(shì)這樣啦。比如Kafka的全局有序消息,就是(shì)這種思想的體現(xiàn): 就是(shì)生産者發消息時,1個Topic
隻能對應1個Partition
,一個 Consumer
,内部單線(xiàn)程消費(fèi)。
但(dàn)是(shì)這樣吞吐量太低,一般保證消息局部有序即可。在發消息的時候指定Partition Key
,Kafka對其進行Hash計算,根據計算結果決定放(fàng)入哪個Partition
。這樣Partition Key相(xiàng)同的消息會放(fàng)在同一個Partition。然後多消費(fèi)者單線(xiàn)程消費(fèi)指定的Partition。
9、Nacos的選舉機制了解嘛?說下Raft算法?
Nacos作爲配置中心的功能是(shì)基于Raft算法來實現(xiàn)的。
Raft 算法是(shì)分布式系統開發首選的共識算法,它通過“一切以領導者爲準”的方式,實現(xiàn)一系列值的共識和各節點日志的一緻。
Raft選舉規程 涉及三種角色和任期(Term),
Follower:默默地接收和處理來自Leader的消息,當等待Leader心跳(tiào)信息超時的時候,就主動站出來,推薦自己當Candidate。
Candidate:向其他節點發送投票(piào)請求,通知(zhī)其他節點來投票(piào),如果赢得了大多數(N/2+1)選票(piào),就晉升Leader。
Leader:負責處理客戶端請求,進行日志複制等操作,每一輪選舉的目标就是(shì)選出一個領導者;領導者會不斷地發送心跳(tiào)信息,通知(zhī)其他節點“我是(shì)領導者,我還活着,你們不要發起新的選舉,不用找個新領導者來替代我。”
Term:這跟民主社會的選舉很像,每一屆新的履職期稱之爲一屆任期
領導選舉過程
在初始時,集群中所有的節點都是(shì)Follower狀态,都被設定一個随機選舉超時時間(一般150ms-300ms):
如果Follower在規定的超時時間,都沒有收到來自Leader的心跳(tiào),它就發起選舉:将自己的狀态切爲 Candidate,增加自己的任期編号,然後向集群中的其它Follower節點發送請求,詢問其是(shì)否選舉自己成爲Leader:
其他節點收到候選人A的請求投票(piào)消息後,如果在編号爲1的這屆任期内還沒有進行過投票(piào),那麽它将把選票(piào)投給節點A,并增加自己的任期編号:
當收到來自集群中過半節點的接受投票(piào)後,A節點即成爲本屆任期内 Leader,他将周期性地發送心跳(tiào)消息,通知(zhī)其他節點我是(shì)Leader,阻止Follower發起新的選舉:
10、聊一聊TCC補償機制
TCC是(shì)分布式事務的一種解決方案。它采用了補償機制,其核心思想是(shì):針對每個操作,都要注冊一個與其對應的确認和補償(撤銷)操作。TCC(Try-Confirm-Cancel)包括三段流程:
try階段:嘗試去(qù)執行,完成所有業務的一緻性檢查,預留必須的業務資源。
Confirm階段:該階段對業務進行确認提交,不做任何檢查,因爲try階段已經檢查過了,默認Confirm階段是(shì)不會出錯的。
Cancel 階段:若業務執行失敗,則進入該階段,它會釋放(fàng)try階段占用的所有業務資源,并回滾Confirm階段執行的所有操作。
下面再拿用戶下單購買禮物作爲例子來模拟TCC實現(xiàn)分布式事務的過程:
假設用戶A餘額爲100金币,擁有的禮物爲5朵。A花了10個金币,下訂單,購買10朵玫瑰。餘額、訂單、禮物都在不同數據庫。
TCC的Try階段:
生成一條訂單記錄,訂單狀态爲待确認。
将用戶A的賬戶金币中餘額更新爲90,凍結金币爲10(預留業務資源)
将用戶的禮物數量爲5,預增加數量爲10。
Try成功之後,便進入Confirm階段
Try過程發生任何異常,均進入Cancel階段
TCC的Confirm階段:
訂單狀态更新爲已支付
更新用戶餘額爲90,可凍結爲0
用戶禮物數量更新爲15,預增加爲0
Confirm過程發生任何異常,均進入Cancel階段
Confirm過程執行成功,則該事務結束
TCC的Cancel階段:
修改訂單狀态爲已取消
更新用戶餘額回100
更新用戶禮物數量爲5
TCC的優點是(shì)可以自定義數據庫操作的粒度,降低了鎖沖突,可以提升性能
TCC的缺點是(shì)應用侵入性強,需要根據網絡、系統故障等不同失敗原因實現(xiàn)不同的回滾策略,實現(xiàn)難度大,一般借助TCC開源框架,ByteTCC,TCC-transaction,Himly。
掃一掃,關注我們