Java高階面試-面經

賴皮貓發表於2020-11-30

寫在前面

根據筆者面試經驗總結出來的一些知識點。mark一下,希望能給大家幫助。到高階了,需要所有的知識點都成體系,零散的知識點很容易讓面試官抓到薄弱處,給與降維打擊。所以,我也儘量按照知識體系模組來劃分。

面試java,肯定少不了jvm原理。此外關係型資料庫,非關係型資料庫,分散式快取,分散式鎖;高併發,

JVM原理相關

記憶體模型

記憶體模組
1、程式計數器:字元碼行號指示器。
2、虛擬機器棧:java方法執行的記憶體模型,儲存區域性變數,操作棧,動態棧,方法出口燈資訊。使用連續的記憶體空間
3、java堆:儲存所有物件例項,包括陣列。陣列也需要在堆上分配。可使用不連續的記憶體空間。
4、方法區:jdk1.8中,該區域已徹底移除,取而代之的是元空間。元空間不受jvm記憶體約束,直接在本地記憶體中申請記憶體分配。可使用不連續的記憶體空間。
5、執行時常量池:方法區的一部分,1.8後該區域轉移到Heap堆中。

Java記憶體模型,除了定義了一套規範,還提供了一系列原語,封裝了底層實現後,供開發者直接使用。
比如volatile,synchronize,final等。

volatile三大特性:原子性,可見性和有序性

synchronize關鍵字:保證同一時間只有一個執行緒在排程,同樣保證了原子性,可見性和有序性。

可見性是基於CPU和執行緒之間的快取一致性原理來說的。

GC原理

對於執行緒獨享的記憶體區域,生命週期與執行緒相同,且該區域內物件在生成時需要明確其生命週期。
對於執行緒共享的區域(方法區,Heap堆),生命週期與虛擬機器相同。該區域內物件沒有明確的死亡時間,所以GC的主要場所就在於Heap堆。

執行緒生命週期
在這裡插入圖片描述
幾種GC模式:

  1. Serial:最早的GC收集器,單執行緒,停頓時間長
    Serial:新生代,複製刪除
    Serial old:老年代,標記清理
  2. Parallel scavenge:多執行緒同時執行,注重吞吐量,可設定GC的自適應策略。虛擬機器自適應設定GC引數,包括老年代與新生代大小比例等。
    Parallel scavenge 配合Parallel old實現高吞吐量。
  3. ParNew:新生代收集器,多執行緒,其餘和Serial類似
    特點:配合CMS使用。
  4. CMS:併發標記掃描,標記清除。
    特點:兵法標記,GC執行緒和使用者執行緒可同時執行,大大減少了停頓時間,平且在CPU數較多是,效能提升明顯。
    缺點:佔用CPU時間,降低了吞吐量;產生不連續空間;產生Concurrent Mode Fail異常。

Concurrent Mode Fail:CMS GC的過程中同時業務執行緒將物件放入老年代,而此時老年代空間不足,或者在做Minor GC的時候,新生代Survivor空間放不下,需要放入老年代,而老年代也放不下而產生的。產生Concurrent Mode Failure的時候,收集器會降級成Serial Old收集器,停頓業務執行緒,等待GC收集完成。這將導致停頓時間增加,降低效能。通過調低CMSInitiatingOccupancyFraction引數,降低觸發CMS的老年代負載因子,讓CMS提前進入GC收集,確保分配資源時有足夠的空間。

產生不連續空間:可以通過這是以下兩個引數控制
-XX:UseCMSCompactAtFullCollection:在full GC錢,使用標記-整理演算法
-XX:CMSFullGCBeforeCompaction=5:執行多少次非整理的Full GC後,進行一次壓縮full GC。預設值為0,即每次Full GC都執行壓縮full GC,整理記憶體碎片。

多執行緒

執行緒池中的幾個重要的引數
corePoolSize :核心執行緒數量
maximumPoolSize :執行緒最大執行緒數
workQueue :阻塞佇列,儲存等待執行的任務 很重要 會對執行緒池執行產生重大影響
keepAliveTime :執行緒沒有任務時最多保持多久時間終止
unit :keepAliveTime的時間單位
threadFactory :執行緒工廠,用來建立執行緒
rejectHandler :當拒絕處理任務時的策略

執行緒池處理流程

阻塞佇列:

  • ArrayBlockingQueue:一種基於陣列結構的有界佇列。先進先出原則。
  • LinkedBlockingQueue:基於連結串列的無界組賽佇列。先進先出原則,吞吐量高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用這個佇列。
  • SynchronousQueue:不儲存元素的阻塞佇列,每個插入操作都必須零一個執行緒呼叫移除操作。吞吐量高於LinkedBlockingQueue。Executors.newCachedThreadPool()使用這個佇列。
  • PriorityBlockingQueue:具有優先順序的無界阻塞佇列

飽和策略(Rejected Execution Handler)

  • AbortPolicy:直接丟擲異常(預設策略)
  • CallerRunsPolicy:只呼叫所有執行緒來執行任務
  • DiscardOldestPolicy:丟棄最老任務,加入該任務
  • DiscardPolicy:丟棄當前任務

Executor框架成員-ThreadPoolExecutor

  • FixedThreadPool:為了滿足資源管理的需求,而需要限制當前執行緒數量的應用場景。適用於負載比較重的伺服器。
  • SingleThreadPool:適用於需要保證順序地執行各個任務;並且在任意時間點,不會有多個執行緒是活動的應用場景
  • CachedThreadPool:無界執行緒池,適用於執行很多的短期非同步的小程式,或者負載較輕的伺服器

關閉執行緒

  • SHUTDOWN:將執行緒池狀態設定為shutdown狀態,然後所有沒有正在執行任務的執行緒。正在執行任務的執行緒,會在任務執行完後終止。
  • SHUTDOWNNOW:首先將執行緒池狀態設定為STOP,然後嘗試停止所有正在執行獲暫停的執行緒。立即中斷。

原理:均為便利執行緒池中的工作執行緒,並逐個呼叫執行緒的interrupt方法來終端。

只要呼叫其中一個方法,isShutdown方法就會返回true,所有任務都完成關閉,才表示執行緒池關閉成功,isTerminad方法才會返回true。

類載入過程

在Java中,類裝載器把一個類裝入Java虛擬機器中,要經過三個步驟來完成:裝載、連結和初始化,其中連結又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:

  • 裝載:查詢和匯入類或介面的二進位制資料;
  • 連結:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;
  • 校驗:檢查匯入類或介面的二進位制資料的正確性;
  • 準備:給類的靜態變數分配並初始化儲存空間;
  • 解析:將符號引用轉成直接引用;
  • 初始化:啟用類的靜態變數的初始化Java程式碼和靜態Java程式碼塊。

Class.forName 和 ClassLoader.loadClass 的區別
Class.forName(className)方法,其實呼叫的方法是Class.forName(className,true,classloader);注意看第2個boolean引數,它表示的意思,在loadClass後必須初始化。比較下我們前面準備jvm載入類的知識,我們可以清晰的看到在執行過此方法後,目標物件的 static塊程式碼已經被執行,static引數也已經被初始化。

再看ClassLoader.loadClass(className)方法,其實他呼叫的方法是ClassLoader.loadClass(className,false);還是注意看第2個 boolean引數,該參數列示目標物件被裝載後不進行連結,這就意味這不會去執行該類靜態塊中間的內容。

雙親委派模式

Synchronized與lock方法

synchronized:互斥,可重入,不可中斷,非公平的隱式鎖實現

synchroniz關鍵字在普通方法和靜態方法上的區別:

synchronized修飾不加static的方法,鎖是加在單個物件上,不同的物件沒有競爭關係;

synchronized修飾加了static的方法,鎖是載入類上,這個類所有的物件競爭一把鎖。

區別

  • Lock是一個介面,是JDK層面的實現,而Synchronized是java中的關鍵字,是Java的內建特性,是jvm層面的實現。
  • synchronized在發生異常時,會自動釋放執行緒佔有的鎖,從而避免死鎖。lock發生異常是,需要顯式的呼叫unlock方法釋放鎖。例如在finally塊中釋放鎖,從而避免死鎖。
  • lock可以讓等待的執行緒響應中斷,而synchronized不能響應中斷。
  • lock可以判斷是否成功獲取鎖,而synchronized不能
  • lock可以提高多個執行緒的讀操作效率。

ReentrantLock
lock鎖中比較常見的就是ReentrantLock,可重入鎖。

鎖優化
針對synchronized關鍵字,jvm做了一系列鎖優化,使得synchronized的加鎖效率和lock相當。
鎖優化的策略包括:

  • 自旋鎖/適應性自旋鎖
  • 消除鎖
  • 鎖粗化
  • 輕量級鎖
  • 偏向鎖

另外,對加鎖的過程也做了優化。
對資源加鎖時,預設加偏向鎖,即一個樂觀鎖。認為當前資源不存在爭搶,執行緒獲取鎖後,一直持有該鎖,直到有其他執行緒申請獲取該鎖。
當有執行緒爭搶的時候,終止偏向鎖,膨脹為輕量級鎖。採用CAS方式置換鎖資源持有者的標誌位(標誌位記錄執行緒ID)。採用CAS獲取鎖資源,而不是掛起執行緒,這裡其實就是自旋鎖。自旋一段時間仍然獲取不到鎖資源,也會膨脹為重量級鎖。這個時間是系統設定的,也可以讓鎖來根據每次從開始自旋到獲取到鎖資源的時間,來自適應設定這個等待時間。這時候,就是一個自適應自旋鎖。
當兩個以上的執行緒同時爭搶鎖資源的時候,鎖會膨脹為重量級鎖。爭搶的執行緒進入阻塞佇列,並掛起。

jdk代理

1、原理

jdk靜態代理實現比較簡單,一般是直接代理物件直接包裝了被代理物件。

jdk動態代理是介面代理,被代理類A需要實現業務介面,業務代理類B需要實現InvocationHandler介面。jdk動態代理會根據被代理物件生成一個繼承了Proxy類,並實現了該業務介面的jdk代理類,該類的位元組碼會被傳進去的ClassLoader載入,建立了jdk代理物件例項,

jdk代理物件例項在建立時,業務代理物件例項會被賦值給Proxy類,jdk代理物件例項也就有了業務代理物件例項,同時jdk代理物件例項通過反射根據被代理類的業務方法建立了相應的Method物件m(可能有多個)。當jdk代理物件例項呼叫業務方法,如proxy.addUser();這個會先把對應的m物件作為引數傳給invoke()方法(就是invoke方法的第二個引數),呼叫了jdk代理物件例項的invoke()回撥方法,在invoke方法裡面再通過反射來呼叫被代理物件的因為方法,即result = method.invoke(target, args);。

cglib動態代理是繼承代理,通過ASM位元組碼框架修改位元組碼生成新的子類,重寫並增強方法的功能。

2、優缺點
jdk靜態代理類只能為一個被代理類服務,如果需要代理的類比較多,那麼會產生過多的代理類。jdk靜態代理在編譯時產生class檔案,執行時無需產生,可直接使用,效率好。
jdk動態代理必須實現介面,通過反射來動態代理方法,消耗系統效能。但是無需產生過多的代理類,避免了重複程式碼的產生,系統更加靈活。
cglib動態代理無需實現介面,通過生成子類位元組碼來實現,比反射快一點,沒有效能問題。但是由於cglib會繼承被代理類,需要重寫被代理方法,所以被代理類不能是final類,被代理方法不能是final。

因此,cglib的應用更加廣泛一點。

典型資料結構

ThreadLocal資料結構

jdk1.8之後,hashMap的改進之處
1,節點table的定位,廢棄了原有的indexfor方法,使用key的hash值和長度-1的二進位制數做按位與運算,提高了定位的效率。採用這種方式,同樣的hash值,在resize之後,位置可能不變,或者位置變為當前位置加長度的一半。hashmap擴容時,預設擴容為當前的兩倍。
2,在解決hash衝突的時候,1.8之前,採用的是連結串列形式。連結串列形式的查詢效率是O(n)。在n變大時,查詢效率降低。因此在1.8之後,當連結串列元素超過TREEIFY_THRESHOLD時,會裂變成紅黑樹。紅黑樹的查詢複雜度為O(logn),在n增大時,效率提升顯著。
當n>TREEIFY_THRESHOLD(預設8)時,連結串列=》紅黑樹
當n<UNTREEIFY_THRESHOLD(預設6)時,紅黑樹=》連結串列
3,jdk1.8中用Node替代了Entry。
static class Node<K,V> implements Map.Entry<K,V> {}
3,concurrentHashMap除了上述改進外,廢棄了原來分割槽加鎖的概念,採用了CAS非阻塞的形式,保證了資料一致性。

sleep wait的區別

java IO模型

IO分類

IO資料結構劃分

  • 按資料流向:輸入流,輸出流
  • 按傳輸單位:位元組流,字元流
  • 按角色:結點流,處理流(常見的讀寫流都是結點流,處理流用的最多的就是帶快取的BufferedInputStream等)

四大基類

根據流的流向以及操作的資料單元不同,將流分為了四種型別,每種型別對應一種抽象基類。這四種抽象基類分別為:InputStream,Reader,OutputStream以及Writer。

問題排查

記憶體洩漏如何排查
1,系統出現執行緩慢,或者爆出OOM異常,先分析是否是因為記憶體洩漏引起。jvm堆大小設定不合理,過小的時候,也會因為無法給大物件分配資源而爆出OOM;或者因為年輕代分配不了物件,導致短暫存在的大物件分配到老年代,帶來頻繁full GC也是執行緩慢的原因。所以首先確認,是不是因為出現了OOM。
2,如果確定是出現了記憶體洩漏,導致了記憶體溢位,參照一下步驟排查。
a,登入Linux系統,使用jps或者ps命令,獲取正在執行的jvm的執行緒id,即PID

使用jps:jps -l
使用ps:ps aux | grep java

b,使用jstate命令檢視jvm執行緒的狀態。主要是GC狀態,以及各個記憶體模組的佔用比例。

jstat -gcutil 20954 1000 # 每1000毫秒檢視一次執行緒20954的gc資訊

c,如果確實是記憶體模組在短時間內達到飽和,並且居高不下,那基本上就可以確定是記憶體洩漏了。
d,使用jmap命令,轉儲堆疊資訊,分析記憶體

jmap -histo:live 3514 | head -7

e,使用分析工具檢視堆疊資訊。可使用jdk自帶的visualVM分析dump檔案。該工具在jkd的bin目錄下。

CPU過載,如何排查
1,找到最耗CPU的程式

  • 執行top -c ,顯示程式執行資訊列表
  • 鍵入P (大寫p),程式按照CPU使用率排序

2,找到最耗CPU的執行緒(假設1中查到的程式pid為10765 )

  • top -Hp 10765 ,顯示一個程式的執行緒執行資訊列表
  • 鍵入P (大寫p),執行緒按照CPU使用率排序

3,將執行緒PID轉化為16進位制
假設2中查到的執行緒id為10804,其十六進位制為2a34。
之所以要轉化為16進位制,是因為堆疊裡,執行緒id是用16進製表示的。

4,檢視堆疊資訊
jstack 10765 | grep ‘0x2a34’ -C5 --color
通過列印並過濾執行緒的堆疊資訊,就可以看到執行緒在執行的任務,然後具體分析程式碼。

MySQL資料庫

儲存模式InnoDB和MyISAM

專案InnoDBMyISAM
儲存形式table.frm 表結構
table.ibd 資料和索引
table.frm 表結構
table.myd 資料
table.myi 索引
鎖的粒度行級鎖
間隙鎖策略,防止幻讀
MVCC控制事務,支援併發
實現四個事務隔離級別
表級鎖,併發寫入效率低
讀操作相對快速
事務典型的事務型儲存引擎不支援事務
索引B+樹實現聚簇索引
主鍵索引的葉子節點存放資料
輔助索引的葉子節點存放主鍵值
B+樹實現的非聚簇索引
主鍵索引和輔助索引的葉子節點存放的都是資料的物理儲存地址
儲存特點基於聚簇索引:對主鍵的查詢具有很高的效能非聚簇索引
支援全文索引,壓縮空間函式,延遲更新索引健等;表空間小,基於壓縮-》效能高

由於索引結構的關係,InnoDB的索引查詢,最終都是要走到主鍵索引。而MyISAM則不需要。查詢到key對應的葉子節點後,就可以直接通過葉子節點存放的實體地址,拿到資料。

索引結構,B+樹

B+數索引建立,B+樹原理,

聚簇索引
索引的順序即為資料的物理儲存順序。即搜尋的葉子節點存放的是資料本身。
非聚簇索引
索引的順序與資料的儲存實體地址無關。索引的葉子節點存放的是資料指標。

索引優化

1,經常用作過濾器,或者查詢頻率較高的欄位上建立索引
2,在sql語句中,經常進行group by或者order by的欄位上建立索引
3,在取值範圍較小的列舉類欄位上避免建立索引,如性別
4,對於經常存取的欄位避免建立索引
5,用於連結的列(主鍵/外來鍵)建立索引
6,在經常查詢的多個列上,建立複合索引,先後順序按使用頻率來定。

MVCC多版本控制

對錶資料的修改,增加,或者刪除的時候,會對根據事務id,對資料生成多個版本,並通過redo,undo日誌來記錄操作。在事務提交之前,對於資料表的讀取,都還是取原來的版本。等到資料提交後,其他事務才能讀取到提交後的資料。

事務隔離機制

MySQL資料庫為我們提供的四種隔離級別:

① Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生。(鎖表)
  ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
(mvcc每次select查詢之後使用第一個查詢的read view 避免讀取到別人提交的資料,)
  ③ Read committed (讀已提交):可避免髒讀的發生,會讀取到別人提交的事務資料。
(mvcc每次select同一條資料都會有一個新的read view,也就是會有多個)
  ④ Read uncommitted (讀未提交):最低階別,任何情況都無法保證。

分表分庫

資料量達到一定級別之後,需要進行分表分庫,以達到資料庫快速響應的目標。
分表:資料縱向擴充套件,即按照某個既定的規則,根據主鍵索引來把資料對映到不同的表中。分表後,對於查詢分頁有一定影響。比較好的解決方案是每次查詢都帶上分表用的id來查詢。否則,對於資料的查詢需要排序和分頁的時候,很難處理。有一種解決方案是,維護一個主鍵和搜尋條件的中間表,只附帶少數常用欄位。在排序分頁後,拿著主鍵id去查詢詳細資料。或者採用分表工具來實現。
分庫:分庫屬於一個橫向擴充套件,一般按照業務來劃分。比如系統中商品資料量達到一定程度,可考慮分出一個商品庫,然後商品分成20個子表。通過這種方式,達到商品資訊查詢的快速響應。

常見問題:

  1. 全域性唯一id生成。
    • 自增id:通過配置每張表的起始id和自增步距,確保id唯一。缺點:分表後,難以擴充套件;不能保證嚴格遞增,只能保證趨勢遞增。
    • 發號器服務:所有的資料庫請求都要走發號器,發號器容易成為效能瓶頸。解決方案:可以批量下發。
    • UUID:全域性唯一,支援廣泛,簡單易用。缺點:無業務含義,id隨機,不能保證遞增。
    • 雪花演算法:在高位採用時間毫秒數,保證趨勢遞增。通過配置各個位置的不同含義,保證全域性唯一。擴充套件性好。缺點:由於各機器時鐘存在差異,不保證嚴格遞增,也是趨勢遞增。
  2. 查詢聚合問題
    • 業務層面避免跨頁查詢:通過要求查詢必須帶著分頁主鍵來控制查詢結果不跨頁。
    • 技術上,維護一個分表欄位對映表:例如按主鍵分表,每次查詢分頁時,先在對映表中查詢到分頁後的主鍵id,然後通過主鍵id去各個分表中查詢具體資料。對映表欄位要簡單,不能過多,否則增加維護成本。缺點就是增加系統複雜度,增加維護成本。

常見元件:
簡單易用的元件:

  • 噹噹sharding-jdbc
  • 蘑菇街TSharding

強悍重量級的中介軟體:

  • sharding
  • TDDL Smart Client的方式(淘寶)
  • Atlas(Qihoo 360)
  • alibaba.cobar(是阿里巴巴(B2B)部門開發)
  • MyCAT(基於阿里開源的Cobar產品而研發)
  • Oceanus(58同城資料庫中介軟體)
  • OneProxy(支付寶首席架構師樓方鑫開發)
  • vitess(谷歌開發的資料庫中介軟體)

預編譯,防注入

通過資料庫對sql語句的預編譯,將傳入的引數作為一個整體屬性,寫入到篩選的引數中,而不會被資料庫解釋為sql語句,從而防止sql注入。

explain查詢計劃

explain查詢計劃中,幾個核心的欄位含義及列舉:
1,select_type: 查詢的型別

  • SIMPLE: 簡單查詢,不包含子查詢和union
  • PRIMRARY: 包含子查詢時的最外層查詢; 使用union時的第一個查詢
  • UNION: 包含union的查詢中非第一個查詢
  • DEPENDENT UNION: 與 UNION 相同,但依賴外層查詢的結果
  • SUBQUERY: 子查詢
  • DEPENDENT SUBQUERY: 依賴外層查詢的子查詢
  • DERIVED: 用於 FROM 中的子查詢

2,type: 搜尋資料的方法

  • null: 不需要訪問索引和表即可完成, 示例: SELECT 1;
  • const: 表中僅有一行匹配,在分解查詢計劃時直接將其讀出作為常量使用。system 是 const 型別的特例。另外,例如直接按照主鍵查詢,也是const
  • eq_ref: 使用 PRIMARY KEY 或 UNIQUE KEY 進行關聯查詢。
  • ref: 使用允許重複的索引進行查詢,即使用普通索引查詢
  • range: 使用索引進行範圍查詢
  • index: 在索引上進行順序掃描。常見於在多列索引中未使用最左列進行查詢。
  • all: 掃描全表,最壞的情況

3,possible_keys: 可能使用的索引
4,key: 最終決定要使用的key
5,key_len: 查詢索引使用的位元組數。通常越少越好
6,ref: 查詢的列或常量
7,rows: 需要掃描的行數,估計值。通常越少越好
8,extra: 額外的資訊

  • using filesort: 查詢時執行了排序操作而無法使用索引排序。雖然名稱為’file’但操作可能是在記憶體中執行的,取決是否有足夠的記憶體進行排序。應儘量避免這種filesort出現。
  • using temporary: 使用臨時表儲存中間結果,常見於ORDER BY和GROUP BY語句中。臨時表可能在記憶體中也可能在硬碟中,應儘量避免這種操作出現。
  • using index: 索引中包含查詢的所有列不需要查詢資料表(回表)。可以加快查詢速度。
  • using where: 使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給客戶端
  • using index condition: 索引條件推送(MySQL 5.6 新特性),伺服器層將不能直接使用索引的查詢條件推送給儲存引擎,從而避免在伺服器層進行過濾。
  • distinct: 優化distinct操作,查詢到匹配的資料後停止繼續搜尋

Redis相關-NoSql

redis支援的資料型別包括:字串、列表、集合、雜湊表、有序集合。
在這裡插入圖片描述
Redis為什麼這麼快
1、完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似於 HashMap,HashMap 的優勢就是查詢和操作的時間複雜度都是O(1);
2、資料結構簡單,對資料操作也簡單,Redis 中的資料結構是專門進行設計的;
3、採用單執行緒,避免了不必要的上下文切換和競爭條件,也不存在多程式或者多執行緒導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗;
4、使用多路 I/O 複用模型,非阻塞 IO;
5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis 直接自己構建了 VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求;

redis持久化
AOF:
RDB:

redis叢集部署
一致性hash演算法
判定雜湊演算法好壞的四個定義
1、平衡性(Balance):平衡性是指雜湊的結果能夠儘可能分佈在所有的緩衝(Cache)中去,這樣可以使得所有的緩衝空間得到利用。很多雜湊演算法都能夠滿足這一條件。
2、單調性(Monotonicity):單調性是指如果已經有一些內容通過雜湊分派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的結果應該能夠保證原有已分配的內容可以被對映到原有的或者新的緩衝中去,而不會對映到舊的緩衝集合中的其他緩衝區。
3、分散性(Spread):在分散式環境中,終端有可能看不到所有的緩衝,而只能看到其中的一部分。當終端希望通過雜湊過程將內容對映到緩衝上去,由於不同終端所見的緩衝範圍有可能不同,從而導致雜湊的結果不一致,最終的結果是相同的內容被不同的終端對映到不同的緩衝區中。這種情況顯然是應該避免的,因為它導致相同內容被儲存到不同緩衝中去,降低了系統儲存的效率。分散性的定義就是上述情況發生的嚴重程度。好的雜湊演算法應該能夠儘量避免不一致的情況發生,也就是儘量降低分散性。
4、負載(Load):負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容對映到不同的緩衝區中,那麼對於一個特定的緩衝區而言,也可能被不同的使用者對映到不同的內容。與分散性一樣,這種情況也是應當避免的,因此好的雜湊演算法應能夠儘量降低緩衝的負荷。

一致性hash使用 hashcode & (len - ) 按位與的計算來快速定位節點位置。通過這個方式取代hashCode對len取模的方式,使得hash演算法滿足單調性,同時降低負載。

一致性hash演算法
在這裡插入圖片描述

redis的分散式鎖實現
何為分散式鎖?
分散式鎖如何實現?基於redis
詳情參照:
分散式鎖之Redis實現

redis實現分散式鎖,當獲取鎖失敗時如何處理。
1,首先判斷處理任務的時耗,如果是短暫任務,比如50ms內能完成,那可以考慮自旋等待。讓當前執行緒等待50ms後再次請求鎖。
2,可以設定一個閾值,自旋次數達到閾值後仍然沒有獲取到鎖,可以根據任務型別來採用不同策略處理。例如,如果是查詢任務,則可以直接丟棄任務,然後丟擲異常。系統查詢失敗後,重新查詢即可。如果是資料處理任務,則可以建立阻塞佇列來儲存任務。
3,阻塞佇列的建立有很多中形式,如果採用redis作為分散式鎖,則可以直接在redis中建立阻塞佇列,講任務序列化後儲存在redis佇列中。資料型別可選list來實現。

Spring框架

Ioc與DI

參照文章:
深入講講spring的IoC的實現方式

AOP程式設計

AOP的幾個核心概念

  1. 橫切關注點

軟體系統,可看作由一組關注點組成。其中,直接的業務關注點,是核心關注點。而為核心關注點提供服務的,就是橫切關注點。一般情況下,切面中封裝的方法都是橫切關注點。

  1. 切面(aspect)

封裝各種橫切關注點的類,就是切面。

  1. 連線點(joinpoint)

所謂連線點是指那些被攔截到的點。就是spring配置檔案中的切入點表示式中需要攔截的所有的方法。

  1. 切入點(pointcut)

所謂切入點是指我們要對那些joinpoint進行攔截的定義。
在類裡面可以有很多方法被增強,比如實際操作中,需要增強類裡面的add方法和update方法,那麼當前被增強的方法稱為切入點。

  1. 通知(advice)

所謂通知是指攔截到joinpoint之後所要做的事情(增強的程式碼)就是通知。通知分為前置通知、後置通知、異常通知、最終通知、環繞通知。
@Before前置通知:在方法執行前執行
@After後置通知:在方法執行後執行
@After-throwing異常通知:在執行核心關注點過程中,如果丟擲異常則會執行
@After-returning返回通知:在後置之前執行(如果出現異常,不執行)–在方法正常執行通過之後執行的通知
@Around環繞通知:在方法之前和之後執行

6、目標物件(Target)

代理的目標物件,即增強方法所在的類。

7、織入(weave)

將切面應用到目標物件並導致代理物件建立的過程或者說把增強用到類的過程

8、引入(introduction)

一般不適用
在不修改程式碼的前提下,引入可以在執行期為類動態地新增一些方法或欄位

切點pointCut定義

切點表示式
切點的功能是指出切面的通知應該從哪裡織入應用的執行流。切面只能織入公共方法。

在Spring AOP中,使用AspectJ的切點表示式語言定義切點其中excecution()是最重要的描述符,其它描述符用於輔助excecution()。

excecution()的語法如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

這個語法看似複雜,但是我們逐個分解一下,其實就是描述了一個方法的特徵:

問號表示可選項,即可以不指定。

excecution(* com.tianmaying.service.BlogService.updateBlog(…))

modifier-pattern:表示方法的修飾符
ret-type-pattern:表示方法的返回值
declaring-type-pattern:表示方法所在的類的路徑
name-pattern:表示方法名
param-pattern:表示方法的引數
throws-pattern:表示方法丟擲的異常

在切點中引用Bean
Spring還提供了一個bean()描述符,用於在切點表示式中引用Spring Beans。例如:

excecution(* com.tianmaying.service.BlogService.updateBlog(…)) and bean(‘tianmayingBlog’)
這表示將切面應用於BlogService的updateBlog方法上,但是僅限於ID為tianmayingBlog的Bean。

也可以排除特定的Bean:

excecution(* com.tianmaying.service.BlogService.updateBlog(…)) and !bean(‘tianmayingBlog’)

自定義註解

自定義一個註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Super {
}

引數說明
@Target是用來標記作用目標

@Target(ElementType.TYPE)——介面、類、列舉、註解
@Target(ElementType.FIELD)——欄位、列舉的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法引數
@Target(ElementType.CONSTRUCTOR) ——建構函式
@Target(ElementType.LOCAL_VARIABLE)——區域性變數
@Target(ElementType.ANNOTATION_TYPE)——註解
@Target(ElementType.PACKAGE)——包

@Retention表示註解的保留位置

RetentionPolicy.SOURCE:這種型別的Annotations只在原始碼級別保留,編譯時就會被忽略,在class位元組碼檔案中不包含。
RetentionPolicy.CLASS:這種型別的Annotations編譯時被保留,預設的保留策略,在class檔案中存在,但JVM將會忽略,執行時無法獲得。
RetentionPolicy.RUNTIME:這種型別的Annotations將被JVM保留,所以他們能在執行時被JVM或其他使用反射機制的程式碼所讀取和使用。

@Document:說明該註解將被包含在javadoc中
@Inherited:說明子類可以繼承父類中的該註解

常用的注入方式

構造器注入
setter方法注入

  • byName
  • byType

介面注入

實現方式
@Resouce
@Inject
@Autowired
xml

Spring如何處理迴圈引用

spring通過預設使用單例模式和setter方法注入來避免迴圈引用出錯。使用單例模式,在下次引用時,可以返回之前建立的例項。使用setter方法注入,可以先建立出例項,然後再進行屬性的注入。

Spring事務傳播機制

spring在TransactionDefinition介面中定義了七個事務傳播行為:

  • propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中。這是最常見的選擇,也是spring的預設級別。
  • propagation_supports:支援當前事務,如果沒有當前事務,就以非事務方法執行。
  • propagation_mandatory:使用當前事務,如果沒有當前事務,就丟擲異常。
  • propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
  • propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
  • propagation_never:以非事務方式執行操作,如果當前事務存在則丟擲異常。
  • propagation_nested:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作

spring事務失效場景:

  1. 首先使用如下程式碼 確認你的bean 是代理物件嗎?
    必須是Spring定義(通過XML或註解定義都可以)的Bean才接受事務。直接new出來的物件新增事務是不起作用的。

  2. 如使用mysql且引擎是MyISAM,則事務會不起作用,原因是MyISAM不支援事務,可以改成InnoDB;

  3. @Transactional 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯,事務也會失效。這一點由Spring的AOP特性決定的;

  4. 如果呼叫的方法沒加@Transactional,那麼被呼叫的方法家了@Transactional,也不會回滾。自呼叫問題,自身呼叫內部方法時,預設編譯成this.method(),而不會重新生成代理類,自然不會走事務代理。

解決方案:
一局發生場景,選擇對應的解決方案

spring bean的生命週期

生命週期如下:

  • 例項化bean物件(通過構造方法或者工廠方法)
  • 設定物件屬性(setter等)(依賴注入)
  • 完成配置
    • 如果Bean實現了BeanNameAware介面,工廠呼叫Bean的setBeanName()方法傳遞Bean的ID。(和下面的一條均屬於檢查Aware介面)
    • 如果Bean實現了BeanFactoryAware介面,工廠呼叫setBeanFactory()方法傳入工廠自身
  • 初始化
    • 將Bean例項傳遞給Bean的前置處理器的postProcessBeforeInitialization(Object bean, String beanname)方法
    • 呼叫Bean的初始化方法
    • 將Bean例項傳遞給Bean的後置處理器的postProcessAfterInitialization(Object bean, String beanname)方法
  • 使用Bean
  • 容器關閉之前,呼叫Bean的銷燬方法

開源框架

RocketMq

RocketMQ在面試中那些常見問題及答案+彙總

Dubbo

Dubbo常見面試題
Dubbo原始碼環境搭建

負載均衡策略:
1,隨機策略
2,輪詢策略
3,最少使用策略
4,一致性hash策略

Dubbo預設選擇hession作為序列化工具,Netty作為傳輸工具
常用的序列化方式有:
Dubbo序列化:高效二進位制序列化,不成熟,不建議生產使用
hession2:高效二進位制序列化,且序列化後的資料較小,適合傳輸
json:文字序列化,序列化之後,還要轉換成二進位制流來傳輸,效能不好
Java序列化:java自帶的序列化工具,效能不理想。

關於hession序列化

  • 靜態屬性不能被序列化;
  • transient關鍵字修飾的屬性不能被序列化;
  • 父類和子類中不能有相同名稱的屬性,否則序列化時父類屬性會覆蓋子類屬性。讀取時按子類屬性讀取。

Dubbo 服務註冊與暴露
Dubbo(二十二)–dubbo-原理-服務暴露流程
通過ServiceBean來實現服務的註冊與暴露。ServiceBean實現了兩個重要的機制:
InitializingBean 和監聽ContextRefreshedEvent事件。
InitializingBean:當元件建立完物件以後會呼叫InitializingBean的唯一的方法afterPropertiesSet,也就是在屬性設定完以後來回撥這個方法。afterPropertiesSet就是把spring配置檔案中dubbo的標籤內容儲存起來。儲存在ServiceBean裡面。
監聽ContextRefreshedEvent事件:當我們ioc容器整個重新整理完成,也就是ioc容器裡面所有物件都建立完成以後來回撥方法onApplicationEvent(ContextRefreshedEvent event)。在這個方法裡面會把上一個機制中獲取到的dubbo配置包括地址,埠,服務名稱等資訊組成url,暴露給註冊中心。同時會在底層拉起一個netty伺服器,監聽本機的相應埠。在暴露服務的時候,要獲取到invoker(下圖getInvoker()),再用exporter來暴露執行器。Exporter會用兩個,dubboExporter和registryExporter。DubboExporter來開啟netty伺服器,registryExporter用來註冊,服務(執行器)和對應的url地址,註冊到登錄檔裡。

MyBatis

MyBatis 是一款優秀的持久層框架,它支援自定義 SQL、儲存過程以及高階對映。MyBatis 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄。

處理流程

在這裡插入圖片描述

  • 第一步通過SqlSessionFactoryBuilder建立SqlSessionFactory
  • 第二步通過SqlSessionFactory建立SqlSession
  • 第三步通過SqlSession拿到Mapper物件的代理
  • 第四步通過MapperProxy呼叫Maper中相應的方法,運算元據庫

mybatis 一二級快取

一級快取
Mybatis對快取提供支援,但是在沒有配置的預設情況下,它只開啟一級快取,一級快取只是相對於同一個SqlSession而言。所以在引數和SQL完全一樣的情況下,我們使用同一個SqlSession物件呼叫一個Mapper方法,往往只執行一次SQL,因為使用SelSession第一次查詢後,MyBatis會將其放在快取中,以後再查詢的時候,如果沒有宣告需要重新整理,並且快取沒有超時的情況下,SqlSession都會取出當前快取的資料,而不會再次傳送SQL到資料庫。
一級快取生命週期和sqlsession一致。隨SqlSession的建立而建立,隨SqlSession的銷燬而銷燬。SqlSession的任何insert,update,delete操作都會清空SqlSession中的一級快取。只是清空資料,物件仍然存在,沒有被銷燬。

二級快取
MyBatis的二級快取是Application級別的快取,它可以提高對資料庫查詢的效率,以提高應用的效能。SqlSessionFactory層面上的二級快取預設是不開啟的,二級快取的開啟需要進行配置,實現二級快取的時候,MyBatis要求返回的POJO必須是可序列化的。 也就是要求實現Serializable介面,配置方法很簡單,只需要在對映XML檔案配置就可以開啟快取了。

1,在mapper檔案中開啟本mapper範圍內的二級快取

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yihaomen.mybatis.dao.StudentMapper">
    <!--開啟本mapper的namespace下的二級快取-->
    <!--
        eviction:代表的是快取回收策略,目前MyBatis提供以下策略。
        (1) LRU,最近最少使用的,一處最長時間不用的物件
        (2) FIFO,先進先出,按物件進入快取的順序來移除他們
        (3) SOFT,軟引用,移除基於垃圾回收器狀態和軟引用規則的物件
        (4) WEAK,弱引用,更積極的移除基於垃圾收集器狀態和弱引用規則的物件。這裡採用的是LRU,
                移除最長時間不用的對形象

        flushInterval:重新整理間隔時間,單位為毫秒,這裡配置的是100秒重新整理,如果你不配置它,那麼當
        SQL被執行的時候才會去重新整理快取。

        size:引用數目,一個正整數,代表快取最多可以儲存多少個物件,不宜設定過大。設定過大會導致記憶體溢位。
        這裡配置的是1024個物件

        readOnly:只讀,意味著快取資料只能讀取而不能修改,這樣設定的好處是我們可以快速讀取快取,缺點是我們沒有
        辦法修改快取,他的預設值是false,不允許我們修改
    -->
    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

</mapper>

2,在配置檔案中,開啟全域性的二級快取

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--這個配置使全域性的對映器(二級快取)啟用或禁用快取-->
        <setting name="cacheEnabled" value="true" />
        .....
    </settings>
    ....
</configuration>

關於二級快取:

  • 對映語句檔案中的所有select語句將會被快取。
  • 對映語句檔案中的所欲insert、update和delete語句會重新整理快取。
  • 快取會使用預設的Least Recently Used(LRU,最近最少使用的)演算法來收回。
  • 快取會被視為是read/write(可讀/可寫)的快取,意味著物件檢索不是共享的,而且可以安全的被呼叫者修改,不干擾其他呼叫者或執行緒所做的潛在修改。

二級快取之所以要求所有的返回pojo都是可序列化的,是因為二級快取的儲存媒介不限於系統記憶體,也可以是memcached,OsCached等三方快取庫。如果是基於記憶體的二級快取,可以不用實現Serializable。

通訊相關

HTTP協議

三次握手,四次揮手

HTTPS加密協議

SSL , TLS

公共知識模組

設計模式的六大原則

設計模式中心思想:高內聚、低耦合
一,單一職責原則
單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。
二,開閉原則
開閉原則(Open-Closed Principle, OCP):一個軟體實體應當對擴充套件開放,對修改關閉。即軟體實體應儘量在不修改原有程式碼的情況下進行擴充套件。
三,里氏替換原則
里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的物件。
四,依賴倒置原則
依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對介面程式設計,而不是針對實現程式設計。
五,介面隔離原則
介面隔離原則(Interface Segregation Principle, ISP):使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些它不需要的介面。
六,迪米特法則
迪米特法則(Law of Demeter, LoD):一個軟體實體應當儘可能少地與其他實體發生相互作用。迪米特法則又稱為最少知識原則(LeastKnowledge Principle, LKP)。

DDD模型理論

Domain Drived Design:領域驅動設計

分散式原理 AOP和BASE

CAP理論
CAP理論作為分散式系統的基礎理論,它描述的是一個分散式系統在以下三個特性中:

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分割槽容錯性(Partition tolerance)

實際應用中,系統最多能滿足其中兩個,無法做到兼顧。

BASE理論
分散式系統規範之BASE理論

IO分類與原理

IO分類

IO模型可分為五種:

  • blocking IO:阻塞型IO
    請求發出後,在請求得到響應前,一直阻塞,直到得到響應,然後繼續完成後續操作。
  • nonblocking IO:非阻塞型IO
    請求發出後,執行緒繼續處理後續命令。然後每隔一段時間輪詢資料是否處理好。
  • IO multiplexing:多路複用型IO,也稱事件驅動IO(event driven IO)
    多路複用,則是阻塞佇列的複數版。請求傳送完後,同時輪詢多路IO,看是否有資料準備好。
  • signal driven IO:訊號驅動型IO
    型號驅動型,也屬於非阻塞IO,但是不是每隔一段時間詢問資料是否準備好,而是資料準備好時會給執行緒傳送訊號,通知執行緒來處理資料。
  • asynchronous IO:非同步IO

IO模型參照
五種IO模型(詳解+形象例子說明)

雪花演算法:

SnowFlake 演算法,是 Twitter 開源的分散式 id 生成演算法。其核心思想就是:使用一個 64 bit 的 long 型的數字作為全域性唯一 id,生成該id時,給每一位上賦予一定的含義,從而生成各個機器都不重複的全域性唯一id。在分散式系統中的應用十分廣泛,且ID 引入了時間戳。
雪花演算法的原理和實現Java

zookeeper

zookeeper保證資料一致性

Paxos演算法
應用程式連線到任意一臺伺服器後提起狀態修改請求(也可以是獲得某個狀態所的請求),會將這個請求傳送給叢集中其他伺服器進行表決。如果某個伺服器同時收到了另一個應用程式同樣的修改請求,它可能會拒絕伺服器1的表決,並且自己也發起一個同樣的表決請求,那麼其他伺服器就會根據時間戳和伺服器排序進行表決。

ZooKeeper Atomic Broadcast
又稱ZAB協議,簡化版的Paxos演算法,zookeeper自己實現的保持資料一致性的協議。這個協議保證的是最終一致性。也就是說,zookeeper滿足CAP理論中的A(可用)和P(分割槽容錯)

訊息廣播具體步驟

1)客戶端發起一個寫操作請求。
2)Leader 伺服器將客戶端的請求轉化為事務 Proposal 提案,同時為每個 Proposal 分配一個全域性的ID,即zxid。
3)Leader 伺服器為每個 Follower 伺服器分配一個單獨的佇列,然後將需要廣播的 Proposal 依次放到佇列中取,並且根據 FIFO 策略進行訊息傳送。
4)Follower 接收到 Proposal 後,會首先將其以事務日誌的方式寫入本地磁碟中,寫入成功後向 Leader 反饋一個 Ack 響應訊息。
5)Leader 接收到超過半數以上 Follower 的 Ack 響應訊息後,即認為訊息傳送成功,可以傳送 commit 訊息。
6)Leader 向所有 Follower 廣播 commit 訊息,同時自身也會完成事務提交。Follower 接收到 commit 訊息後,會將上一條事務提交。

doc容器化部署

相關文章