文章目錄
- 1、JVM
- 1.1、類加載機制
- 1.2、類加載器
- 1.3、雙親委派機制
- 1.4、為什么要設計雙親加載機制
- 1.5、全盤委托機制
- 1.6、Tomcat如何自定義類加載機制
- 1.7、記憶體模型
- 1.8、物件創建與分配
- 1.8.1、創建
- 1.8.2、分配
- 1.9、何判斷一個類是無用的類
- 1.10、finalize()方法最終判定物件是否存活
- 1.11、四大參考
- 1.12、物件回收演算法
- 1.13、四大垃圾回收演算法
- 1.14、CMS(標記-清除演算法=>寫屏障 + 增量更新)
- 1.14.1、運作程序
- 1.14.2、三色標記法
- 1.14.3、漏標-讀寫屏障(解決方案)
- 1.5、G1(復制演算法=>寫屏障 + SATB)
- 1.5.1、運作程序
- 1.5.2、G1的RS和CT
- 1.5.3、Collect Set
- 1.5.4、Marking bitmaps/TAMS
- 1.11、ZGC的顏色指標
- 1.12、100%CPU排查
- 1.13、JIT
- 1.14、逃逸分析
- 2、Disruptor的原理(生產消費者模型)
- 2.1、解決佇列速度慢
- 2.2、資料結構
- 2.3、核心組件
- 2.4、Wait Strategy
- 2.5、寫資料
- 3、Spring
- 3.1、Spring流程(IOC下的Bean的生命周期,回圈依賴,建構式)
- 3.2、AOP
- 4、SpringMVC
- 4.1、SpringMVC執行流程
- 5、SpringBoot
- 5.1、SpringBoot的自動裝箱
- 5.2、Starter自動裝配
- 6、RabbitMQ
- 6.1、訊息丟失
- 6.1.1、生產者丟失訊息
- 6.1.2、訊息佇列丟資料:訊息持久化,
- 6.1.3、消費者丟失訊息:消費者丟資料一般是因為采用了自動確認訊息模式,改為手動確認訊息即可!
- 6.1.4、訊息不被重復消費(冪等性)
- 6.1.5、如何保證RabbitMQ訊息的順序性?
- 7、Redis
- 7.1、Redis執行緒模型(reactor模型)
- 7.2、Redis核心資料結構使用與原理
- 7.2.1、String
- 7.2.2、List
- 7.2.3、Hash
- 7.2.4、Set
- 7.2.5、Sort Set
- 7.2.6、GeoHash
- 7.2.7、BloomFilter
- 7.3、持久化
- 7.4、Redis持久化資料和快取怎么做擴容?
- 7.5、記憶體淘汰策略
- 7.6、資料洗掉策略
- 7.7、快取擊穿/快取雪崩/快取穿透/熱點快取key重建優化/快取與資料庫雙寫不一致
- 7.7.1、快取擊穿(失效)
- 7.7.1.1、原因
- 7.7.1.2、解決方案
- 7.7.2、快取雪崩
- 7.7.2.1、原因
- 7.7.2.2、解決方案
- 7.7.3、快取穿透
- 7.7.3.1、原因
- 7.7.3.2、解決方案
- 7.7.4、熱點快取key重建優化
- 7.7.4.1、原因
- 7.7.4.2、解決方案
- 7.7.5、快取與資料庫雙寫不一致
特殊: JUC系列:https://blog.csdn.net/zhouhengzhe/article/details/112691117
1、JVM
1.1、類加載機制
加載:把.java檔案編譯成.class檔案,生成Class物件
驗證:驗證位元組碼的準確性
準備:給類的靜態變數做分配記憶體,并賦予默認值
決議:符號參考和動態鏈接都變為直接參考
初始化:給類的靜態變數初始化為指定的值,執行靜態代碼塊
1.2、類加載器
1、根類加載器(Bootstrap classLoader):負責加載lib下的核心類別庫
2、擴展加載器(ExtClassLoader):負責加載lib目錄下的ext的jar類包
3、應用加載器(AppClassLoader):負責加載ClassPath路勁下的類包(自定義的類)
4、自定義類加載器:繼承ClassLoader,重寫loadClass(),findClass(),一般是只需要重寫findClass
1.3、雙親委派機制
雙親加載機制中原始碼有兩個方法:
1、loadClass
1)先檢查指定的類是否已經加載過了,若已經加載過,則直接回傳加載的類
2)若沒有加載,則判斷有沒有父類,有的話則呼叫父類加載器,或者呼叫根類加載器(Bootstrap)加載,
3)若父類加載器與Bootstrap加載器都沒有找到指定的類,則呼叫下面的方法(findClass)來完成類加載
2、findClass
1.4、為什么要設計雙親加載機制
1、保證類的唯一性
2、沙箱安全機制
1.5、全盤委托機制
如果沒有顯示的使用其他類加載器,則類下的所有依賴與及參考的類都將會有加載該類的類加載器加載
1.6、Tomcat如何自定義類加載機制
1、CommonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
2、CatalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見;
3、SharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見,但是對于Tomcat容器不可見;
4、WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見,比如加載war包里相關的類,
每個war包應用都有自己的WebappClassLoader,實作相互隔離,比如不同war包應用引入了不同的spring版本,這樣實作就能加載各自的spring版本;
5、模擬實作Tomcat的JasperLoader熱加載
原理:后臺啟動執行緒監聽jsp檔案變化,如果變化了找到該jsp對應的servlet類的加載器參考(gcroot),重新生成新的JasperLoader加載器
賦值給參考,然后加載新的jsp對應的servlet類,之前的那個加載器因為沒有gcroot參考了,下一次gc的時候會被銷毀
=>總結:每個webappClassLoader加載自己的目錄下的class檔案,不會傳遞給父類加載器,打破了雙親委派機制,
1.7、記憶體模型
私有:
程式計時器:記錄當前執行緒執行到位元組碼行號
虛擬機堆疊:內部有許多堆疊幀,每個堆疊幀里面包括區域變數表,運算元堆疊,動態鏈接,方法出口,
本地方法堆疊:執行本地的Native方法
共享:
堆:內部分為eden區,s0,s1,老年代,保存物件和陣列
方法區/永久代(1.8后元空間):保存類資訊、常量、靜態變數、即時編譯器編譯后的代碼;內部有個運行時常量池,用于保存類的版本、欄位、方法、介面等;
擴展=>直接記憶體:通過unsafe,或者netty的DirectByteBuffer申請
1.8、物件創建與分配
1.8.1、創建
1、類加載檢查
虛擬機遇到一條new指令時,首先將去檢查這個指令的引數是否能在常量池中定位到一個類的符號參考,并且檢查這個符號參考代表 的類是否已被加載、決議和初始化過,如果沒有,那必須先執行相應的類加載程序, new指令對應到語言層面上講是,new關鍵詞、物件克隆、物件序列化等2、分配記憶體
//劃分記憶體 1、指標碰撞 記憶體規整,用過的記憶體放一邊,沒用過的放一邊 2、空閑串列 記憶體不規整,使用的和空閑的相互交錯,需要一個串列進行存盤 //并發問題解決 1、CAS 2、本地執行緒分配緩沖區(TLAB) 把記憶體分配的動作按照執行緒劃分在不同的空間之中進行,即每個執行緒在Java堆中預先分配一小塊記憶體, 通過XX:+/-UseTLAB引數來設定虛擬機是否使用TLAB(JVM會默認開啟XX:+UseTLAB),XX:TLABSize指定TLAB大小,3、初始化
為分配到的記憶體初始化為零值,不設定物件頭,若是呀TLAB,可以提前至TLAB分配時進行,保證物件即使不賦初始值也可以直接使用4、設定物件頭
物件布局: 1、物件頭(Header) 2、實體資料(Instance Data) 3、對齊填充(Padding)
5、執行方法
執行<init>方法,也就是所謂的屬性賦值與執行構造器
1.8.2、分配
1、堆疊上分配
通過逃逸分析確定該物件不會被外部訪問,如果不會逃逸可以將該物件在堆疊上分配記憶體,這樣該物件所占用的記憶體空間就可以隨堆疊幀出堆疊而銷毀,就減輕了垃圾回收的壓力,
/**
*物件逃逸分析:分析物件動態作用域,當一個物件在方法中被定義后,它可能被外部方法所參考,例如作為呼叫引數傳遞到其他地方中
*標量替換:通過逃逸分析確定該物件不會被外部訪問,并且物件可以被進一步分解時,JVM不會創建該物件,而是將該物件成員變數分解若干個被這個方法使用的成員變數所 代替,這些代替的成員變數在堆疊幀或暫存器上分配空間,這樣就不會因為沒有一大塊連續空間導致物件記憶體不夠分配
*/
'結論:堆疊上分配依賴于逃逸分析和標量替換'
2、堆上分配(eden區)
1、先eden區分配,滿了young GC,把存活的物件放入s0
2、再eden區分配,滿了young GC,把s0存活的物件和eden區存活的物件放入s1,
3、重復1,2操作
3、大物件進入老年代
大量連續的記憶體空間的物件
4、長期存活物件進入老年代
在2(堆上分配)中,每次移動都會給當前物件設定個計數器,默認15,CMS默認6,則會young gc放入老年代
5、物件動態年齡判斷
當一批物件的總大小大于s區記憶體大小的50%,則大于等于這批物件年齡最大值的物件,就可以進入老年代
6、空間擔保機制
年輕代每次young gc之前JVM都會計算下老年代剩余可用空間,如果這個可用空間小于年輕代里現有的所有物件大小之和(包括垃圾物件),
就會看一個“-XX:-HandlePromotionFailure”(jdk1.8默認就設定了) 的引數是否設定了,如果有這個引數,就會看看老年代的可用記憶體大小,
是否大于之前每一次minor gc后進入老年代的物件的平均大小,小于或者之前說的引數沒有設定,那么就會觸發一次Full gc,
對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的物件就會發生"OOM",
1.9、何判斷一個類是無用的類
1、該類所有的實體都已經被回收,也就是 Java 堆中不存在該類的任何實體,
2、加載該類的 ClassLoader 已經被回收,
3、該類對應的 java.lang.Class 物件沒有在任何地方被參考,無法在任何地方通過反射訪問該類的方法,
1.10、finalize()方法最終判定物件是否存活
1. 第一次標記并進行一次篩選,
篩選的條件是此物件是否有必要執行finalize()方法,
當物件沒有覆寫finalize方法,物件將直接被回收,
2. 第二次標記
如果這個物件覆寫了finalize方法,finalize方法是物件脫逃死亡命運的最后一次機會,如果物件要在finalize()中成功拯救 自己,只要重新與參考鏈上的任何的一個物件建立關聯即可,譬如把自己賦值給某個類變數或物件的成員變數,那在第 二次標記時它將移除出“即將回收”的集合,如果物件這時候還沒逃脫,那基本上它就真的被回收了,
//注意:一個物件的finalize()方法只會被執行一次,也就是說通過呼叫finalize方法自我救命的機會就一次,
1.11、四大參考
1、強參考:普通的變數參考
2、軟參考(SoftReference):將物件用SoftReference軟參考型別的物件包裹,正常情況不會被回收,但是GC做完后發現釋放不出空間存放新的物件,則會把這些軟參考的物件回收掉,軟參考可用來實作記憶體敏感的高速快取,
//使用場景:瀏覽器的后退按鈕
3、弱參考(WeakReference):將物件用WeakReference軟參考型別的物件包裹,弱參考跟沒參考差不多,GC會直接回收掉,很少用
4、虛參考:虛參考也稱為幽靈參考或者幻影參考,它是最弱的一種參考關系,幾乎不用
1.12、物件回收演算法
1、參考計數法:回圈參考無法解決
2、Gc root演算法
將“GC Roots” 物件作為起點,從這些節點開始向下搜索參考的物件,找到的物件都標記為非垃圾物件,其余未標記的物件都是垃圾物件
GC Roots根節點:執行緒堆疊的本地變數、靜態變數、本地方法堆疊的變數等等
1.13、四大垃圾回收演算法
1、標記復制演算法
//定義:將記憶體分兩塊,每使用一塊,都會在記憶體用完之后,將存活的物件復制到另一塊中,再把使用過的空間清理
//問題:浪費空間,永遠浪費一半空間
2、標記清除演算法
//定義:標記存活物件,統一回收未被標記的物件
//問題:1、效率問題:效率不高,物件過多,就要浪費時間標記物件
2、空間問題:產生大量的不連續的碎片
3、標記整理演算法
跟標記清除一樣,多了個整理存活物件的程序
4、分代收集演算法
年輕代復制演算法,老年代標記整理
1.14、CMS(標記-清除演算法=>寫屏障 + 增量更新)
1.14.1、運作程序
1、初始標記: 暫停所有的其他執行緒(STW),并記錄下gc roots直接能參考的物件,速度很快,
2、并發標記: 并發標記階段就是從GC Roots的直接關聯物件開始遍歷整個物件圖的程序, 這個程序耗時較長但是不需要停頓用戶執行緒, 可以與垃圾收集執行緒一起并發運行,因為用戶程式繼續運行,可能會有導致已經標記過的物件狀態發生改變,
3、重新標記: 重新標記階段就是為了修正并發標記期間因為用戶程式繼續運行而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比并發標記階段時間短,主要用到'增量更新演算法'做重新標記,
4、并發清理: 開啟用戶執行緒,同時GC執行緒開始對未標記的區域做清掃,這個階段如果有新增物件會被標記為`三色標記法`里面的黑色不做任何處理
5、并發重置:重置本次GC程序中的標記資料,
1.14.2、三色標記法
黑色:'表示物件已經被垃圾收集器訪問過',且這個物件的所有參考都已經掃描過,黑色的物件代表已經掃描過, 它是安全存活的,如果有其他物件參考指向了黑色物件,無須重新掃描一遍,黑色物件不可能直接(不經過灰色物件)指向某個白色物件,
灰色:'表示物件已經被垃圾收集器訪問過',但這個物件上至少存在一個參考還沒有被掃描過,
白色:'表示物件尚未被垃圾收集器訪問過',顯然在可達性分析剛剛開始的階段,所有的物件都是白色的,若在分析結束的階段,仍然是白色的物件,即代表不可達,
1.14.3、漏標-讀寫屏障(解決方案)
1、增量更新(Incremental Update)+寫屏障
增量更新就是當黑色物件插入新的指向白色物件的參考關系時, 就將這個新插入的參考記錄下來, 等并發掃描結束之后, 再將這些記錄過的參考關系中的黑色物件為根, 重新掃描一次, 這可以簡化理解為, 黑色物件一旦新插入了指向白色物件的參考之后, 它就變回灰色物件了,
2、原始快照(Snapshot At The Beginning,SATB)+寫屏障
原始快照就是當灰色物件要洗掉指向白色物件的參考關系時, 就將這個要洗掉的參考記錄下來, 在并發掃描結束之后,再將這些記錄過的參考關系中的灰色物件為根, 重新掃描一次,這樣就能掃描到白色的物件,將白色物件直接標記為黑色(目的就是讓這種物件在本輪gc清理中能存活下來,待下一輪gc的時候重新掃描,這個物件也有可能是浮動垃圾)
以上無論是對參考關系記錄的插入還是洗掉, 虛擬機的記錄操作都是通過寫屏障實作的,
1.5、G1(復制演算法=>寫屏障 + SATB)
1.5.1、運作程序
初始標記(initial mark,STW):暫停所有的其他執行緒,并記錄下gc roots直接能參考的物件,速度很快
并發標記(Concurrent Marking):并發標記階段就是從GC Roots的直接關聯物件開始遍歷整個物件圖的程序, 這個程序耗時較長但是不需要停頓用戶執行緒, 可以與垃圾收集執行緒一起并發運行,因為用戶程式繼續運行,可能會有導致已經標記過的物件狀態發生改變,
最終標記(Remark,STW):重新標記階段就是為了修正并發標記期間因為用戶程式繼續運行而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比并發標記階段時間短,主要用到'增量更新演算法'做重新標記,
篩選回收(Cleanup,STW):篩選回收階段首先對各個Region的==回收價值和成本進行排序,根據用戶所期望的GC停頓時間(可以用JVM引數 -XX:MaxGCPauseMillis指定)來制定回收計劃
1.5.2、G1的RS和CT
'已記憶集合RememberedSets:',
存盤著其他磁區中的物件對本磁區物件的參考,每個磁區有且只有一個RSet,用于提高GC效率,
YGC時,GC root主要是兩類:堆疊空間和老年代磁區到新生代磁區的參考關系,所以記錄老年代磁區對新生代磁區的參考
Mixed GC時,由于僅回收部分老年代磁區,老年代磁區之間的參考關系也將被使用,所以記錄老年代磁區之間的參考
因此,我們僅需要記錄兩種參考關系:老年代磁區參考新生代磁區,老年代磁區之間的參考,
因為每次GC都會掃描所有young區物件,所以RSet只有在掃描old參考young,old參考old時會被使用,
'卡表,Card Table:'
Java堆劃分為相等大小的一個個區域,這個小的區域(一般size在128-512位元組)被當做Card,而Card Table維護著所有的Card,Card Table的結構是一個位元組陣列,Card Table用單位元組的資訊映射著一個Card,當Card中存盤了物件時,稱為這個Card被臟化了(dirty card), 對于一些熱點Card會存放到Hot card cache,同Card Table一樣,Hot card cache也是全域的結構,
1.5.3、Collect Set
Collect Set(CSet)是指,在Evacuation階段,由G1垃圾回收器選擇的待回收的Region集合,G1垃圾回收器的軟實時的特性就是通過CSet的選擇來實作的,對應于演算法的兩種模式fully-young generational mode和partially-young mode,CSet的選擇可以分成兩種:
在fully-young generational mode下:顧名思義,該模式下CSet將只包含young的Region,G1將調整young的Region的數量來匹配軟實時的目標;
在partially-young mode下:該模式會選擇所有的young region,并且選擇一部分的old region,old region的選擇將依據在Marking cycle phase中對存活物件的計數,G1選擇存活物件最少的Region進行回收,
1.5.4、Marking bitmaps/TAMS
Marking bitmap是一種資料結構,其中的每一個bit代表的是一個可用于分配給物件的起始地址
bitmap
其中addrN代表的是一個物件的起始地址,綠色的塊代表的是在該起始地址處的物件是存活物件,而其余白色的塊則代表了垃圾物件,
G1使用了兩個bitmap,一個叫做previous bitmap,另外一個叫做next bitmap,previous bitmap記錄的是上一次的標記階段完成之后的構造的bitmap;next bitmap則是當前正在標記階段正在構造的bitmap,在當前標記階段結束之后,當前標記的next bitmap就變成了下一次標記階段的previous bitmap,
TAMS(top at mark start)變數,是一對用于區分在標記階段新分配物件的變數,分別被稱為previous TAMS和next TAMS,在previous TAMS和next TAMS之間的物件則是本次標記階段時候新分配的物件,
previous TMAS 和 next TAMS
白色region代表的是空閑空間,綠色region代表是存活物件,橙色region代表的在此次標記階段新分配的物件,注意的是,在橙色區域的物件,并不能確保它們都事實上是存活的,
1.11、ZGC的顏色指標
組成
GC資訊保存在指標中,
每個物件有一個64位指標,這64位被分為
18位:預留給以后使用
1位:Finalizable標識,此位與并發參考處理有關,它表示這個物件只能通過finalizer才能訪問
1位:Remapped標識,設定此位的值后,物件未指向relocation set中(relocation set表示需要GC的Region集合)
1位:Marked1標識
1位:Marked0標識,和上面的Marked1都是標記物件用于輔助GC
42位:物件的地址(所以它可以支持2^42=4T記憶體)
優勢:
一旦某個Region的存活物件被移走之后,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該Region的參考都被修正后才能清理,這使得理論上只要還有一個空閑Region,ZGC就能完成收集,
顏色指標可以大幅減少在垃圾收集程序中記憶體屏障的使用數量,ZGC只使用了讀屏障,
顏色指標具備強大的擴展性,它可以作為一種可擴展的存盤結構用來記錄更多與物件標記、重定位程序相關的資料,以便日后進一步提高性能,
1.12、100%CPU排查
1使用top命令查看cpu占用資源較高的PID
2、通過jps 找到當前用戶下的java程式PID(jps -l 能夠列印出所有的應用的PID)
3、使用 pidstat -p
4、找到cpu占用較高的執行緒TID
5、將TID轉換為十六進制的表示方式
6、通過jstack -l(使用jstack 輸出當前PID的執行緒dunp資訊)
7、 查找 TID對應的執行緒(輸出的執行緒id為十六進制),找到對應的代碼
1.13、JIT
JIT是一種提高程式運行效率的方法,通常,程式有兩種運行方式:靜態編譯與動態解釋,靜態編譯的程式在執行前全部被翻譯為機器碼,而動態解釋執行的則是一句一句邊運行邊翻譯,
1.14、逃逸分析
逃逸分析是指在某個方法之內創建的物件,除了在方法體之內被參考之外,還在方法體之外被其它變數參考到;這樣帶來的后果是在該方法執行完畢之后,該方法中創建的物件將無法被GC回收,由于其被其它變數參考,正常的方法呼叫中,方法體中創建的物件將在執行完畢之后,將回收其中創建的物件;故由于無法回收,即成為逃逸,
2、Disruptor的原理(生產消費者模型)
2.1、解決佇列速度慢
1、環形陣列結構:
為了避免垃圾回收,采用陣列而非鏈表,同時,陣列對處理器的快取機制更加友好(CPU加載空間區域性原則),
2、元素位置定位:
陣列長度2^n,通過位運算,加快定位的速度,下標采取遞增的形式,不用擔心index溢位的問題,index是long型別,即使100萬QPS的處理速度,也需要30萬年才能用完,
3、無鎖設計:
每個生產者或者消費者執行緒,會先申請可以操作的元素在陣列中的位置,申請到之后,直接在該位置寫入或者讀取資料
2.2、資料結構
框架使用RingBuffer來作為佇列的資料結構,RingBuffer就是一個可自定義大小的環形陣列,除陣列外還有一個序列號(sequence),用以指向下一個可用的元素,供生產者與消費者使用
2.3、核心組件
1、RingBuffer——Disruptor底層資料結構實作,核心類,是執行緒間交換資料的中轉地;
2、Sequencer——序號管理器,生產同步的實作者,負責消費者/生產者各自序號、序號柵欄的管理和協調,Sequencer有單生產者,多生產者兩種不同的模式,里面實作了各種同步的演算法;
3、Sequence——序號,宣告一個序號,用于跟蹤ringbuffer中任務的變化和消費者的消費情況,disruptor里面大部分的并發代碼都是通過對Sequence的值同步修改實作的,而非鎖,這是disruptor高性能的一個主要原因;
4、SequenceBarrier——序號柵欄,管理和協調生產者的游標序號和各個消費者的序號,確保生產者不會覆寫消費者未來得及處理的訊息,確保存在依賴的消費者之間能夠按照正確的順序處理, Sequence Barrier是由Sequencer創建的,并被Processor持有;
5、EventProcessor——事件處理器,監聽RingBuffer的事件,并消費可用事件,從RingBuffer讀取的事件會交由實際的生產者實作類來消費;它會一直偵聽下一個可用的號,直到該序號對應的事件已經準備好,
6、EventHandler——業務處理器,是實際消費者的介面,完成具體的業務邏輯實作,第三方實作該介面;代表著消費者,
7、Producer——生產者介面,第三方執行緒充當該角色,producer向RingBuffer寫入事件,
8、Wait Strategy:Wait Strategy決定了一個消費者怎么等待生產者將事件(Event)放入Disruptor中,
2.4、Wait Strategy
1、BlockingWaitStrategy
Disruptor的默認策略是BlockingWaitStrategy,在BlockingWaitStrategy內部是使用鎖和condition來控制執行緒的喚醒,BlockingWaitStrategy是最低效的策略,但其對CPU的消耗最小并且在各種不同部署環境中能提供更加一致的性能表現,
2、SleepingWaitStrategy
SleepingWaitStrategy 的性能表現跟 BlockingWaitStrategy 差不多,對 CPU 的消耗也類似,但其對生產者執行緒的影響最小,通過使用LockSupport.parkNanos(1)來實作回圈等待,一般來說Linux系統會暫停一個執行緒約60μs,這樣做的好處是,生產執行緒不需要采取任何其他行動就可以增加適當的計數器,也不需要花費時間信號通知條件變數,但是,在生產者執行緒和使用者執行緒之間移動事件的平均延遲會更高,它在不需要低延遲并且對生產執行緒的影響較小的情況最好,一個常見的用例是異步日志記錄,
3、YieldingWaitStrategy
YieldingWaitStrategy是可以使用在低延遲系統的策略之一,YieldingWaitStrategy將自旋以等待序列增加到適當的值,在回圈體內,將呼叫Thread.yield(),以允許其他排隊的執行緒運行,在要求極高性能且事件處理線數小于 CPU 邏輯核心數的場景中,推薦使用此策略;例如,CPU開啟超執行緒的特性,
4、BusySpinWaitStrategy
性能最好,適合用于低延遲的系統,在要求極高性能且事件處理執行緒數小于CPU邏輯核心數的場景中,推薦使用此策略;例如,CPU開啟超執行緒的特性,
2.5、寫資料
單執行緒寫資料的流程:
1、申請寫入m個元素;
2、若是有m個元素可以入,則回傳最大的序列號,這兒主要判斷是否會覆寫未讀的元素;
3、若是回傳的正確,則生產者開始寫入元素,
3、Spring
3.1、Spring流程(IOC下的Bean的生命周期,回圈依賴,建構式)
1. 啟動ApplicationContext
兩個重要的子類:
AnnotationConfigApplicationContext(用的最多)
ClassPathXmlApplicationContext
2. 初始化AnnotationBeanDefinitionReader
a.讀取spring內部的初始的 beanFactoryPostProcess 和 其他的幾種 beanPostProcess(AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry))
1. AnnotationAwareOrderComparator:決議@Order進行排序
2. ContextAnnotationAutowireCandidateResolver
3. ConfigurationClassPostProcessor:決議加了@Configuration、@ComponentScan、@ComponentScans、@Import等注解(最重要的類)
4. AutowiredAnnotationBeanPostProcessor:決議@Autowired
5. RequiredAnnotationBeanPostProcessor:決議@Required
6. CommonAnnotationBeanPostProcessor:負責決議@Resource、@WebServiceRef、@EJB
7. EventListenerMethodProcessor:找到@EventListener
8. DefaultEventListenerFactory:決議@EventListener
b. 在ConfigurationClassPostProcessor類中有主要是為了決議加了@Configuration、@ComponentScan、@ComponentScans、@Import等注解,在這里面他有一個細節,就是加了@Configuration里面,他會把當前類標注成full類,就會產生一個aop的動態代理去加載當前類,沒有的話就把當前類標注成lite類,也就是普通類處理,
3. 初始化ClassPathBeanDefinitionScanner
a. 程式員能夠在外部呼叫doScan(), 或者 繼承該類可以重寫scan規則用來動態掃描注解,需要注冊到容器,
b. spring內部是自己重新new 新的物件來掃描,
4. 執行register()方法,一般來說就是注冊我們的配置類
a. 先把此物體型別轉換為一個BeanDefinition
5. 執行refresh(),先初始化比如BeanFactory這類基礎的容器,
a. 執行invokeBeanFactoryPostProcessors(),主要的作用是掃描包和parse (類->beanDefinition)
1. 執行BeanFactoryPostProcessor的子介面BeanDefinitionRegistryPostProcessor方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry register)
作用:主要是掃描包找到合格的類,決議類
i. 先執行程式員通過 context.add的
ii. 再執行spring內部的和程式員通過注解注冊的 并且特殊的比如 實作了PriorityOrdered,Order
iii. 最后再執行其他的 BeanDefinitionRegistryPostProcessor
2. 再執行BeanFactoryPostProcessor介面 方法postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)**
作用:1. 和子介面一樣 掃描包找到合格的類,決議類
2. 為@Configuration的類做代理
i. 先執行子介面中的方法
ii. 再執行程式員通過 context.add添加的
iii. 再執行spring內部和程式員通過注解注冊的 并且特殊的比如 PriorityOrdered,Order
iv. 最后執行其他的 BeanFactoryPostProcessor
他們在spring中唯一的實作類是ConfigurationClassPostProcessor
將類變成beanDefinition的流程:
1. 從BeanDefinitionRegistry中獲取所有的bd
2. 判斷是否該bd是否被決議過,主要根據bd中是否有full或者lite屬性,
3. 將未解的bd去,回圈決議bd
a. 先處理內部類
b. 處理@PropertrySource 環境配置
c. 處理@ComponentScan
決議帶有ComponentScan,會呼叫ClassPathBeanDefinitionScanner,根據包路徑,和匹配規則掃描出合格類,
d. 處理@Import
i. 先處理 ImportSelect,執行selectImports(), 事務的初始化和aop的代理型別,是否傳遞代理 就是在這里做的,
ii. 然后處理 ImportBeanDefinitionRegistrar介面,會放到該bd的一個Map中,回圈map統一去執行實作方法registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
iii. 最后處理普通的類,同樣會遞回去決議該bd
e. 處理@ImportResource
f. 處理@Bean
g.處理介面bd
4. 然后將所有的合格的類,轉換成bd,注冊到beanDefinitionRegistry,
b. 然后會注冊beanPostProcessor,國際化等等,不是很重要
c. 比較重要的,也是將bd變成bean的方法 finishBeanFactoryInitialization(),實體化非延遲的單例(回圈依賴)
d. 一般來說首先getBeanDefinition之前,都要合并bd,
1)第一次getSingleton,從單例池拿是否存在,單例的第一次一般是不存在,并且會判斷是否在正在創建bean的set集合中,
singletonObjects 一級快取,完整的bean
singletonFactories 二級快取,存的是代理bean工廠
earlySingletonObjects 三級快取,一般是是半成品的bean
a. 如果存在,直接回傳
b. 如果不存在,并且不在正在創建bean的set集合中,直接回傳null
c. 如果不存在,并且在正在創建bean的set集合中,從三級快取拿,
i. 存在,直接三級快取拿,
ii. 不存在,通過二級快取,代理的bean工廠拿,獲得該bean,然后將得到bean放到三級快取中,移出二級快取,(原因是生產bean工廠周期比較長的,)
2)第二次getSingleton
a. 首先將beanName放到正在創建bean的set集合中,表示正在創建該bean
b. 然后會呼叫二級快取去獲取bean,lambda延遲機制,就會呼叫運算式中,也就是createBean,這時候是正在獲取代理bean工廠會走一個完整的bean 的生命周期,
c. 然后從bean工廠獲取bean,
1. 建構式:第一次 BeanPostProcessor,是否需要代理bean,如果代理bean直接回傳,不會走下面的流程,
2. 第二次BeanPostProcessor,推斷建構式
a. 首先推斷建構式陣列
i. 沒提供建構式=========設定建構式陣列為null
ii. 一個默認的建構式======設定建構式陣列為null
iii. 一個不是默認的建構式===設定建構式陣列為該建構式
iv. 一個構造方法并且加了@Autowired====設定建構式陣列為該建構式
v. 多個模糊建構式========設定建構式陣列為null
vi. 多個建構式,有唯一加了@Autowired==設定建構式陣列為該建構式
vii. 多個建構式,多個@Autowired(required為false)===設定建構式陣列為多個@Autowired
viii. 提供多個建構式,多個@Autowired(required為true)=== 拋例外
b. 如果推斷構造陣列不為null 或者,自動注入型別為建構式,或者設定了建構式的屬性(xml方式)等,還有一種傳引數金來
i. 推斷建構式,
1. 只有個建構式,最終被確定的建構式,
2. 有多個建構式
a. 優先修飾符最開放的,public>protected>Default>private
b. 修飾符一樣找屬性最多的
ii. 推斷引數,
1. 首先找出所有候選的引數型別,實體化屬性
2. 然后型別是介面,那么判斷是否開啟寬松構造
a. 未開啟報錯,
b. 開啟了,判斷子類的差值(spring有個演算法),默認差值是-1024,
c. 差值低的為該引數,一樣的丟到模糊集合中,隨機取出,
c. 建構式陣列為null,直接通過無參實體化建構式,
3. 第三次BeanPostProcessor ,快取了注入元素的資訊
injectionMetadataCache: key: beanName或者類名 value:為決議出的屬性(包括方法)集合 InjectionMetadata,
InjectionMetadata:可以存放method 和 屬性,類中有欄位判斷是否是屬性 isField,
checkedInitMethods: 存放 @PostConstruct ,
checkedDestroyMethods:存放 @PreDestroy,
a. AutowiredAnnotationBeanPostProcessor 主要決議加了 @Autowired 和 @Value 方法和屬性,
b. CommonAnnotationBeanPostProcessor 主要決議加了 @Resource屬性,
c. InitDestroyAnnotationBeanPostProcessor 主要決議加了 @PostConstruct 和 @PreDestroy方法
d. 還有很多
4. 第四次 BeanPostProcessor,生產代理工廠,作用是可以解決回圈依賴
a. 先判斷是否允許回圈依賴,可通過api修改屬性,或者直接改源代碼,
b. 然后判斷當前bean是否是正在創建的bean
c. 呼叫populateBean 主要作用,注入屬性,
5. 第五次BeanPostProcessor,控制是否需要屬性注入,目前沒什么作用,
再注入快取的屬性之前,先通過 自動注入模型
a. byType byName,找到setter,注入,體現了@Autowired不是自動注入,而是手動注入,
6. 第六次 BeanPostProcessor ,完成注解的屬性填充** **@Autowired @Resource**
a. 注入之前還是會再找一下是否有其他需要注入的屬性和方法,
b. 屬性的呼叫屬性注入方法,函式呼叫函式的注入方法,
i. 通過屬性的型別,從BeanDefinitionMap中找屬性名稱(介面則找找這個介面的子類),
ii. 然后判斷我們當前需要注入的屬性是不是這幾個型別,得到候選的型別,
iii. 當有多個型別,再通過屬性名稱去推斷出唯一候選的屬性名,如果找到多個候選的屬性名,拋例外,
iv. 只有唯一的屬性名,通過類名去獲取型別,
v. 最終通過找到唯一匹配的beanName和型別去注入,當沒有找到匹配的名稱和型別,就會拋例外,
c. 在注入的時候,有回圈依賴的時候,會去先去實體化該屬性,
7. 第七次BeanPostProcessor ,處理實作各種aware介面的重寫方法 + 生命周期回呼 執行@PostConstruct方法
執行 實作InitializingBean介面的,重寫方法,和 xml 中的 init-method="xxx"方法,
8. 第八次BeanPostProcessor ,做aop代理
a. 判斷是否需要做代理
i. 找出所有的候選切面,比如 加了 @Aspect的類 , 事務的切面
ii. 做匹配邏輯,比如根據切面的連接點運算式 或者 類中方法是否加了@Transaction去 判斷當前類是否匹配出,合適的切面集合,
iii. 然后對匹配出的切面集合,做排序,
iv. 能匹配上說明就做代理
b. 哪種代理(默認用JDK動態代理)
i. 當代理工廠設定ProxyTargetClass為 true,則為CGLIB代理,
ii. 當目標物件為類,則也用為CGLIB代理,
iii. 只有proxyTarget為 false,并且為目標物件為介面,則用JDK動態代理
c. 執行代理invokeHandler(這里主要是JDK的代理,invoke方法)
i. 首先會進行普通方法的判斷比如hashcode eques等等,沒有就給代理類創建,不是很重要
ii. 然后判斷是否需要將代理傳遞下去,就是系結到 ThreadLocal中(在事務中,這個特別的重要)
iii. 獲取執行鏈,也就是這個目標物件的通知集合,(也就是所有過濾器鏈,實作了MethodIntercept,)
iv. 執行過濾器執行鏈,類似于火炬傳遞,(事務的methodInterceptor也在這里會被呼叫)
1. 判斷通知是否執行完,沒有執行完去,按順序執行通知,
2. 依次呼叫對應的通知,最終都會去回呼到proceed()方法,
3. 最終執行完代理方法,就會呼叫本身的方法,比較特殊的是around是在通知里,執行被代理的目標方法,
3.2、AOP
原始碼底層的實作是動態代理
動態代理有cglib和jdk實作
1、JDK動態代理通過反射機制實作:
通過實作InvocationHandlet介面創建自己的呼叫處理器;
通過為Proxy類指定ClassLoader物件和一組interface來創建動態代理;
通過反射機制獲取動態代理類的建構式,其唯一引數型別就是呼叫處理器介面型別;
通過建構式創建動態代理類實體,構造時呼叫處理器物件作為引數參入;
JDK動態代理是面向介面的代理模式,如果被代理目標沒有介面那么Spring也無能為力,Spring通過Java的反射機制生產被代理介面的新的匿名實作類,重寫了其中AOP的增強方法,
2、CGLib動態代理:
CGLib是一個強大、高性能的Code生產類別庫,可以實作運行期動態擴展java類,Spring在運行期間通過 CGlib繼承要被動態代理的類,重寫父類的方法,實作AOP面向切面編程,底層是ASM實作
3、兩者對比:
JDK動態代理是面向介面的,
CGLib動態代理是通過位元組碼底層繼承要代理類來實作(被代理類不能被final關鍵字所修飾,),
4、使用注意:
如果要被代理的物件是個實作類,那么Spring會使用JDK動態代理來完成操作(Spirng默認采用JDK動態代理實作機制);
如果要被代理的物件不是個實作類,那么Spring會強制使用CGLib來實作動態代理
4、SpringMVC
4.1、SpringMVC執行流程
1)前端控制器DispatcherServlet 由框架提供作用:接收請求,處理回應結果
2)處理器映射器HandlerMapping由框架提供
作用:根據請求URL,找到對應的Handler
3)處理器配接器HandlerAdapter由框架提供
作用:呼叫處理器(Handler|Controller)的方法
4)處理器Handler又名Controller,后端處理器
作用:接收用戶請求資料,呼叫業務方法處理請求
5)視圖決議器ViewResolver由框架提供
作用:視圖決議,把邏輯視圖名稱決議成真正的物理視圖
支持多種視圖技術:JSTLView,FreeMarker...
6)視圖View,程式員開發
作用:將資料展現給用戶
5、SpringBoot
5.1、SpringBoot的自動裝箱
1、@SpringBootApplication=>
2、@EnableAutoConfiguration=>
3、@Import(AutoConfigurationImportSelector.class)=>呼叫getCandidateConfigurations()方法,里面有個讀取Meta-info/spring.factories
5.2、Starter自動裝配
1.撰寫一個帶有@Configuration注解的類,如果按條件加載可以加上@ConditionalOnClass或@ConditionalOnBean注解
2.在classpath下創建META-INF/spring.factories檔案,并在spring.factories中添加
org.springframework.boot.autoconfigure.EnableAutoConfiguretion =\
上面定義類的全類名
6、RabbitMQ
6.1、訊息丟失
6.1.1、生產者丟失訊息
RabbitMQ提供transaction和confirm模式來確保生產者不丟訊息;
transaction機制就是說:發送訊息前,開啟事務(channel.txSelect()),然后發送訊息,如果發送程序中出現什么例外,事務就會回滾(channel.txRollback()),如果發送成功則提交事務(channel.txCommit()),然而,這種方式有個缺點:吞吐量下降;
confirm模式用的居多:一旦channel進入confirm模式,所有在該信道上發布的訊息都將會被指派一個唯一的ID(從1開始),一旦訊息被投遞到所有匹配的佇列之后;
rabbitMQ就會發送一個ACK給生產者(包含訊息的唯一ID),這就使得生產者知道訊息已經正確到達目的佇列了;
如果rabbitMQ沒能處理該訊息,則會發送一個Nack訊息給你,你可以進行重試操作,
6.1.2、訊息佇列丟資料:訊息持久化,
處理訊息佇列丟資料的情況,一般是開啟持久化磁盤的配置,
這個持久化配置可以和confirm機制配合使用,你可以在訊息持久化磁盤后,再給生產者發送一個Ack信號,
這樣,如果訊息持久化磁盤之前,rabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發,
那么如何持久化呢?
1. 將queue的持久化標識durable設定為true,則代表是一個持久的佇列
2. 發送訊息的時候將deliveryMode=2
這樣設定以后,即使rabbitMQ掛了,重啟后也能恢復資料
6.1.3、消費者丟失訊息:消費者丟資料一般是因為采用了自動確認訊息模式,改為手動確認訊息即可!
消費者在收到訊息之后,處理訊息之前,會自動回復RabbitMQ已收到訊息;
如果這時處理訊息失敗,就會丟失該訊息;
解決方案:處理訊息成功后,手動回復確認訊息,(手動ACK)
6.1.4、訊息不被重復消費(冪等性)
保證訊息的唯一性,就算是多次傳輸,不要讓訊息的多次消費帶來影響;保證訊息等冪性=》redis,資料庫自增
6.1.5、如何保證RabbitMQ訊息的順序性?
同一個queue里面訊息是有序的,保證訊息發送到同一個queue就好了,
單執行緒消費保證訊息的順序性;對訊息進行編號,消費者處理訊息是根據編號處理訊息;
7、Redis
7.1、Redis執行緒模型(reactor模型)
Redis的IO多路復用:redis利用epoll來實作IO多路復用,將連接資訊和事件放到佇列中,依次放到
檔案事件分派器,事件分派器將事件分發給事件處理器,

7.2、Redis核心資料結構使用與原理
7.2.1、String
1、底層:
是SDS實作,其編碼方式有int,raw,embstr,主要存在于redisObject的ptr屬性中
a. 默認是int,正式型別是long
b. 當字串大于32位元組的字串值,設定為raw
c.當 字串保存的小于等于32位元組,設定為embstr
總結:
在Redis中,存盤long、double型別的浮點數是先轉換為字串再進行存盤的,
raw與embstr編碼效果是相同的,不同在于記憶體分配與釋放,raw兩次,embstr一次,
embstr記憶體塊連續,能更好的利用快取在來的優勢
int編碼和embstr編碼如果做追加字串等操作,滿足條件下會被轉換為raw編碼;embstr編碼的物件是只讀的,一旦修改會先轉碼到raw,
2、應用場景
a. 單值快取
b. 分布式鎖
c. 計數器
d. Web集群session共享
e. 分布式系統全域序列號
f. 物件快取
7.2.2、List
1、底層:
List是一個有序(按加入的時序排序)的資料結構,Redis采用quicklist(雙端鏈表) 和 ziplist 作為List的底層實作
2、應用場景
a.Stack(堆疊) = LPUSH + LPOP
b. Queue(佇列)= LPUSH + RPOP
c. Blocking MQ(阻塞佇列)= LPUSH + BRPOP
d. 微博和微信公號訊息流
e. 微博訊息和微信公號訊息
7.2.3、Hash
1、底層:
Hash 資料結構底層實作為一個字典( dict ),也是RedisBb用來存盤K-V的資料結構,當資料量比較小,或者單個元素比較小時,底層用ziplist存盤,資料大小和元素數量閾值可以通過如下引數設定
2、應用場景:
a. 物件快取
b. 電商購物車
c. 購物車操作(添加商品,增加數量,商品總數,洗掉商品,獲取購物車所有商品)
7.2.4、Set
1、底層:
Set為無序的,自動去重的集合資料型別,Set資料結構底層實作為一個value為null的字典( dict ),當資料可以用整形表示時,Set集合將被編碼為intset資料結構,兩個條件任意滿足時Set將用hashtable存盤資料,
a. 元素個數大于 set-max-intset-entries ,
b. 元素無法用整形表示
set-max-intset-entries 512 // intset 能存盤的最大元素個數,超過則用hashtable編碼
2、應用場景:
a. 微信抽獎小程式
b. 微信微博點贊,收藏,標簽
c. 集合操作實作微博微信關注模型
d. 集合操作實作電商商品篩選
7.2.5、Sort Set
1、底層:
Sort Set 為有序的,自動去重的集合資料型別,ZSet 資料結構底層實作為 字典(dict) + 跳表(skiplist) ,當資料比較少時,用ziplist編碼結構存盤
zset-max-ziplist-entries 128 // 元素個數超過128 ,將用skiplist編碼
zset-max-ziplist-value 64 // 單個元素大小超過 64 byte, 將用 skiplist編碼
2、應用場景:
a. 點擊新聞
b. 展示當日排行前十
c. 七日搜索榜單計算
d. 展示七日排行前十
7.2.6、GeoHash
1、底層:
空間填充曲線,也就是經緯度換編碼,二分取右為1
地球緯度區間是[-90,90], 如某緯度是39.92324,可以通過下面演算法來進行維度編碼:
1)區間[-90,90]進行二分為[-90,0),[0,90],稱為左右區間,可以確定39.92324屬于右區間[0,90],給標記為1
2)接著將區間[0,90]進行二分為 [0,45),[45,90],可以確定39.92324屬于左區間 [0,45),給標記為0
3)遞回上述程序39.92324總是屬于某個區間[a,b],隨著每次迭代區間[a,b]總在縮小,并越來越逼近39.928167
4)如果給定的緯度(39.92324)屬于左區間,則記錄0,如果屬于右區間則記錄1,這樣隨著演算法的進行會 產生一個序列1011 1000 1100 0111 1001,序列的長度跟給定的區間劃分次數有關,
2、應用場景:
搖一搖
附近位置
7.2.7、BloomFilter
底層是取n個hash,做位運算
7.2.8、HyperLogLog(基數統計):統計用戶訪問量
7.3、持久化
RDB:快照,bgsave異步創建dump.rdb檔案,底層是fork+cow實作,
AOF:追加,底層是先寫入快取中,然后每隔一段時間會fsync到磁盤,也是fork一個子行程
運行:默認加載rdb檔案,如果同時啟用了RDB 和 AOF 方式,AOF 優先,啟動時只加載 AOF 檔案恢復資料,若開啟混合持久化方式則會創建一個檔案,上面是rdb,下面是aof的資料,啟動加載這個檔案
7.4、Redis持久化資料和快取怎么做擴容?
1、如果Redis被當做快取使用,使用一致性哈希實作動態擴容縮容,
2、如果Redis被當做一個持久化存盤使用,必須使用固定的keys-to-nodes映射關系,節點的數量一旦確定不能變化,否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行資料再平衡的一套系統,而當前只有Redis集群可以做到這樣
7.5、記憶體淘汰策略
a) 針對設定了過期時間的key做處理:
1、volatile-ttl:在篩選時,會針對設定了過期時間的鍵值對,根據過期時間的先后進行洗掉,越早過期的越先被洗掉,
2、volatile-random:就像它的名稱一樣,在設定了過期時間的鍵值對中,進行隨機洗掉,
3、volatile-lru:會使用 LRU 演算法篩選設定了過期時間的鍵值對洗掉,
4、volatile-lfu:會使用 LFU 演算法篩選設定了過期時間的鍵值對洗掉,
b) 針對所有的key做處理:
5、allkeys-random:從所有鍵值對中隨機選擇并洗掉資料,
6、allkeys-lru:使用 LRU 演算法在所有資料中進行篩選洗掉,
7、allkeys-lfu:使用 LFU 演算法在所有資料中進行篩選洗掉,
c) 不處理:
8、noeviction:不會剔除任何資料,拒絕所有寫入操作并回傳客戶端錯誤資訊"(error)OOM command not allowed when used memory",此時Redis只回應讀操作,
7.6、資料洗掉策略
1、被動洗掉:當讀/寫一個已經過期的key時,會觸發惰性洗掉策略,直接洗掉掉這個過期key
2、主動洗掉:由于惰性洗掉策略無法保證冷資料被及時刪掉,所以Redis會定期主動淘汰一批已過期的key
3、當前已用記憶體超過maxmemory限定時,觸發主動清理策略
4、LRU 演算法(Least Recently Used,最近最少使用):淘汰很久沒被訪問過的資料,以最近一次訪問時間作為參考,
5、LFU 演算法(Least Frequently Used,最不經常使用):淘汰最近一段時間被訪問次數最少的資料,以次數作為參考
7.7、快取擊穿/快取雪崩/快取穿透/熱點快取key重建優化/快取與資料庫雙寫不一致
7.7.1、快取擊穿(失效)
7.7.1.1、原因
由于大批量快取在同一時間失效可能導致大量請求同時穿透快取直達資料庫,可能會造成資料庫瞬間壓力過大甚至掛掉
7.7.1.2、解決方案
1、在批量增加快取時將這一批資料的快取過期時間設定為一個時間段內的不同時間,
2、分布式鎖
7.7.2、快取雪崩
7.7.2.1、原因
快取雪崩指的是快取層支撐不住或宕掉后, 流量會像奔逃的野牛一樣, 打向后端存盤層,由于快取層承載著大量請求, 有效地保護了存盤層, 但是如果快取層由于某些原因不能提供服務(比如超大并發過來,快取層支撐不住,或者由于快取設計不好,類似大量請求訪問bigkey,導致快取能支撐的并發急劇下降), 于是大量請求都會打到存盤層, 存盤層的呼叫量會暴增, 造成存盤層也會級聯宕機的情況,
7.7.2.2、解決方案
1) 保證快取層服務高可用性,比如使用Redis Sentinel或Redis Cluster,
2) 依賴隔離組件為后端限流熔斷并降級,比如使用Sentinel或Hystrix限流降級組件,
比如服務降級,我們可以針對不同的資料采取不同的處理方式,當業務應用訪問的是非核心資料(例如電商商品屬性,用戶資訊等)時,暫時停止從快取中查詢這些資料,而是直接回傳預定義的默認降級資訊、空值或是錯誤提示資訊;當業務應用訪問的是核心資料(例如電商商品庫存)時,仍然允許查詢快取,如果快取缺失,也可以繼續通過資料庫讀取,
3) 提前演練, 在專案上線前, 演練快取層宕掉后, 應用以及后端的負載情況以及可能出現的問題, 在此基礎上做一些預案設定,
7.7.3、快取穿透
7.7.3.1、原因
快取穿透是指查詢一個根本不存在的資料, 快取層和存盤層都不會命中, 通常出于容錯的考慮, 如果從存盤層查不到資料則不寫入快取層,
快取穿透將導致不存在的資料每次請求都要到存盤層去查詢, 失去了快取保護后端存盤的意義,
造成快取穿透的基本原因有兩個:
第一, 自身業務代碼或者資料出現問題,
第二, 一些惡意攻擊、 爬蟲等造成大量空命中,
7.7.3.2、解決方案
1、快取空物件
2、布隆過濾器(redission里面有個getBloomFilter()方法實作,==布隆過濾器不能洗掉資料,如果要洗掉得重新初始化資料==)
7.7.4、熱點快取key重建優化
7.7.4.1、原因
開發人員使用“快取+過期時間”的策略既可以加速資料讀寫, 又保證資料的定期更新, 這種模式基本能夠滿足絕大部分需求, 但是有兩個問題如果同時出現, 可能就會對應用造成致命的危害:
當前key是一個熱點key(例如一個熱門的娛樂新聞),并發量非常大,
重建快取不能在短時間完成, 可能是一個復雜計算, 例如復雜的SQL、 多次IO、 多個依賴等,
在快取失效的瞬間, 有大量執行緒來重建快取, 造成后端負載加大, 甚至可能會讓應用崩潰,
要解決這個問題主要就是要避免大量執行緒同時重建快取,
7.7.4.2、解決方案
互斥鎖(也就是所謂的分布式鎖)
7.7.5、快取與資料庫雙寫不一致
1、可以通過加讀寫鎖保證并發讀寫或寫寫的時候按順序排好隊,讀讀的時候相當于無鎖,
2、可以用阿里開源的canal通過監聽資料庫的binlog日志及時的去修改快取,但是引入了新的中間件,增加了系統的復雜度,
3、先刪快取,再寫資料庫
(1)timer異步淘汰(本文沒有細講,本質就是起個執行緒專門異步二次淘汰快取)
(2)總線異步淘汰
(3)讀binlog異步淘汰
我是小白弟弟,一個在互聯網行業的小白,立志成為一名架構師
https://blog.csdn.net/zhouhengzhe?t=1
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266040.html
標籤:java
上一篇:Freemarker專案創建與語法(IDEA 2018)
下一篇:看圖

