一、Java基礎
1.集合(數據結構:數組 、鏈表、棧和隊列、二叉樹、堆和堆棧、散列表、紅黑樹)
1.1 List:元素按進入先後有序保存,可重複。
(1)ArrayList: 底層數據結構是數組,查詢快,增刪慢,線程不安全,效率高,可存貯重複元素;
(2)LinkList: 底層數據結構是鏈表,查詢慢,增刪快,線程不安全,效率高,可存貯重複元素;
(3)Vector: 底層數據結構是數組,查詢快,增刪慢,線程安全,效率低,可存貯重複元素;
1.2 Set: 僅接收一次,不可重複,並做內部排序,唯一性需重寫hashcode和equals方法。
(1) HashSet: 底層數據結構採用哈希表實現,元素無序且唯一,線程不安全,效率高;
(2) ListHashSet: 底層數據結構鏈表+hash,有序且唯一,線程不安全,效率高;
(3) TreeSet: 底層數據結構採用二叉樹實現,有序且唯一;
1.3 Map:
(1) HashMap: 非線程安全
a. 底層數據結構: 基於hash表, 數組+ 鏈表(jdk1.8加入紅黑樹);當鏈表的長度 >= 8的時候,將鏈表轉換成紅黑樹;
紅黑樹數量 <= 6,紅黑樹轉換爲鏈表;
b. HashMap主幹是一個靜態內部類Entry,包含key,value,next, hash;
c. 初始化容量initialCapacity默認16,加載因子默認0.75, 實際容量 = 負載因子 x 容量,也就是 12 = 0.75 x 16;
d. 數組長度一定是2的次冪, index = hashCode("xxx") & (Length - 1) 取模運算時方便&二進制中
的與運算, 減少hash衝突;
e. Hash衝突:一種是開放尋址法,另一種是鏈表法
兩個節點的key值相同(hash值一定相同),導致衝突
兩個節點的key值不同,由於hash函數的侷限性導致hash值相同,導致衝突
兩個節點的key值不同,hash值不同,但hash值對數組長度取模後相同,導致衝突
解決hash衝突:
鏈表法也正是被應用HashMap中,每一個Entry對象通過next指針指向它的下一個Entry節點。當新來的Entry映射到與之衝突的數組位置時,只需要插入到對應的鏈表中即可。
f. 允許使用 null 做爲值(key)和鍵(value)
g. 解決線程安全問題:
Map<String, Integer> map = Collections.synchronizedMap(hashMap)
(2) 內部數據結構
- 判斷數組是否爲空,爲空進行初始化;
- 不爲空,計算 k 的 hash 值,通過(n - 1) & hash計算應當存放在數組中的下標 index;
- 查看 table[index] 是否存在數據,沒有數據就構造一個Node節點存放在 table[index] 中;
- 存在數據,說明發生了hash衝突(存在二個節點key的hash值一樣), 繼續判斷key是否相等,相等,用新的value替換原數據(onlyIfAbsent爲false);
- 如果不相等,判斷當前節點類型是不是樹型節點,如果是樹型節點,創造樹型節點插入紅黑樹中;
- 如果不是樹型節點,創建普通Node加入鏈表中;判斷鏈表長度是否大於 8, 大於的話鏈表轉換爲紅黑樹;
- 插入完成之後判斷當前節點數是否大於閾值,如果大於開始擴容爲原數組的二倍。
(3) HashTable: 線程安全,加入synchronized關鍵字
(4) HashMap 底層數據數組原因:
1)數組效率高
在HashMap中,定位桶的位置是利用元素的key的哈希值對數組長度取模得到。此時,我們已得到桶的位置。顯然數組的查找效率比LinkedList大。
2)可自定義擴容機制
採用基本數組結構,擴容機制可以自己定義,HashMap中數組擴容剛好是2的次冪,在做取模運算的效率高。
(5)HashMap死鎖原因及替代方案
HashMap是非線程安全,在併發的時候,容量不足,擴容遷移到新的Hash表中(refresh),新線程進來也refresh),形成環形鏈表就會陷入死循環。
解決方案:HashTable也是線程安全,但HashTable鎖定的是整個Hash表,效率相對比較低。使用ConcurrentHashMap進行替代。jdk1.8中的實現已經拋棄了Segment分段鎖機制,利用CAS+Synchronized來保證併發更新的安全。其中value和next都用volatile修飾,保證併發的可見性。
2.多線程
2.1 多線程狀態:
線程在一定條件下,狀態會發生變化。線程變化的狀態轉換圖如下:
(1)、新建狀態(New):新創建了一個線程對象。
(2)、就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
(3)、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
(4)、阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
(5)、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
2.2 線程調度:
- a. sleep 線程轉到阻塞狀態,結束後轉爲就緒;
- b. wait 當前線程等待,notify或notifyAll喚醒;
- c. yield 暫定當前正在執行線程對象,讓相同或者優先級高的線程執行;
- d. join 等待其他線程終止,當前線程調用另一個線程join,當前線程轉入阻塞,直到另一個線程結束,當前線程阻塞轉爲就緒,如多個線程統計累加數量。
2.3 併發編程三大特性:原子性、可見性、有序性
(1) Volatile保證變量可見性與有序性,但不能保證原子性,原子性要藉助synchronized這樣的鎖機制。
(2)JMM內存模型
所有的變量都存儲在主內存中, 總線MESI緩存一致性協議,store寫回內存操作,會使其他CPU緩存地址數據無效
(3) Synchronized關鍵字: 鎖是存在對象頭裏面。
- 可見性:可見性是指多個線程訪問一個資源時,該資源的狀態、值信息等對於其他線程都是可見的
- 原子性:原子性就是指一個操作或者多個操作,要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行(CAS全稱Compare And Swap,AtomicInteger保證變量賦值的原子性)
- 有序性:有序性值程序執行的順序按照代碼先後執行
- 可重入性:synchronized和ReentrantLock都是可重入鎖。一個線程擁有了鎖仍然還可以重複申請鎖,多個同步代碼塊(如:synchronized嵌套synchronized),可以重複申請鎖。(計數器記錄鎖)
a. 三種方式:
- A. 修飾普通方法: new出來的實例對象鎖;
- B. 修飾靜態方法: 其鎖就是當前類的class對象鎖;
- C. 修飾代碼塊: 鎖是括號裏面的對象;
b. synchronized底層原理: 是由一對monitorenter和monitorexit指令實現的,Monitor對象是同步的基本實現單元。
- 同步代碼塊是通過monitorenter和monitorexit來實現,當線程執行到monitorenter的時候要先獲得monitor鎖,才能執行後面的方法。當線程執行到monitorexit的時候則要釋放鎖。
- 同步方法是通過中設置ACC_SYNCHRONIZED標誌來實現,當線程執行有ACC_SYNCHRONI標誌的方法,需要獲得monitor鎖。
- 每個對象維護一個加鎖計數器,當前線程已經擁有了這個對象的鎖,那麼就把鎖的計數器加1;當執行monitorexit指令時,鎖的計數器也會減1,當爲0表示可以被其他線程獲得鎖,不爲0時,只有當前鎖的線程才能再次獲得鎖。
- 同步方法和同步代碼塊底層都是通過monitor來實現同步的。
- 每個對象都與一個monitor相關聯,線程可以佔有或者釋放monitor。
c. Synchronized鎖優化(性能優化,避免升級重量級鎖)
鎖一共有四種狀態,隨着競爭情況逐漸升級,級別從低到高依次爲:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。
- CAS 比較並替換(Compare and Swap),是原子操作的一種,可用於在多線程編程中實現不被打斷的數據交換操作。該操作通過將內存中的值與指定數據進行比較,當數值一樣時將內存中的數據替換爲新的值。
- 對象頭 對象頭是對象在內存中的其中一個部分,這個部分又分別由Mark Work和類型指針組成。
- Mark Word 用於存儲對象自身運行時的數據,包括hashcode、GC分代年齡、鎖狀態標記、偏向鎖ID、偏向時間戳等數據,Mark Work佔用的內存與虛擬機位長保持一致。
(4)線程池
a. Executors四種線程池:
- newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
- newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
- newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
- newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
b. 執行方法
- execute(Runnable): 接收的是一個Runnable實例,並異步執行,run()方法是void,沒有返回值;
- submit(Runnable): 和execute一樣,只是會返回一個Future對象,可以檢測任務是否執行完畢;
- submit(Callable): 接收的是一個Callable實例,Callable接口中的call()方法有返回值,可返回任務執行結果;
- invokeAny(...): 接收的是一個Callable的集合,不會返回Future,會返回任意一個執行結果;
- invokeAll(...): 和invokeAny一樣,返回Future的List;
(5) 死鎖: 指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象。
-
互斥條件:進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
-
請求和保持條件:進程獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他進程佔有,此事請求阻塞,但又對自己獲得的資源保持不放
-
不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
-
環路等待條件:是指進程發生死鎖後,若干進程之間形成一種頭尾相接的循環等待資源關係、
(6) ThreadLocal
線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。
ThreadLocal支持線程局部變量,是一種實現線程安全的方式。
(7) synchronized 和 Lock 有什麼區別?
-
首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
-
synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖(tryLock);
-
synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
-
用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
-
synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
-
Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
(8) synchronized 和 ReentrantLock 區別是什麼?
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
-
ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
-
ReentrantLock可以獲取各種鎖的信息
-
ReentrantLock可以靈活地實現多路通知
3.設計模式
(1) 設計原則:
- 單一職責
- 里氏置換原則
- 依賴倒置原則
- 接口隔離原則
- 迪米特原則
- 開閉原則
4.JVM
對象包含對象頭、實例數據和填充數據三部分。
4.1 JVM構成:字節碼命令:javap -v Test.class
4.2 五大內存區域
(1) 程序計數器:當前線程的行號指標號,記錄虛擬機字節碼指令的位置。
(2) 本地方法棧:使用native方法服務的,底層調用c或者c++;
(3) 棧:先進後出(FILO), 棧幀是一種數據結構, 入棧和出棧的一個單元。
- 局部變量表:存放方法參數和方法內部定義的局部變量。
- 操作數棧:把局部變量表裏的數據、實例的字段等數據入棧。(字節碼指令,壓入棧)
- 動態鏈接:方法之間調用,符號引用和直接引用在運行時進行解析和鏈接的過程。
- 方法出口:方法調用完成,返回給調用方法的值。
(4) 堆:
a. 堆內存分爲年輕代、老年代
b. 年輕代分爲Eden(伊甸園區)和Survivor,Survivor又分爲FromSpace和ToSpace。默認比例Eden:S0:S1=8:1:1。
c. Eden存放new出來的新對象,Eden內存滿了,會進行minor GC, 每經歷一次GC年齡+1,默認年齡閾值到達15, 進入老年代。
(5) 方法區(非堆內存):也稱元空間(Metaspace), jdk1.8以前叫永久代(已廢棄),存儲程序運行時長期存活的對象,比如類的元 數據、方法、常量、屬性等。
- MetaspaceSize :初始化元空間大小,控制發生GC閾值。
- MaxMetaspaceSize : 限制元空間大小上限,防止異常佔用過多物理內存。
4.3 JVM 調優:減少fullgc次數和時間
1. 三大GC收集算法:效率:複製算法 > 標記/整理算法 > 標記/清除算法
- 標記/清除算法(最基礎):老年代GC算法)
- 標記階段:對象的header中, 遍歷所有的GC Roots對象, 可達的對象(可達性分析算法)都打上一個標識。
- 清除階段:遍歷堆內存,發現某個對象沒有被標記爲可達對象(通過讀取對象header信息),則將其收回。
- 複製算法:(新生代GC算法) 將可用內存按容量劃分爲大小相等的兩塊,每次使用其中的一塊。當這一塊的內存用完了,就將還存活的對象複製到另一塊內存上,然後把這一塊內存所有的對象一次性清理掉。Eden區中所有存活的對象都會被複制到To Survivor區,而在From Survivor區中,仍存活的對象會根據它們的年齡值決定去向,年齡值達到年齡閥值(默認爲15,新生代中的對象每熬過一輪垃圾回收,年齡值就加1)的對象會被移到老年代中。
- 標記整理算法: (老年代GC算法)跟標標記清除算法類似,讓所有存活的對象都向一端移動,然後直接清理掉端邊線以外的內存。
2.調優工具:jconsole、VisualVM、jstat
JAVA內存管理機制及配置參數:
JAVA_OPTS="-server -Xms512m -Xmx2g -XX:+UseG1GC -XX:SurvivorRatio=6 -XX:MaxGCPauseMillis=400 -XX:G1ReservePercent=15 -XX:ParallelGCThreads=4 -XX:
ConcGCThreads=1 -XX:InitiatingHeapOccupancyPercent=40 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:../logs/gc.log"
- 設置堆內存最小和最大值,最大值參考歷史利用率設置
- 設置GC垃圾收集器爲G1
- 啓用GC日誌,方便後期分析,idea配置參數(-XX:+PrintGCDetails)
3.垃圾收集器
- 串行收集器(Serial)
比較老的收集器,單線程。收集時,必須暫停應用的工作線程,直到收集結束。 - 並行收集器(Parallel)
多條垃圾收集線程並行工作,在多核CPU下效率更高,應用線程仍然處於等待狀態。 - CMS收集器(Concurrent Mark Sweep)
CMS收集器是縮短暫停應用時間爲目標而設計的,是基於標記-清除算法實現,整個過程分爲4個步驟,包括:- 初始標記(Initial Mark)
- 併發標記(Concurrent Mark)
- 重新標記(Remark)
- 併發清除(Concurrent Sweep)
初始標記、重新標記這兩個步驟仍然需要暫停應用線程(stop the world)
4.內存溢出(OOM)
- 老年代內存不足:java.lang.OutOfMemoryError:Javaheapspace
- 永久代內存不足:java.lang.OutOfMemoryError:PermGenspace
- 代碼bug,佔用內存無法及時回收。
5. jdk1.8特性:
Lambda實現原理: 匿名內部類,
- lambda$main$0() 私有靜態方法的生成
- ResourceAnalysis$$Lambda$1.class 反編譯類
- 發現 Lambda表達式,最終的實現還是基於匿名內部類的方式
- 調用lambda方法時使用了
invokedynamic(
動態調用點)
Stream原理:
1. 生成並構造一個流 (List.stream() 等方法)
2. 中間操作:在流的處理過程中添加、綁定惰性求值流程 (map、filter、limit 等方法)
3. 結束操作:對流使用強制求值函數,生成最終結果 (max、collect、forEach等方法)
Stream調試工具:
idea版本已經集成了Java Stream Debugger。
二、Spring
1.Spring IOC(反射機制)
(1) 原理
- 依賴注入:通過諸如實例化對象,替代new對象,由Spring 容器進行管理,解耦。
- 控制反轉:對象創建權交給是spring容器。
- 注入三種方式:構造方法注入,setter注入,基於註解的注入。
- 自動裝配有三種模式:byType(類型模式),byName(名稱模式)、constructor(構造函數模式)。
(2) bean作用域
- singleton:單例模式,每次請求該Bean都將獲得相同實例。(Spring默認作用域)
- prototype:原型模式,每次請求該Bean,Spring都會新建一個Bean實例。
- request:每次http請求產生一個新實例,只有在web應用中使用該作用域纔有效。
- session:每次http session請求產生一個新實例。
(3) bean的生命週期
- 實例化 Instantiation
- 屬性賦值 Populate
- 初始化 Initialization
- 銷燬 Destruction
主要邏輯都在doCreate()中,按順序調用一下三個方法:
- eateBeanInstance() -> 實例化
- populateBean() -> 屬性賦值
- initializeBean() -> 初始化
2.Spring AOP
(1) 面向切面編程,主要是日誌記錄、權限認證、安全控制、事務處理、異常處理。
(2) 底層原理:使用代理模式
- JDK動態代理:必須是面向接口,目標業務類必須實現接口。通過反射代理目標信息,InvokeHandler來處理。
- CGLIB動態代理:利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
如果目標對象實現了接口,默認使用JDK動態代理,也可以使用CGLIB代理;如果目標對象沒有實現接口,則必須實現CGLIB。
(3) AOP註解方式:
@Aspect 註釋修飾,聲明爲一個切面類。@Component註解註釋爲一個bean對象。
@Pointcut:定義切點。
@Around:環繞通知("execution(public * com.xxx.controller..*.*(..))")。
(4) 過濾器和攔截器
- Filter:依賴Servlet容器,基於函數回調,filter一般實現統一設置編碼、用戶是否登錄、權限訪問等。攔截web訪問url地址。
- Interceptor:攔截器基於Java反射,使用代理模式。可以繼承HandlerInterceptorAdapter類。攔截Action的訪問。
3.Spring MVC
(1) 流程圖
(2) 運行原理:
- 客戶端發起Http請求到DispatcherServlet
- DispatcherServlet控制器尋找HanderMapping, 查詢具體的Handler
- HanderMapping調用HandlerAdapter執行handler
- HandlerAdapter經過適配調用具體Controller,處理業務邏輯
- Controller處理完成將ModelAndView返回DispatcherServlet
- DispatcherServlet請求視圖解析器ViewReslover解析ModelAndView
4.Spring 事務
(1) 四個原則:
- 原子性:一個事務要麼全部執行,要麼不執行
- 一致性:事務執行前後數據處於正確狀態
- 隔離性:併發事務之間互不影響
- 持久性:事務一旦執行成功,數據永久存在
(2) 事務分類:
- 編程式事務:在業務邏輯中自行實現事務
- 聲明式事務:通過XML配置或者註解@Transactional實現
(3) 隔離級別
- ISOLATION_DEFAULT:用數據庫默認隔離級別
- ISOLATION_READ_UNCOMMITTED(未提交讀):最低隔離級別,事務未提交,可被其他事務讀取(幻讀、髒讀、不可重複讀)
- ISOLATION_READ_COMMITTED(提交讀):一個事務修改的數據提交後才能被其他事務讀取(幻讀、不能重複讀)(Oracle默認級別)
- ISOLATION_REPEATABLE_READ(可重複讀):保證多次讀取同一數據是一致(幻讀)(MySql默認級別)
- ISOLATION_SERIALIZABLE(序列化):代價最高最可靠的隔離級別
(4) 事務傳播性:
- PROPAGATION_REQUIRED:支持當前事務,如果當前沒有事務,就新建一個事務。(Spring 默認)
- PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
- PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
- PROPAGATION_MANDATORY:支持當前事務,如果當前沒有事務,就拋出異常
- PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
- PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
- PROPAGATION_NESTED:如果一個活動的事務存在,則運行在一個嵌套的事務中。
4.Mybatis
(1) 封裝JDBC操作,利用反射打通Java類與SQL語句之間的相互轉換
(2) 工作原理:
- 讀取 MyBatis 配置文件:mybatis-config.xml
- 加載映射文件。映射文件即 SQL 映射文件
- 通過配置信息構建會話工廠 SqlSessionFactory
- 由會話工廠創建 SqlSession 對象, 包含了執行 SQL 語句的所有方法
- Executor 執行器:在 Executor 接口的執行方法中有一個 MappedStatement 類型的參數
- 輸入參數映射:輸入參數類型可以是 Map、List 等集合類型,也可以是基本數據類型和 POJO 類型
- 輸出結果映射:輸出結果類型可以是 Map、 List 等集合類型,也可以是基本數據類型和 POJO 類型
(3)mybaits中#和$的區別
- #{}:佔位符號,可以防止sql注入(替換結果會增加單引號‘’)
- ${}:sql拼接符號(替換結果不會增加單引號‘’,like和order by後使用,存在sql注入問題,需手動代碼中過濾)表名作爲變量時,必須使用 ${ }
5. Hibernate
(1) Hibernate緩存:
- Hibernate一級緩存: 一級緩存又稱爲“Session的緩存”, 不可以取消session緩存。
- Hibernate二級緩存: 默認不會開啓
» 很少被修改的數據
» 不是很重要的數據,允許出現偶爾併發的數據
» 不會被併發訪問的數據
» 常量數據
(2) 緩存配置
在默認情況下,Hibernate會使用EHCache作爲二級緩存組件。
<!-- a. 開啓二級緩存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- b. 指定使用哪一個緩存框架(默認提供的) -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
<!-- 開啓查詢緩存 -->
<property name="hibernate.cache.use_query_cache">true</property>
(3) 緩存算法,常見有三種:
● LRU:(Least Rencently Used)新來的對象替換掉使用時間算最近很少使用的對象
● LFU:(Least Frequently Used)替換掉按命中率高低算比較低的對象
● LFU:(First In First Out)把最早進入二級緩存的對象替換掉
(4) 二級緩存策略
● READ_ONLY:實體只讀緩存(常用)
● NONSTRICT_READ_WRITE:實體非嚴格讀/寫緩存
允許更新,更新後緩存失效,需再查詢一次。
允許新增,新增記錄自動加到二級緩存中。
整個過程不加鎖。
● READ_WRITE:實體讀/寫緩存
允許更新,更新後自動同步到緩存。
允許新增,新增記錄後自動同步到緩存。
保證read committed隔離級別及可重複讀隔離級別(通過時間戳實現)
整個過程加鎖,如果當前事務的時間戳早於二級緩存中的條目的時間戳,說明該條目已經被別的
事務修改了,此時重新查詢一次數據庫,否則才使用緩存數據,因此保證可重複讀隔離級別。
讀寫緩存和不嚴格讀寫緩存在實現上的區別在於,讀寫緩存更新緩存的時候會把緩存裏面的數據換成一個鎖
● TRANSACTIONAL:實體事務緩存
緩存支持事務,發生異常的時候,緩存也能夠回滾,只支持jta環境
● Collection集合緩存
(5)查詢緩存
- 查詢緩存緩存的不是對象而是 id
- 只有當HQL 完全相同時,包括參數設置都相同時,查詢緩存纔會起效
三、Spring Boot/Spring Cloud
1、原理
Eureka:註冊中心,心跳機制,30時間s,最長時間90s。底層是ConcurrentHashMap實現數據存取,有多層緩存機制,讀緩存,寫緩存等多級。
2、Eureka服務註冊與發現
(1) Euraka Server:註冊中心服務端
- 服務註冊: 服務提供者啓動時,通過Euraka Client向Euraka Server註冊元數據(IP 地址、端口、狀態等信息)。Eureka採用的是ConcurrentHashMap來存儲註冊表信息,使用二層緩存機制來維護整個註冊表。
- 提供註冊表: 服務消費者調用服務,若本地沒有,會去服務端Euraka Server拉取存放本地。
(2) Euraka Client:Eureka Client 會拉取、更新和緩存 Eureka Server 中的信息。
(3) Renew: 服務續約,Eureka Client 會每隔 30 秒發送一次心跳來檢測續約,告知 Eureka Server 該 Eureka Client 運行正常。
如果 Eureka Server 在 90 秒內沒有收到 Eureka Client 的續約,Server 端會將實例從其註冊表中刪除。
(4) 自我保護機制:
Eureka Server 在運行期間會去統計心跳失敗比例在 15 分鐘之內是否低於 85%,如果低於 85%,Eureka Server 即會進入自我保護機制。
(5) Eureka集羣:
Eureka Server 高可用集羣,集羣相互之間通過 Replicate(複製)來同步數據,不區分主從,如果某臺Euraka Server宕機,Euraka Client的請求會自動切換新的Eureka Server。
Eureka 提供了 Region(地址位置的區域)和 Zone(具體機房) 兩個概念來進行分區。
(6) 工作流程:
- Eureka Server 啓動成功,等待服務端註冊。在啓動過程中如果配置了集羣,集羣之間定時通過 Replicate 同步註冊表,每個 Eureka Server 都存在獨立完整的服務註冊表信息
- Eureka Client 啓動時根據配置的 Eureka Server 地址去註冊中心註冊服務
- Eureka Client 會每 30s 向 Eureka Server 發送一次心跳請求,證明客戶端服務正常
- 當 Eureka Server 90s 內沒有收到 Eureka Client 的心跳,註冊中心則認爲該節點失效,會註銷該實例
- 單位時間內 Eureka Server 統計到有大量的 Eureka Client 沒有上送心跳,則認爲可能爲網絡異常,進入自我保護機制,不再剔除沒有上送心跳的客戶端
- 當 Eureka Client 心跳請求恢復正常之後,Eureka Server 自動退出自我保護模式
- Eureka Client 定時全量或者增量從註冊中心獲取服務註冊表,並且將獲取到的信息緩存到本地
- 服務調用時,Eureka Client 會先從本地緩存找尋調取的服務。如果獲取不到,先從註冊中心刷新註冊表,再同步到本地緩存
- Eureka Client 獲取到目標服務器信息,發起服務調用
- Eureka Client 程序關閉時向 Eureka Server 發送取消請求,Eureka Server 將實例從註冊表中刪除
3、ribbon客服端負載均衡
實現客戶端負載均衡,可以自定義Ribbon Client, 也可以yml中配置負載的策略(隨機、輪詢、權重),默認是輪詢。
4、hystrix熔斷
(1) 服務雪崩效應:分佈式系統中經常會出現某個基礎服務不可用造成整個系統不可用的情況(如:服務器宕機、緩存擊穿、程序bug等)
(2) 服務之間相互調用,分佈式環境中經常出現某個服務節點故障,調用失敗。熔斷器就是當服務調用失敗時候提供保障服務正常運行不被卡死。@HystrixCommand(fallbackMethod = "fallback")
(3) 斷路器機制:hystrix存在三種狀態:CLOSED、OPEN、HALF_OPEN。默認closed時,服務在一定時間內(10s) 請求次數達到了某個閥值(20次),並且錯誤率也達到了某個閥值(>50%)此時斷路器變成了OPEN的狀態, 過了一定的時間(5s)將會放行一個請求,此時變成HALF_OPEN狀態,如果可以訪問就變成CLOSED否則變成OPEN狀態。
5、feign 基於Ribbon和Hystrix的聲明式服務調用組件
JAX-RS 注意不能使用GetMapping不支持,PathVariable需設置value
6、zuul網關(通過過濾器來實現)
生命週期:
- pre Filter:這種過濾器再請求被路由之前調用。如:認證鑑權、限流等。
- route:將請求路由到微服務,構建發送給微服務的請求,HttpClient或Ribbon請求微服務。
- post:在路由到微服務執行之後執行,返回結果或者發生異常後執行的filter。
- error:發生異常,執行改filter。
7、Springcloud config 配置中心(實現方式是git管理配置)
- 環境部署之前,將所需的配置信息推送到配置倉庫
- 啓動配置中心服務端,將配置倉庫的配置信息拉取到服務端,配置服務端對外提供REST接口
- 啓動配置客戶端,客戶端根據 spring.cloud.config 配置的信息去服務器拉取相應的配置
8、zipkin服務追蹤
分佈式服務追蹤,收集來自各個系統的監控數據。
- Collector 接受或者收集各個應用傳輸的數據
- Storage:負責存儲接收到的數據,默認是存儲在內存當中的,也可以支持存在MySQL當中
- API:負責查詢Storage中存儲的數據,主要是提供給Web UI來使用
- Web:主要是提供簡單的web界面
四、redis
1、數據結構
redis是一個key-value存儲系統,常見數據結構:
- String:常用命令(set,get,decr,incr,mget)value可以是String,也可以是數字。常規計數:微博數,粉絲數等
- Hash:常用命令(hget,hset,hgetall)跟map一樣,適合用於存儲對象,如:用戶信息,商品信息等。
- List:常用命令(lpush,rpush,lpop,rpop,lrange)list就是雙向鏈表,即可以支持反向查找和遍歷。如:微博粉絲列表、最小消息排行等。
- Set:常用命令(sadd,spop,smembers,sunion)與list類似是一個列表的功能,可以自動去重複數據。如:共同好友、好友推薦。
-
Sorted Set:常用命令(zadd,zrange,zrem,zcard)和set相比,sorted set增加了一個權重參數score,能夠按score進行有序排列。如:禮物排行榜,彈幕消息。
-
hyperloglog:統計頁面訪問量等數據。
-
GEO:用於處理地理位置的信息,可以保存地理位置,可以計算地理位置的距離。
2、分佈式鎖
(1) 原理
- 加鎖:(setnx) key設置一個值(SET lock_key random_value NX PX 5000)
- 解鎖:刪除key 先判斷當前鎖的字符串random_value是否與傳入的值相等,是的話就刪除Key,解鎖成功。
- 鎖超時:避免死鎖,給定一個過期時間。
(2) redisson實現分佈式鎖
- 原子性,lua腳本發送給redis。
- watch dog自動延期機制,後臺守護線程,每隔10s檢查。
3、緩存:數據保存在內存,存取速度快,併發能力強(mybatis二級緩存)
持久化方式:(開機的時候掃描持久化文件)
- RDB:RDB 持久化可以在指定的時間間隔內生成數據集的時間點快照
- AOF:AOF 持久化記錄服務器執行的所有寫操作命令
4、緩存失效
1. 緩存過了失效時間,緩存被刪除,需要更新緩存。
-
互斥鎖:發起請求,將緩存中該數據上鎖。無緩存,則等待數據庫查詢,並更新緩存。
-
設置不同的失效時間:緩存失效時間錯開,加個隨機數等。
2.redis內存不足時的策略
(1)noeviction: 拒絕寫操作, 讀、刪除可以正常使用。默認策略,不建議使用;
(2)allkeys-lru: 移除最近最少使用的key,最常用的策略;
(3)allkeys-random:隨機刪除某個key,不建議使用;
(4)volatile-lru:在設置了過期時間的key中,移除最近最少使用的key,不建議使用;
(5)volatile-random:在設置了過期時間的key中,隨機刪除某個key,不建議使用;
(6)volatile-ttl: 在設置了過期時間的key中,把最早要過期的key優先刪除。
5、緩存雪崩
(1) 雪崩:網絡不穩定或服務器宕機,服務調用者阻塞,引發雪崩連鎖效應。
(2) 緩存雪崩:當緩存服務器重啓或者大量緩存集中在某一個時間段失效,直接訪問數據庫,數據庫宕機,從而造成系統的崩潰。
(3) 雪崩解決方案:
- 熔斷模式:使用Hystrix(熔斷、降級、限流)一旦發現當前服務的請求失敗率達到預設的值,Hystrix將會拒絕隨後該服務的所有請求,直接返回一個預設的結果。這就是所謂的“熔斷”。
- 隔離模式:緩存失效後,通過枷鎖,對某個key只允許一個線程查詢數據和寫緩存。
- 限流模式:對請求設置最高的QPS閾值,高於閾值直接返回。
6、緩存穿透
一般緩存系統,都是按照key去緩存查詢數據,如果不存在對應的value,就去數據庫查詢。頻繁訪問數據庫,這就叫緩存穿透。
1.緩存空數據:查詢結果爲空的key也存儲在緩存中,該key的查詢請求時,緩存直接返回null,而無需查詢數據庫。
2.BloomFilter(推薦):bloomFilter就類似一個hashset(bigmap),判斷某個key是否存在,不存在直接返回null。
7、集羣(高可用、可擴展性、分佈式、容錯)
- 主從複製
將Master(讀寫操作)的數據自動同步到Slave數據庫。
實現原理:
- 從服務器向主服務器發送SYNC命令
- 主服務器收到SYNC命令後,執行BGSAVE命令,在後臺生成RDB文件,使用緩衝區記錄從現在開始執行的所有的寫命令。
- 當主服務器的BGSAVE命令執行完畢後,主服務器後將BGSAVE命令生成的RDB文件發送給從服務器,從服務器接收並載入這個RDB文件,將自己的數據庫狀態更新至主服務器執行BGSAVE命令時的數據庫狀態。
- 主服務器將記錄在緩衝區裏面的所有寫命令發送給從服務器,從服務器執行這些寫命令,將自己的數據庫狀態更新至主服務器數據庫當前所處的狀態。
缺點:master 宕機需要手動將slave提升爲master。
- 哨兵模式
Redis Sentinel(哨兵):節點數量要滿足 2n+1奇數個。
Sentinel功能:
- 監控主從數據庫是否正常運行
- master出現故障時,自動將slave轉化爲master
- 多哨兵配置的時候,哨兵之間也會自動監控
- 多個哨兵可以監控同一個redis
缺點:Redis較難支持在線擴容,選舉leader(slave轉化爲master)時候服務無法工作。
- Cluster集羣(3.0版本)推薦使用
(1) Redis Cluster是多個Redis主從節點,在分區期間還提供了一定程度的可用性。
(2) Redis集羣數據分片,分片採用slot(槽)的概念,一共分成16384個槽。對於每個進入Redis的鍵值對,每個key通過CRC16校驗,分配到這16384個slot中的某一箇中。
比如當前集羣有3個節點:(slots)默認是平均分配槽,推薦工具redis client
-
節點 A 包含 0 到 5500號哈希槽;
-
節點 B 包含5501 到 11000 號哈希槽;
-
節點 C 包含11001 到 16384號哈希槽;
(3) 擴容
-
增加節點的順序是先增加Master主節點,然後在增加Slave從節點
-
分配數據槽,從其他節點分配槽。
五、數據庫
1.mysql
1. InnoDB:存儲引擎,支持行鎖(B+Tree)
局部內存,按照頁取數據,每頁16kb,放在內存中 每行數據最多存65535個字節 佔位符3個
數據插入的時候是B+Tree聚集索引
按照主鍵創建索引,沒有使用唯一索引,沒有才使用rowId
2. myISAM:非聚集性索引
3. mysql explain 詳解
explain查看sql執行計劃,查看sql有沒有使用索引,有沒有全表掃描。
- id:選擇標識符
- select_type:表示查詢的類型。
- table:輸出結果集的表
- partitions:匹配的分區
- type:表示表的連接類型
- possible_keys:表示查詢時,可能使用的索引
- key:表示實際使用的索引
- key_len:索引字段的長度
- ref:列與索引的比較
- rows:掃描出的行數(估算的行數)
- filtered:按表條件過濾的行百分比
- Extra:執行情況的描述和說明
通過主從複製的方式來同步數據,再通過讀寫分離來提升數據庫的併發負載能力。
複製的工作過程
1) 在每個事務更新數據完成之前,master在二進制日誌記錄這些改變。寫入二進制日誌完成後,master通知存儲引擎提交事務。
2) Slave將master的binary log複製到其中繼日誌。首先slave開始一個工作線程(I/O),I/O線程在master上打開一個普通的連接,然後開始binlog dump process。binlog dump process從master的二進制日誌中讀取事件,如果已經跟上master,它會睡眠並等待master產生新的事件,I/O線程將這些事件寫入中繼日誌。
3) Sql slave thread(sql從線程)處理該過程的最後一步,sql線程從中繼日誌讀取事件,並重放其中的事件而更新slave數據,使其與master中的數據一致,只要該線程與I/O線程保持一致,中繼日誌通常會位於os緩存中,所以中繼日誌的開銷很小。
2. Sql優化
- sql避免全表掃描,避免 ‘ * ‘ 或 is null 或 != 或 <> 或 or 等查詢
- 儘量使用(NOT) EXISTS 替代( NOT)IN
- 通配符%,like '%c%'不會使用索引,而 like 'c%'會使用所有
- 合理的建立索引能夠加速數據讀取效率,不合理的建立索引反而會拖慢數據庫的響應速度
- 索引越多,更新數據的速度越慢
- 儘量用union all代替union
- 對於聯合索引來說,要遵守最左前綴法則,創建聯合索引的時候一定要注意索引字段順序,常用的查詢字段放在最前面。
左右連接:
left join (左連接):返回包括左表中的所有記錄和右表中連接字段相等的記錄。
right join (右連接):返回包括右表中的所有記錄和左表中連接字段相等的記錄。
inner join (等值連接或者叫內連接):只返回兩個表中連接字段相等的行。
full join (全外連接):返回左右表中所有的記錄和左右表中連接字段相等的記錄。
————————————————
版權聲明:本文爲CSDN博主「灰太狼_cxh」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_39220472/article/details/81193617
3. Oracle
(1) LATCH:Latch是用於保護SGA區數據結構的一種串行化鎖定機制,對內存數據結構提供互斥訪問的一種機制。
(2) Latch和Lock的區別:
- Latch是對內存數據結構提供互斥訪問的一種機制,而Lock是以不同的模式來套取共享資源對象,各個模式間存在着兼容或排斥,從這點看出,Latch的訪問,包括查詢也是互斥的,任何時候,只能有一個進程能pin住內存的某一塊,幸好這個過程是相當的短暫,否則系統性能將沒的保障,現在從9I開始,允許多個進程同時查詢相同的內存塊,但性能並沒有想象中的好。
- Latch只作用於內存中,他只能被當前實例訪問,而L ock作用於數據庫對象,在RAC體系中實例間允許Lock檢測與訪問
- Latch是瞬間的佔用,釋放,Lock的釋放需要等到事務正確的結束,他佔用的時間長短由事務大小決定
- Latch是非入隊的,而Lock是入隊的
- Latch不存在死鎖,而Lock中存在(死鎖在Oracle中是非常少見的)
(3) 按鎖的機制分類
排他鎖( X ):如果事務T對對象A加上排他鎖,則只允許T對A對象讀取和修改,其他事務不能對A增加任何鎖,直到T釋放加載A上的排他鎖
共享鎖( S ):如果事務T對錶A加上共享鎖,則事務T可以讀該表,但是不能修改該表數據。其他事務也只能對該對象加上共享鎖,但是不能加上排他鎖。這就保證了其他事務可以讀A表數據,但是不能修改A表數據(這就不會影響到讀該表的事務了,有利於併發操作)。
(4) 按操作行爲分類
- DML Locks 保證併發情況下的數據完整性
- Row Locks (TX) 稱爲行級鎖。當事務執行 DML 語句(INSERT, UPDATE, DELETE, and SELECT with the FOR UPDATE clause)時會申請TX 類型的鎖,TX 的機制是排他鎖。
- Table Locks (TM) 稱爲表級鎖。當Oracle執行 DML 語句時,系統自動在所要操作的表上申請TM類型的鎖。
- DDL Locks 用於保護數據庫對象的結構
- 排他DDL鎖(Exclusive DDL lock):阻塞其他會話得到該對象的DDL鎖或DML鎖。在DDL操作期間你可以查詢一個表,但是無法以任何方式修改這個表。
- 共享DDL鎖(Share DDL lock):保護所引用對象的結構,使其結構不會被其他會話修改
- System Locks 保護數據庫的內部結構
4.數據庫設計
(1)無限分級樹形結構的數據庫表設計:採用左右值編碼來存儲。
只用一條查詢語句即可得到某個根節點及其所有子孫節點的先序遍歷。由於消除了遞歸,在數據記錄量較大時,可以大大提高列表效率。但是,這種編碼方案由於層信息位數的限制,限制了每層能所允許的最大子節點數量及最大層數。同時,在添加新節點的時候必須先計算新節點的位置是否超過最大限制。
參考:https://blog.csdn.net/comiunknown/article/details/1586020
六、Zookeeper+Dubbo
1. Dubbo: Dubbo是一個分佈式服務框架,致力於提供高性能和透明化的RPC(Remote Procedure Call)遠程服務調用方案,以及SOA服務治理方案.
2.服務註冊與發現:
0 服務容器負責啓動,加載,運行服務提供者。
1. 服務提供者(生產者)在啓動時,向註冊中心註冊自己提供的服務。
2. 服務消費者在啓動時,向註冊中心訂閱自己所需的服務。
3. 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
4. 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
5. 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。