柚子快報邀請碼778899分享:開發(fā)語言 java八股文面試
柚子快報邀請碼778899分享:開發(fā)語言 java八股文面試
B站地址
目錄
JVM1、運行數(shù)據(jù)區(qū)2、虛擬機棧3、棧內(nèi)存溢出4、NIO與BIO5、類加載器6、類加載的過程7、對象什么時候回收(判斷是否GC算法)8、JVM垃圾回收算法9、強引用、軟引用、弱引用、虛引用
多線程線程基礎(chǔ)1、創(chuàng)建線程的方式2、Runnable和Callable有什么區(qū)別3、線程有哪些狀態(tài),狀態(tài)如何切換4、如何保證T1、T2、T3按順序執(zhí)行(join)5、notify()和notifyALL()區(qū)別6、wait和sleep的不同7、如何停止正在運行的線程
線程安全1、synchronized關(guān)鍵字底層原理2、monitor重量級鎖,鎖升級3、JMM(Java內(nèi)存模型)4、CAS(比較再交換)5、樂觀鎖與悲觀鎖6、volatile(線程可見性)7、AQS(抽象隊列同步器)8、ReentrantLock可重入鎖9、synchronized和ReentrantLock區(qū)別10、死鎖產(chǎn)生條件11、ConcurrentHashMap線程安全的HashMap12、如何保證多線程執(zhí)行安全(并發(fā)問題出現(xiàn)根本原因)
線程池1、線程池核心參數(shù)、執(zhí)行原理2、線程池中有哪些常見阻塞隊列3、如何確定核心線程數(shù)4、線程池種類
使用場景1、CountDownLatch2、控制某個方法允許并發(fā)訪問線程的數(shù)量(信號量)3、ThreadLocal線程安全
MySQL優(yōu)化定位慢查詢?nèi)绾畏治鯯QL執(zhí)行慢
索引1、索引的底層數(shù)據(jù)結(jié)構(gòu)2、聚簇索引與非聚簇索引3、回表查詢4、覆蓋索引5、MySQL超大分頁處理6、索引創(chuàng)建原則7、索引什么時候失效8、sql語句優(yōu)化
事務(wù)事務(wù)特性(ACID)隔離級別:
主從同步
Redis緩存1、緩存問題緩存穿透緩存雪崩緩存擊穿
2、如何修改Redis中的數(shù)據(jù)雙寫一致性強一致性的解決方案最終一致性的解決方案
3、緩存持久化RDBAOFRDB-AOF混合
4、數(shù)據(jù)過期刪除策略(時間)惰性刪除定期刪除
5、數(shù)據(jù)淘汰策略(空間、內(nèi)存)
分布式鎖Redisson
集群主從復(fù)制哨兵模式分片集群
為什么快
SSM1、Spring框架中單例bean是線程安全的嗎2、什么是AOP3、Spring中事務(wù)失效的場景4、Spring中循環(huán)引用5、Springboot自動配置原理6、Spring框架常見注解7、MyBatis執(zhí)行流程8、Mybatis是否支持延時加載9、Mybatis一級、二級緩存攔截器
SpringCloud1、SpringCloud的5大組件2、SpringCloud如何實現(xiàn)服務(wù)的注冊發(fā)現(xiàn)3、負載均衡如何實現(xiàn)4、服務(wù)雪崩(降級熔斷)5、限流6、分布式事務(wù)CAP和BASE解決分布式事務(wù)思想和模型seata
7、接口的冪等性問題
RabbitMQRabbitMQ模式交換機類型消息可靠性消息重復(fù)消費如何解決?延時消息死信交換機
消息堆積解決?
JVM
1、運行數(shù)據(jù)區(qū)
方法區(qū)/元空間 JKD8后,堆中永久代變成了元空間 線程共享區(qū)域,保存對象的類型數(shù)據(jù) 靜態(tài)域:存放靜態(tài)成員 常量池:存放字符串常量對象和基本類型常量堆 線程共享區(qū)域,保存對象實例、數(shù)組,內(nèi)存不足拋出OutOfMempryErroe異常 年輕代:伊甸園區(qū)+幸存者區(qū)(s0+s1);伊甸園區(qū)GC一次進入幸存者區(qū) 老年代:保存生命周期長的對象;年齡到15后,進入老年代 永久代:1.8后被移除,數(shù)據(jù)存入元空間,防止內(nèi)存溢出虛擬機棧 線程私有,存儲基本數(shù)據(jù)類型,引用對象的地址 每個線程運行時需要的內(nèi)存,先進后出 棧幀對應(yīng)每次方法調(diào)用所占內(nèi)存 活動棧幀對應(yīng)當(dāng)前正在執(zhí)行的方法本地方法棧 調(diào)用非Java代碼的接口程序計數(shù)器 線程私有,每個線程都有各自的程序計數(shù)器 記錄正在執(zhí)行的字節(jié)碼地址
棧管運行,堆管存儲
2、虛擬機棧
方法內(nèi)局部變量是否線程安全?
方法內(nèi)局部變量沒有逃離方法的作用范圍,則是線程安全的局部變量引用了對象,或逃離了方法作用范圍(return),則線程不安全
3、棧內(nèi)存溢出
棧幀過多,比如遞歸調(diào)用(StackOverFlowError)
4、NIO與BIO
NIO 使用非阻塞式 I/O,而 BIO 使用阻塞式 I/O。 在阻塞式 I/O 中,當(dāng)一個 I/O 操作完成之前,線程會一直被阻塞,直到 I/O 操作完成; 在非阻塞式 I/O 中,線程可以繼續(xù)執(zhí)行其他任務(wù),直到 I/O 操作完成并返回結(jié)果。 BIO操作磁盤文件時,需要將文件從系統(tǒng)內(nèi)存讀取進Java堆內(nèi)存再進行操作 NIO操作時,使用直接內(nèi)存做為數(shù)據(jù)緩沖區(qū), 直接內(nèi)存:Java可直接操作磁盤文件 讀寫性能高,不受JVM內(nèi)存回收管理。但回收成本高
5、類加載器
類加載器將字節(jié)碼加載到JVM中
Bootstrap 啟動類加載器ExtClassLoader 擴展類加載器AppClassLoader 應(yīng)用類加載器
類加載器的雙親委派機制
3種加載器如果父類加載器已經(jīng)可以加載,則不用自身加載
可以避免某一個類被重復(fù)加載,保證唯一性 為了安全,保證類庫API不會被修改
6、類加載的過程
加載:將字節(jié)碼文件通過IO流讀取到JVM的方法區(qū),并同時在堆中生成Class對像。
驗證:校驗字節(jié)碼文件的正確性。
準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并初始化為默認值;對于final static修飾的變量,在編譯時就已經(jīng)分配好內(nèi)存了。
解析:將類中的符號引用轉(zhuǎn)換為直接引用。
初始化:對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼。
7、對象什么時候回收(判斷是否GC算法)
可達性分析算法 對象沒有任何引用指向它,就可以被回收
8、JVM垃圾回收算法
標(biāo)記清除 根據(jù)可達性分析進行標(biāo)記,再清除標(biāo)記整理 標(biāo)記清除后,將存活對象都向內(nèi)存一段移動復(fù)制算法
9、強引用、軟引用、弱引用、虛引用
強引用 只要GC Roots能找到,就不會被回收 軟引用 多次垃圾回收后,內(nèi)存依然不夠,就會被回收 弱引用 只要進行了垃圾回收,就會被回收 虛引用 在任何時候都可能被垃圾回收器回收。必須和引用隊列聯(lián)合使用
多線程
線程基礎(chǔ)
1、創(chuàng)建線程的方式
實現(xiàn)Thread類實現(xiàn)Runnable接口實現(xiàn)Callable接口創(chuàng)建線程池
2、Runnable和Callable有什么區(qū)別
Runnable接口run方法沒有返回值Callable接口run方法有返回值,F(xiàn)utureTask可以獲取異步執(zhí)行結(jié)果Runnable接口run方法異常只能內(nèi)部處理,不能拋出Callable接口run方法允許拋出異常
3、線程有哪些狀態(tài),狀態(tài)如何切換
新建:新創(chuàng)建了一個線程對象,和其他java對象一樣,僅在堆中分配內(nèi)存。就緒: 線程對象創(chuàng)建后,其他線程調(diào)用了該對象的 start() 方法。線程已具備了條件,只須再獲得CPU便可立即執(zhí)行運行:就緒態(tài)的線程獲得了CPU時間片,執(zhí)行程序代碼阻塞:阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),有機會轉(zhuǎn)到運行狀態(tài)。
等待阻塞:運行狀態(tài)中的線程執(zhí)行 wait 方法,等待阻塞;其他線程調(diào)用notify()切換為可執(zhí)行態(tài)同步阻塞:如果沒有獲得鎖,進入阻塞狀態(tài),獲得鎖后切換為可執(zhí)行態(tài)其他阻塞:運行的線程執(zhí)行sleep(50)方法,進入等待倒計時,時間到了切換為可執(zhí)行態(tài) 死亡:線程執(zhí)行完了或者因異常退出run()方法
4、如何保證T1、T2、T3按順序執(zhí)行(join)
join():阻塞當(dāng)前線程,讓當(dāng)前線程等待另一個線程執(zhí)行完畢的方法在線程T2中t1.join;在線程T3中t2.join
5、notify()和notifyALL()區(qū)別
notify():只隨機喚醒一個wait線程notifyALL():喚醒所有wait線程
6、wait和sleep的不同
共同點:wait()、wait(long)、sleep(long)都是讓當(dāng)前線程暫時放棄CPU使用權(quán),進入阻塞
不同點
方法歸屬不同
sleep是Thread靜態(tài)方法。wait是Object成員方法 醒來時間不同
wait(long)、sleep(long)等待時間后醒來wait()、wait(long)還可以被notify喚醒,不喚醒則一直等待 鎖特性不同
wait方法調(diào)用必須獲取wait對象的鎖,而sleep無此限制 Object object = new Object();
Thread t1 = new Thread(() ->{
synchronized (object){
object.wait(); //wait必須與synchronized配合使用
}
});
wait方法執(zhí)行后會釋放鎖,允許其他線程獲得該對象鎖(釋放鎖不用喚醒或等待時間結(jié)束) sleep在synchronized中執(zhí)行,不會釋放對象鎖
7、如何停止正在運行的線程
stop方法,不推薦,已過時 使用退出標(biāo)志,使線程正常退出(run方法執(zhí)行完成后線程終止) class MyThread extends Thread {
private volatile boolean running = true; //注意volatile線程可見性
public void run() {
while (running) {
// 線程的執(zhí)行邏輯
}
}
public void stopThread() {
running = false;
}
}
MyThread thread = new MyThread();
thread.start();
// 停止線程
thread.stopThread();
interrupt方法中斷線程(類似推出標(biāo)志)
打斷阻塞線程(sleep、wait、join),線程會拋出InterruptedException 打斷正常線程,根據(jù)打斷標(biāo)志標(biāo)記是否退出線程 class MyRunnable implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// 線程的執(zhí)行邏輯
}
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
// 停止線程
thread.interrupt();
線程安全
1、synchronized關(guān)鍵字底層原理
synchronized對象鎖,同一時刻只能有一個線程持有 底層是monitor實現(xiàn),內(nèi)部有三個屬性
owner:關(guān)聯(lián)獲得鎖的線程,只能關(guān)聯(lián)一個entrylist:關(guān)聯(lián)處于阻塞狀態(tài)的線程waitset:關(guān)聯(lián)處于waiting狀態(tài)的線程 synchronized有偏向鎖狀態(tài)、輕量級鎖狀態(tài)、重量級鎖狀態(tài)三種形式
2、monitor重量級鎖,鎖升級
鎖主要存在四種狀態(tài),依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)、重量級鎖狀態(tài),性能依次是從高到低。鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖。但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現(xiàn)鎖的降級。
重量級鎖:底層使用monitor,會涉及用戶態(tài)與內(nèi)核態(tài)的切換,性能較低輕量級鎖:線程加鎖時間錯開(沒有競爭)時使用。修改了對象頭的鎖標(biāo)志,修改使用了CAS偏向鎖:很長時間都只被一個線程使用鎖時使用。第一次修改標(biāo)志用CAS,之后不用。
一旦鎖發(fā)生了競爭,都會升級為重量級鎖。
3、JMM(Java內(nèi)存模型)
定義了共享內(nèi)存中多線程程序讀寫操作
JMM把內(nèi)存分為:私有的工作內(nèi)存、共享的主內(nèi)存
線程共享:堆、方法區(qū) 線程私有:程序計數(shù)器、虛擬機棧、本地方法棧
4、CAS(比較再交換)
(compare And Swap)比較再交換,樂觀鎖/自旋鎖,其他線程可以修改共享變量 線程讀取數(shù)據(jù)操作完成,再把數(shù)據(jù)寫入共享空間, 比較是否與讀取數(shù)據(jù)時相同,不同則重新讀取再做操作。
沒有加鎖,線程不會陷入阻塞,效率較高
如果競爭激烈,重試頻繁發(fā)生,效率會受影響
實現(xiàn):Unsafe類的compareAndSwap方法
5、樂觀鎖與悲觀鎖
樂觀鎖 CAS,不怕其他線程修改共享數(shù)據(jù)(樂觀地認為別的線程不會修改共享數(shù)據(jù))悲觀鎖 synchronized、ReentrantLock,防止其他線程修改數(shù)據(jù)(悲觀地認為別的線程會修改共享數(shù)據(jù))
6、volatile(線程可見性)
用volatile修飾共享變量
防止編譯器優(yōu)化讓一個線程對共享變量的修改對另一個線程可見禁止指令重排序 技巧:
寫變量讓volatile修飾的變量在代碼最后位置讀變量讓volatile修飾的變量在代碼最開始位置
7、AQS(抽象隊列同步器)
AbstractQueuedSynchronizer 抽象隊列同步器,是一種鎖機制
常見實現(xiàn)
ReentrantLock 阻塞式鎖 Semaphora 信號量 CountDownLatch 倒計時鎖 AQS內(nèi)部維護了一個先進先出的雙向隊列,存儲排隊的線程 AQS可以是非公平鎖、公平鎖。取決于新來的線程直接競爭還是排隊。 AQS內(nèi)部有一個屬性state,相當(dāng)于一個資源,默認為0(無鎖) 如果隊列中有一個線程修改成功了state為1,則相當(dāng)于當(dāng)前線程獲取了資源 多個線程修改state時,使用CAS操作,保證原子性
8、ReentrantLock可重入鎖
利用CAS+AQS實現(xiàn) ReentrantLock支持重新進入的鎖,調(diào)用lock獲取鎖后,再次調(diào)用lock是不會阻塞的 支持公平鎖與非公平鎖,無參默認是非公平鎖,可傳參設(shè)置為公平鎖
9、synchronized和ReentrantLock區(qū)別
public class Syn implements Runnable{
Object lock = new Object();
@Override
public void run() {
synchronized (lock){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ReentrantLock lock = new ReentrantLock();
public static void lock1() {
try {
lock.lock();
} finally {
lock.unlock();
}
}
語法
使用synchronized時,退出同步代碼塊,鎖會自動釋放使用lock時,需要手動調(diào)用unlock方法釋放鎖 功能
二者都屬于悲觀鎖,都有互斥、同步、可重入功能Lock提供了synchronized不具備功能
公平鎖:可傳參設(shè)置為公平鎖可打斷:打斷后,不會進入阻塞狀態(tài)可超時:設(shè)置等待鎖時間多條件變量:多個線程設(shè)置await等待、signal執(zhí)行(t1.signalAll,喚醒t1.await所有線程) Lock有不同實現(xiàn),如ReentrantLock、ReentrantReadWriteLock(讀寫鎖) 性能
沒有競爭時,synchronized做了很多優(yōu)化,如偏向鎖、輕量級鎖,性能好競爭激烈時,lock的實現(xiàn)性能更好
10、死鎖產(chǎn)生條件
一個線程需要同時獲取多把鎖,此時容易發(fā)生死鎖 jdk自帶的jps可檢測死鎖
11、ConcurrentHashMap線程安全的HashMap
底層與HashMap一樣,數(shù)組+鏈表/紅黑樹使用synchronized鎖定鏈表或紅黑樹的首節(jié)點(數(shù)組的單個位置)CAS添加新節(jié)點
12、如何保證多線程執(zhí)行安全(并發(fā)問題出現(xiàn)根本原因)
原子性 synchronized或lock鎖解決內(nèi)存可見性 volatile解決有序性 (指令重排列) volatile解決
線程池
1、線程池核心參數(shù)、執(zhí)行原理
corePoolSize 核心線程數(shù)maximumPoolSize 最大線程數(shù) = (核心線程數(shù)+救急線程數(shù))keepAliveTime 生存時間:救濟線程的生存時間,生存時間內(nèi)沒有新任務(wù),此線程資源會釋放unit 時間單位:救濟線程生存時間單位workQueue 阻塞隊列:核心線程已滿,任務(wù)進入阻塞隊列;阻塞隊列滿了,創(chuàng)建救急線程threadFactory 線程工廠:定制線程對象創(chuàng)建,可設(shè)置線程名字handler 拒絕策略:核心線程數(shù)、 阻塞隊列、救急線程數(shù)全滿,觸發(fā)拒絕策略
執(zhí)行原理
提交任務(wù)進入核心線程
核心線程沒滿,添加到工作線程并執(zhí)行核心線程滿了,進入阻塞隊列 進入阻塞隊列
阻塞隊列沒滿,添加到阻塞隊列阻塞隊列滿了,創(chuàng)建臨時線程 創(chuàng)建臨時線程
核心線程數(shù)+臨時線程數(shù) < 最大線程數(shù) 創(chuàng)建臨時線程執(zhí)行任務(wù)(也會執(zhí)行阻塞隊列中任務(wù))核心線程數(shù)+臨時線程數(shù) = 最大線程數(shù) 觸發(fā)handler拒絕策略
直接拋出異常(默認)調(diào)用者來執(zhí)行任務(wù)(主線程執(zhí)行)丟棄阻塞隊列最靠前的任務(wù),并執(zhí)行當(dāng)前任務(wù)(加入阻塞隊列)直接丟棄任務(wù)
2、線程池中有哪些常見阻塞隊列
workQueue 阻塞隊列
ArrayBlockingQueue 基于數(shù)組結(jié)構(gòu)的有界阻塞隊列,F(xiàn)IFO
強制有界(初始化參數(shù))提前初始化Node數(shù)組,提前創(chuàng)建一把鎖,鎖數(shù)組 LinkedBlockingQueue 基于鏈表結(jié)構(gòu)的有界阻塞隊列,F(xiàn)IFO
默認無界(無參數(shù)則為int最大值),支持有界創(chuàng)建節(jié)點時才添加數(shù)據(jù),入隊生成新的Node兩把鎖,鎖鏈表頭與鏈表位 DelayedWord 優(yōu)先級隊列,執(zhí)行時間最靠前的先出隊
3、如何確定核心線程數(shù)
IO密集型任務(wù):文件讀寫、DB讀寫、網(wǎng)絡(luò)請求
核心線程數(shù)設(shè)置為2N+1 CPU密集型任務(wù):計算型、Bitmap轉(zhuǎn)換
核心線程數(shù)設(shè)置為N+1
N為CPU核數(shù)
4、線程池種類
使用Executors創(chuàng)建自帶的線程池(不建議使用,可用ThreadPoolExecutor自定義線程池)
固定線程數(shù)的線程池(FixedThreadPool)
核心線程數(shù) = 最大線程數(shù)(沒有臨時線程數(shù))阻塞隊列為LinkedBlockingQueue,容量為int最大值適用于任務(wù)量已知,相對耗時的任務(wù) 單線程化的線程池(SingleThreadExecutor)
核心線程數(shù) = 最大線程數(shù) = 1阻塞隊列為LinkedBlockingQueue,容量為int最大值適用于按照順序執(zhí)行的任務(wù) 如果當(dāng)前線程意外終止,線程池會創(chuàng)建一個新的線程來代替它,保證線程的可用性。 可緩存線程池(CachedThreadPool)
核心線程數(shù) = 0;最大線程數(shù) = int最大值適用于任務(wù)數(shù)比較密集,但每個任務(wù)執(zhí)行時間較短 可延時、周期執(zhí)行的線程池(ScheduledThreadPoolExecutor)
可設(shè)置執(zhí)行任務(wù)開始時間
使用場景
1、CountDownLatch
CountDownLatch latch = new CountDownLatch(2); //參數(shù)2
new Thread(()->{
latch.countDown; //調(diào)用一次減1
}).start();
new Thread(()->{
latch.countDown;
}).start();
latch.await(); //為0時喚醒,繼續(xù)執(zhí)行
2、控制某個方法允許并發(fā)訪問線程的數(shù)量(信號量)
Semaphore信號量
3、ThreadLocal線程安全
set設(shè)置值、get獲取值、remove清除值
ThreadLocal實現(xiàn)資源對象線程隔離,每個線程各用各的資源對象ThreadLocal實現(xiàn)線程內(nèi)的資源共享
ThreadLocal會導(dǎo)致內(nèi)存泄漏,因為ThreadLocalMap中key是弱引用、值是強引用
內(nèi)存泄漏:已動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放
MySQL
優(yōu)化
定位慢查詢
運維工具(skywalking)mysql中慢查詢?nèi)罩?,sql執(zhí)行超過2秒就會記錄
如何分析SQL執(zhí)行慢
explain或desc加在sql前
顯示信息:
key 當(dāng)前sql實際命中索引(檢查索引是否失效) key_len 索引占用大小 type 連接類型,由好到差依次為以下
const:根據(jù)鍵查詢eq_ref:主鍵索引查詢或唯一索引查詢ref:索引查詢range:范圍查詢index:索引樹掃描all:全盤掃描
索引
1、索引的底層數(shù)據(jù)結(jié)構(gòu)
MySQL的InnoDB引擎采用B+樹,階數(shù)更多,路徑更短B+樹非葉子節(jié)點只存儲指針,葉子節(jié)點存儲數(shù)據(jù)B+樹的葉子節(jié)點是雙向鏈表,且是從小到大的有序鏈表
2、聚簇索引與非聚簇索引
主鍵就是聚簇索引(聚集索引),葉子節(jié)點保存了整行數(shù)據(jù)非聚簇索引(二級索引),自己創(chuàng)建的索引,葉子節(jié)點保存的是對應(yīng)主鍵
3、回表查詢
通過二級索引找到對應(yīng)主鍵值,再到聚集索引中查找正行數(shù)據(jù)
4、覆蓋索引
查詢使用了索引,并需要返回的列,在該索引中已經(jīng)全部能夠找到(不需要回表查詢)
#當(dāng)id、name創(chuàng)建了索引,id為主鍵
select * from user where id = 1;
#是覆蓋索引,聚集索引中包含所有行信息
select id, name from user where name = 'jack';
#是覆蓋索引,二級索引中包含主鍵,且name是索引
5、MySQL超大分頁處理
當(dāng)數(shù)據(jù)量特別大,limit分頁查詢,需要進行排序,效率低解決:覆蓋索引+子查詢
6、索引創(chuàng)建原則
表數(shù)據(jù)量大,查詢頻繁,可以給表創(chuàng)建索引(單表超過10萬條)字段常被用于條件、排序、分組,創(chuàng)建索引使用聯(lián)合索引(復(fù)合索引),避免回表控制索引數(shù)量
7、索引什么時候失效
使用explain在sql前判斷是否索引失效
聯(lián)合索引,違反最左前綴原則范圍查詢右邊的列,不能使用索引不要在索引列上進行運算操作字符串不加單引號(數(shù)字類型與String類型的 ‘0’)以百分號%開頭like模糊查詢,索引失效
8、sql語句優(yōu)化
避免使用select *避免索引失效寫法用union all代替union,union會多一次過濾,效率低避免在where字句中對字段進行表達式操作(可能索引失效)多表查詢,用小表驅(qū)動大表批量操作
事務(wù)
事務(wù)特性(ACID)
原子性(Atomicity): 事務(wù)是不可分割的最小操作單元,要么全部完成,要么全部失敗一致性(Consistency): 事務(wù)完成時,必須使所有數(shù)據(jù)都保持一致隔離性(Isolation): 允許并發(fā)事務(wù)同時對其數(shù)據(jù)進行讀寫和修改的能力, 隔離性可以防止多個事務(wù)并發(fā)執(zhí)行時由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。持久性(Durability): 事務(wù)處理結(jié)束后,對數(shù)據(jù)的修改就是永久的
隔離級別:
讀未提交(Read uncommitted)讀提交(read committed) Oracle默認的隔離級別 解決臟讀:讀到事務(wù)沒提交的數(shù)據(jù)可重復(fù)讀(repeatable read) MySQL默認的隔離級別 解決不可重復(fù)讀:事務(wù)讀取同一條數(shù)據(jù),讀取數(shù)據(jù)不同串行化(Serializable) 表級鎖,讀寫都加鎖,效率低下,安全性高,不能并發(fā)。 解決幻讀:查詢時沒有數(shù)據(jù),插入時發(fā)現(xiàn)已經(jīng)存在,好像出現(xiàn)幻覺
主從同步
二進制日志(BINLOG)記錄了所有的DDL與DML
Redis
緩存
1、緩存問題
緩存穿透
查Redis、數(shù)據(jù)庫都不存在的數(shù)據(jù),架空結(jié)果緩存:緩存空數(shù)據(jù)
高并發(fā)請求沒有命中緩存,則所有請求都會直接查詢數(shù)據(jù)庫,造成數(shù)據(jù)庫崩潰 查詢一個一定不存在的數(shù)據(jù),由于緩存不命中,每次都會去查詢數(shù)據(jù)庫,導(dǎo)致緩存失去意義
解決
null結(jié)果緩存(緩存空數(shù)據(jù)),并加入短暫過期時間布隆過濾器(hash算法,bitmap)
緩存雪崩
大量緩存key同時失效:加時間隨機值
設(shè)置緩存時,采用了相同過期時間,導(dǎo)致緩存在某一時刻同時失效 大量請求全部轉(zhuǎn)發(fā)到DB,導(dǎo)致瞬時壓力過重雪崩
解決:在失效時間上加一個隨機值,比如1-5分鐘
緩存擊穿
熱點key失效被大量訪問:加鎖
某一個高頻熱點key,在失效后,正好有大量請求同時訪問,這些key的查詢都會落在DB上
解決:加鎖,大量并發(fā)只讓一個人查,存入緩存,其他人再從緩存查
2、如何修改Redis中的數(shù)據(jù)
雙寫一致性
當(dāng)修改了數(shù)據(jù)庫的數(shù)據(jù)也要同時更新緩存的數(shù)據(jù),緩存和數(shù)據(jù)庫的數(shù)據(jù)要保存一致
解決同時修改緩存與數(shù)據(jù)庫時,的數(shù)據(jù)一致性
強一致性的解決方案
業(yè)務(wù)必須保證強一致性
延時雙刪
刪除緩存 修改數(shù)據(jù)庫 延時 刪除緩存為什么刪兩次?在修改數(shù)據(jù)庫前,可能沒修改完成時被讀取到緩存中 延時作用:Redis主從架構(gòu),保證從機也同步了數(shù)據(jù)缺點:有臟數(shù)據(jù)風(fēng)險,因為延時時間不好確定 讀寫鎖 加入緩存的數(shù)據(jù),讀多寫少 寫時加鎖,可讀取
最終一致性的解決方案
業(yè)務(wù)可以允許數(shù)據(jù)短暫不一致
通過MQ異步通知保證數(shù)據(jù)的最終一致性
修改數(shù)據(jù)庫數(shù)據(jù) -> mq發(fā)布消息 -> 業(yè)務(wù)跟新緩存
3、緩存持久化
RDB
(Redis Database)
在指定時間間隔內(nèi),執(zhí)行數(shù)據(jù)集的時間點快照 保存時將內(nèi)存中數(shù)據(jù)集寫入磁盤,恢復(fù)時再從磁盤寫入內(nèi)存
自動觸發(fā)
每多少秒有操作,自動保存dump.rdb文件自動恢復(fù),當(dāng)redis掛掉,再啟動會自動讀取dump.rdb文件文件并恢復(fù) 手動觸發(fā)
save(不用):會阻塞redis服務(wù)器,此時redis不能處理其他bgsave(可使用):不阻塞
AOF
(Append Only File) 追加文件
以日志記錄每個寫操作。 默認沒有開啟,在conf里設(shè)置
三種保存頻率
1、no 系統(tǒng)控制,性能最好,但可靠性差,可能丟失大量數(shù)據(jù) 2、everysec(一般采用)每秒刷盤,性能適中,最多丟一秒數(shù)據(jù) 3、always 每次查詢時刷盤,可靠性高,但性能影響大
RDB-AOF混合
建議混合使用
RDB和AOF同時開啟,重啟時只會加載aof文件,不會加載rdb文件 aof優(yōu)先級高于rdb
4、數(shù)據(jù)過期刪除策略(時間)
惰性刪除
設(shè)置key過期后不管它,當(dāng)用到時在檢查是否過期,過期則刪
優(yōu)點:對cpu友好,不浪費時間檢查過期
缺點:對內(nèi)存不友好,過期后沒有刪除
定期刪除
每隔一段時間,對key檢查,刪除過期key
Redis過期策略:惰性刪除+定期刪除
5、數(shù)據(jù)淘汰策略(空間、內(nèi)存)
Redis內(nèi)存不夠,此時向Redis中添加新的key,那么Redis會根據(jù)某種規(guī)則將內(nèi)存中的數(shù)據(jù)刪除
noeviction:不淘汰任何key,但內(nèi)存滿是不允許寫入新數(shù)據(jù),默認volatile-ttl:對設(shè)置了ttl的key,比較key的剩余ttl值,小的被淘汰allkeys-random:對全體key,隨機淘汰volatile-random:對設(shè)置了ttl的值隨機淘汰allkeys-lru:對全體key,基于LRU算法進行淘汰(LRU:最近最少使用)volatile-lru:對設(shè)置了ttl的key,基于LRU算法進行淘汰allkeys-lfu:對全體key,基于LFU算法進行淘汰(LFU:最少使用頻率)volatile-lfu:對設(shè)置了ttl的key,基于LFU算法進行淘汰
優(yōu)先使用:allkeys-lru
分布式鎖
獲取鎖
SET lock value NX EX 10
#NX是互斥,EX是設(shè)置超時時間(超時時間防止服務(wù)宕機而不會釋放鎖)
釋放鎖
DEL key
#釋放鎖,刪除即可
redisson底層采用sentnx命令實現(xiàn)分布式鎖(設(shè)置ttl,防止宕機后,鎖永遠無法釋放) SET if not exists(如果key不存在則SET:只能有一個獲取鎖成功)
setnx lock 1 #獲取鎖,返回1獲取鎖成功
setnx lock 2 #因為lock為key值已存在,所以返回0,獲取鎖失敗
del lock 1 #釋放鎖
setnx實現(xiàn)分布式鎖缺點
不可重入不可重試超時釋放主從一致性
Redisson
依賴、配置、注入使用
redisson實現(xiàn)分布式鎖
可重入鎖:判斷獲取鎖的對象是否是當(dāng)前線程,是則count++,標(biāo)識重入次數(shù) 可重試 watch dog 鎖續(xù)期機制:給持有鎖的線程續(xù)期(默認每隔10秒續(xù)期一次) 不能解決主從數(shù)據(jù)一致性
使用multiLock實現(xiàn)主從數(shù)據(jù)一致性: 在所有Redis主節(jié)點設(shè)置聯(lián)合鎖,使用時必須所有節(jié)點都獲取鎖成功 缺點:運維成本高,實現(xiàn)復(fù)雜
RedissonClient redissonClient;
RLock lock = redissonClient.getLock();
boolean islock = lock.tryLock(1,10,TimeUnit.SECONDS)
//參數(shù):獲取鎖最大等待時間(期間可重試),鎖自動釋放時間,時間單位
if(islock){
try{
執(zhí)行業(yè)務(wù)
}finally{
lock.unlock(); //釋放鎖
}
}
redis分布式鎖,如何實現(xiàn)
使用redisson實現(xiàn)的分布式鎖,底層是setnx和lua腳本
redisson實現(xiàn)分布式鎖,如何控制鎖的有效時常
線程獲取鎖成功后,看門狗會給有鎖的線程續(xù)期(默認10秒續(xù)期一次)
redisson的鎖可重入嗎
可重入
集群
主從復(fù)制
讀寫分離 從機只能讀,不能寫。
復(fù)制原理工作流程
slave啟動,同步初清 slave啟動時,會一次性同步master,自生數(shù)據(jù)被覆蓋清除首次連接,全量復(fù)制 master收到命令后,觸發(fā)RDB,將RDB文件發(fā)送給slave,實現(xiàn)復(fù)制初始化心跳持續(xù),保持通訊 每10秒發(fā)心跳,保持通訊進入平穩(wěn),增量復(fù)制 master繼續(xù)將收到的修改命令轉(zhuǎn)給slaver,完成同步從機下線,重連續(xù)傳 slaver下線后,master檢查offset偏移量,slaver上線后再發(fā)給slaver
缺點
復(fù)制延時,信號衰減master掛了,slaver不會上位成為master
哨兵模式
哨兵sentinel
master主機故障后,根據(jù)投票數(shù)自動將某一從庫轉(zhuǎn)換為主庫
哨兵作用:
監(jiān)控redis運行狀態(tài),包括master和slavemaster主機down后,自動將從機上位為主機
分片集群
集群cluster,自帶sentinel故障轉(zhuǎn)移機制,無需再使用哨兵功能
可將多個master看作一個整體,但每個單獨master處理數(shù)據(jù)不同,擴大了數(shù)據(jù)存儲量 根據(jù)槽位把每個key分配到不同的master,每個master負責(zé)不同的key
Redis分片集群作用
集群有多個master,可以保存不同數(shù)據(jù),增大數(shù)據(jù)存儲量整合了主從復(fù)制和哨兵模式
Redis分片集群數(shù)據(jù)如何存儲和讀取
哈希槽,將插槽分配到不同實例根據(jù)key計算哈希值,找到插槽所在的實例
為什么快
Redis是單線程的,但為什么還快?
Redis是純內(nèi)存操作單線程可避免不必要的上下文切換可競爭條件使用I/O多路復(fù)用模型,非阻塞IO
SSM
1、Spring框架中單例bean是線程安全的嗎
不是線程安全,對bean中可修改的成員變量,要考慮線程安全問題(加鎖)
2、什么是AOP
面向切面編程,將與業(yè)務(wù)無關(guān),可重用的模塊抽取出來,做統(tǒng)一處理使用場景:記錄操作日志、緩存處理、spring中內(nèi)置事務(wù)處理
3、Spring中事務(wù)失效的場景
自己吞了異常異常捕獲處理。異常捕獲不會失效;自己處理異常導(dǎo)致事務(wù)無法回滾,失效 解決:手動拋出。在catch塊添加 throw new RuntimeException(e) 拋出拋出檢查異常。throws FileNotFoundException 。Spring默認指揮回滾非檢查異常 解決:@Transactional(rollbackFor=Exception.class)非public方法導(dǎo)致事務(wù)失效。Spring為方法創(chuàng)建代理、添加事務(wù)通知,前提是public的 解決:改為public方法
4、Spring中循環(huán)引用
兩個bean對象互相持有對方,比如A依賴于B,B依賴于A
Spring框架三級緩存解決大部分循環(huán)依賴
一級緩存:單例池,緩存初始化完成的bean對象二級緩存:緩存早期bean對象(未走完生命周期)三級緩存:緩存ObjectFactory對象工廠,用來創(chuàng)建對象 構(gòu)造方法中出現(xiàn)循環(huán)依賴 解決:@Lazy 懶加載,需要時再進行bean對象創(chuàng)建
5、Springboot自動配置原理
@SpringBootApplication中有一個@EnableAutoConfiguration注解 里面通過@Import注解導(dǎo)入相關(guān)配置選擇器
6、Spring框架常見注解
@Component、@Controller、@Service 實例化Bean @Autowired 依賴注入 @Configuration 指定當(dāng)前類為Spring配置類 @ComponentScan 初始化容器時要掃描的包 @Bean 加入Spring容器 @Aspect、@Before、@After、@Around 面向切面編程 @RequestMapping、@PostMapping 請求路徑 @RequestParam 傳入?yún)?shù)與接受參數(shù)不同時,指定參數(shù)名稱 @PathViriable 從請求路徑獲取參數(shù)
7、MyBatis執(zhí)行流程
讀取MyBatis配置文件mybatis-config.xml構(gòu)建會話工廠SqlSessionFactory創(chuàng)建SqlSession對象操作數(shù)據(jù)庫接口,Executor執(zhí)行器Executor接口中有MappedStatement類型參數(shù),封裝了映射信息輸入?yún)?shù)映射輸出結(jié)果映射
8、Mybatis是否支持延時加載
延時加載,需要用到數(shù)據(jù)時才加載 支持一對一,一對多關(guān)聯(lián)查詢的延時加載默認關(guān)閉,可配置啟用
9、Mybatis一級、二級緩存
一級緩存 HashMap本地緩存,作用域Session,當(dāng)Session關(guān)閉后清空,默認打開二級緩存 HashMap本地緩存,作用域namespace。需要單獨開啟 當(dāng)一個作用域(一、二級緩存)增刪改后,select中緩存被清空
攔截器
implements HandlerInterceptor //繼承攔截器
boolean preHandle(){} //重寫方法,返回true放行;返回false攔截
implements WebMvcConfigurer //配置,注冊攔截器
void addInterceptors //重寫方法,可添加排除攔截的路徑
SpringCloud
1、SpringCloud的5大組件
注冊中心/配置中心 Nacos 服務(wù)網(wǎng)關(guān) Gateway 負載均衡 Ribbon 服務(wù)調(diào)用 Feign 服務(wù)保護 Sentinel
OpenFeign整合了Ribbon+RestTemplate
2、SpringCloud如何實現(xiàn)服務(wù)的注冊發(fā)現(xiàn)
服務(wù)注冊: 服務(wù)提供者需要把自己的信息注冊到nacos,由nacos保存這些信息,如:ip、服務(wù)名稱、端口服務(wù)發(fā)現(xiàn): 服務(wù)消費者向nacos拉取服務(wù)列表信息,如果服務(wù)提供者有集群,會使用負載均衡算法,選擇一個發(fā)起調(diào)用服務(wù)監(jiān)控 服務(wù)提供者會向nacos發(fā)送心跳,如果90秒沒收到心跳,則服務(wù)從nacos剔除
3、負載均衡如何實現(xiàn)
Nginx 服務(wù)器負載均衡
Ribbon 本地負載均衡
遠程調(diào)用的feign會使用Ribbon實現(xiàn)負載均衡
負載均衡策略
輪詢、權(quán)重,響應(yīng)時間長、權(quán)重小隨機,區(qū)域敏感策略(默認) 以區(qū)域可用服務(wù)器為基礎(chǔ)做服務(wù)器選擇
4、服務(wù)雪崩(降級熔斷)
服務(wù)互相調(diào)用,一個服務(wù)失敗,可能導(dǎo)致整條鏈路服務(wù)失敗解決
服務(wù)降級 當(dāng)服務(wù)調(diào)用不可用時,服務(wù)自我保護機制,確保服務(wù)不會崩潰(不在拋異常,而是返回給用戶消息) 在Feign接口中fallback編寫降級邏輯(處理服務(wù)調(diào)用失敗,如返回網(wǎng)絡(luò)連接失?。┓?wù)熔斷 如果10秒內(nèi)請求失敗率超過50%,觸發(fā)熔斷機制 之后每隔5秒重新嘗試請求,失敗則繼續(xù)熔斷、成功則恢復(fù)正常請求 默認關(guān)閉,需要手動開啟
5、限流
Nginx限流 控制速率,漏桶算法,固定速率處理請求,可應(yīng)對突發(fā)流量 控制并發(fā)數(shù),限制單個ip的連接數(shù)網(wǎng)關(guān)限流 令牌桶算法,可根據(jù)IP或路徑限流,可設(shè)置每秒填充速率,和令牌桶總?cè)萘?每秒生成令牌,每個令牌對應(yīng)一個請求,可提前生成令牌
6、分布式事務(wù)
分布式系統(tǒng)中,多個業(yè)務(wù)必須同時成功或失敗,叫分布式事務(wù)。
CAP和BASE
CAP(一致性、可用性、分區(qū)容錯性)
AP(可用性、分區(qū))CP(一致性,分區(qū)) BASE理論
基本可用軟狀態(tài)最終一致性
解決分布式事務(wù)思想和模型
強一致性
各個分支事務(wù)執(zhí)行完不要提交,等待彼此結(jié)果,而后統(tǒng)一提交(CP) seata的XA模式, 事務(wù)協(xié)調(diào)者TC統(tǒng)一提交事務(wù)
最終一致性
各個分支事務(wù)分別提交執(zhí)行,如有不一致,再想辦法恢復(fù)(AP) seata的AT模式, 事務(wù)協(xié)調(diào)者TC檢查各個事務(wù)是否有失敗的,失敗則undo-log回滾
seata
TC:Transaction Coordinator 事務(wù)協(xié)調(diào)者,Seata,只有一個
TM:Transaction Manage 事務(wù)管理者,標(biāo)注全局@GlobalTransactional,啟動入口動作的微服務(wù)模塊,只有一個 標(biāo)注@GlobalTransactional的service,既是TM,也是RM
RM:Rescorce Manage 資源管理器,mysql數(shù)據(jù)庫本身,可以多個RM
7、接口的冪等性問題
冪等:多次訪問不會改變業(yè)務(wù)的狀態(tài),保證多次調(diào)用結(jié)果一致性
get、delete 是冪等的post 新增 不是冪等的put 更新 以絕對值更新是冪等的;增量更新則不是冪等的
解決
分布式鎖,效率低token + redis
第一次請求,獲取token,存入redis中,返回給前端第二次請求,帶token業(yè)務(wù)處理。 token存在則執(zhí)行業(yè)務(wù),并刪除redis中的token token不存在,則不處理業(yè)務(wù)
RabbitMQ
RabbitMQ模式
簡單模式輪詢模式發(fā)布確定模式
生產(chǎn)者
RabbitTemplate.convertAndSend(),可進行消息發(fā)布,指定交換機、RoutingKey、消息對象
RabbitTemplate.setReturnsCallback,生產(chǎn)者確認,發(fā)消息給交換機確認是否收到,并處理
消費者
@RabbitListener注解,可聲明隊列、交換機、BindingKey
exchange=@Exchange(delayed=“true”),使用延時消息插件,設(shè)置交換機類型為死信交換機
交換機類型
Fanout(廣播) 將接收到的所有消息廣播到它知道的所有隊列中。 Direct(定向) 交換機根據(jù)接收消息的規(guī)則路由到指定隊列。 發(fā)布者:指定消息的RoutingKey 消費者(隊列):設(shè)置一個與Exchange交換機綁定的BindingKey Topics(話題) 與Direct類似,區(qū)別是RoutingKey可以是多個單詞的列表,并以 . 分割
消息可靠性
生產(chǎn)者可靠性
生產(chǎn)者重連 由于網(wǎng)絡(luò)波動,連接失敗時重連??膳渲弥剡B機制 阻塞重連,會影響性能,建議禁用重試 生產(chǎn)者確認機制 生產(chǎn)者投遞消息到MQ 成功返回ACK:臨時消息(未持久化隊列)入隊成功;持久消息入隊成功且持久化成功 失敗返回NACK 缺點:會增加系統(tǒng)負擔(dān),對可靠性要求嚴(yán)格可使用確認機制,否則可不用 MQ可靠性 MQ消息保存在內(nèi)存中,如果MQ宕機,消息會丟失 內(nèi)存空間有限,可能會導(dǎo)致消息積壓、消息阻塞
數(shù)據(jù)持久化 交換機持久化、隊列持久化、消息持久化。重啟后依然存在,不會發(fā)生消息阻塞 缺點:性能比非持久化隊列差點,因為會寫數(shù)據(jù)進磁盤Lazy Queue惰性隊列 接受消息后直接存入磁盤而非內(nèi)存(內(nèi)存只保留最近消息) 消費者要消費數(shù)據(jù)才從磁盤讀取 3.12版本后,所有隊列都是Lazy Queue模式,無法更改 消費者可靠性
消費者確認機制 spring確認消息是否接收成功,返回消息如下
ack:消息處理成功,MQ從隊列中刪除消息nack:消息處理失敗,MQ需要再次投遞消息(出現(xiàn)運行時異常)reject:消息處理失敗并拒絕,MQ從隊列中刪除消息(拋出消息轉(zhuǎn)換異常) SpringAMQP已經(jīng)實現(xiàn)消息確認,在yml中配置 none:不處理,會丟失消息。manual:手動模式。auto:自動模式(可設(shè)置) 缺點:返回nack,會無限重新進入隊列,不斷的發(fā)給消費者,無限循環(huán) 解決:消息失敗處理 消息失敗處理 retry配置,出現(xiàn)異常時,開啟本地重試,而不是無限重新入隊。 MessageRecover接口的三種實現(xiàn)
重試耗盡后,直接reject(默認)重試耗盡后,返回nack,重新入隊重試耗盡后,將失敗消息投遞給指定交換機(編寫一個處理失敗的交換機)
消息重復(fù)消費如何解決?
與接口的冪等性問題類似
冪等:多次訪問不會改變業(yè)務(wù)的狀態(tài),保證多次調(diào)用結(jié)果一致性
get、delete 是冪等的post 新增 不是冪等的put 更新 以絕對值更新是冪等的;增量更新則不是冪等的
解決
token + redis
第一次請求,獲取token,存入redis中,返回給前端第二次請求,帶token業(yè)務(wù)處理。 token存在則執(zhí)行業(yè)務(wù),并刪除redis中的token token不存在,則不處理業(yè)務(wù)
延時消息
應(yīng)用:超時訂單,取消超時訂單的業(yè)務(wù)
死信交換機
變成死信條件,隊列設(shè)置dead-letter-exchange屬性綁定死信交換機
reject 或 nack消費失敗,并重新入隊參數(shù)為false消息是過期消息(隊列或消息本身設(shè)置了過期時間,無人消費)投遞的隊列消息堆積滿了,最早的消息可能成為死信
消息堆積解決?
增加更多消費者消費者內(nèi)開啟線程池MQ消息持久化、惰性隊列
柚子快報邀請碼778899分享:開發(fā)語言 java八股文面試
相關(guān)閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。