2020年java開發最全面的面試題與答案詳解

ZooKeeper

1.CAP定理
答:CAP原則又稱CAP定理,指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。
2.ZAB協議
答:ZAB協議包括兩種基本的模式:崩潰恢復和消息廣播。當整個 Zookeeper 集羣剛剛啓動或者Leader服務器宕機、重啓或者網絡故障導致不存在過半的服務器與 Leader 服務器保持正常通信時,所有服務器進入崩潰恢復模式,首先選舉產生新的 Leader 服務器,然後集羣中 Follower 服務器開始與新的 Leader 服務器進行數據同步。當集羣中超過半數機器與該 Leader 服務器完成數據同步之後,退出恢復模式進入消息廣播模式,Leader 服務器開始接收客戶端的事務請求生成事物提案來進行事務請求處理。Zab協議 的全稱是 Zookeeper Atomic Broadcast (Zookeeper原子廣播)。
Zookeeper 是通過 Zab 協議來保證分佈式事務的最終一致性。是一種特別爲ZooKeeper的設計的崩潰可恢復的原子消息廣播算法
3.Leader選舉算法和流程
答: 概念:
· 1. Zookeeper的服務器三種角色:Leader,Follower,Observer。Leader提供讀和寫,Follower提供讀,參與過半投票,Observer只提供讀,不參與投票,可以提高讀性能。
· 2. ZXID,事務ID,用來唯一標識一次服務器狀態的變更
· 3. myid,服務器SID,一個數字,通過配置文件配置,唯一
選舉有兩種情況,一是服務器啓動的投票,二是運行期間的投票。

Redis

1.Redis的應用場景
答:redis適合的場景有:1、緩存;2、排行榜;3、計數器;4、分佈式會話;5、分佈式鎖;6、 社交網絡;7、最新列表;8、消息系統。9.共享Session 10.消息隊列系統

2.Redis支持的數據類型(必考)
答:1、string(字符串) 2、hash(哈希)3、list(列表)4、set(集合)5、zset(sorted set:有序集合)
3.zset跳錶的數據結構(必考)
答:ZSet(sorted set-有序集合 數據結構類似於Set結構,只是ZSet結構中,在set基礎上加入了一個score字段,通過利用score和index來進行相關的排序。每個元 素都會有一個分值(score),然後所有元素按照分值的大小進行排列,相當於是一個進行了排序的鏈表。
4.Redis的數據過期策略(必考)
答:1. 定時刪除 2. 惰性刪除 3.定期刪除
redis 過期策略是:定期刪除+惰性刪除

5.Redis的LRU過期策略的具體實現

答: 什麼是LRU: 簡而言之,就是每次淘汰最近最少使用的元素 。
1.get(key) - 如果該元素(總是正數)存在,將該元素移動到lru頭部,並返回該元素的值,否則返回-1。
2.set(key,value) - 設置一個key的值爲value(如果該元素存在),並將該元素移動到LRU頭部。否則插入一個key,且值爲value。如果在設置前檢查到,該key插入後,會超過cache的容量,則根據LRU策略,刪除最近最少使用的key。

6.如何解決Redis緩存雪崩,緩存穿透問題
答:1.Redis緩存雪崩 :數據未加載到緩存中,或者緩存同一時間大面積的失效,從而導致所有請求都去查數據庫,導致數據庫CPU和內存負載過高,甚至宕機。
解決思路:1.緩存的高可用性 2.緩存降級 3.redis備份和快速預熱 4.提前演練
2.緩存穿透問題 :緩存穿透是指查詢一個一不存在的數據。解決思路:如果查詢數據庫也爲空,直接設置一個默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫。設置一個過期時間或者當有值的時候將緩存中的值替換掉即可。
可以給key設置一些格式規則,然後查詢之前先過濾掉不符合規則的Key。

7.Redis的持久化機制(必考)
答:Redis 的持久化機制有兩種,第一種是快照,第二種是 AOF 日誌。
區別:
快照是一次全量備份,AOF 日誌是連續的增量備份。
快照是內存數據的二進制序列化形式,在存儲上非常緊湊,而 AOF 日誌記錄的是內存數據修改的指令記錄文本。
AOF 日誌在長期的運行過程中會變得無比龐大,數據庫重啓時需要加載 AOF 日誌進行指令重放,這個時間就會無比漫長,所以需要定期進行 AOF 重寫,給 AOF 日誌進行瘦身。

8.Redis的管道pipeline
答:Redis的管道可以在大量數據需要一次性操作完成的時候,使用Pipeline進行批處理,將一大隊的操作合併成一次操作,可以減少鏈路層的時間消耗,畢竟頻繁操作是不好的嘛.

Mysql

1.事務的基本要素
1.原子性:事務是一個原子操作單元,其對數據的修改,要麼全都執行,要麼全都不執行
2.一致性:事務開始前和結束後,數據庫的完整性約束沒有被破壞。
3.隔離性:同一時間,只允許一個事務請求同一數據,不同的事務之間彼此沒有任何干擾。
4.持久性:事務完成後,事務對數據庫的所有更新將被保存到數據庫,不能回滾。

2.事務隔離級別(必考)
答:
事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交 是 是 是
不可重複讀 否 是 是
可重複讀 否 否 是
串行化 否 否 否
在MySQL可重複讀的隔離級別中並不是完全解決了幻讀的問題,而是解決了讀數據情況下的幻讀問題。而對於修改的操作依舊存在幻讀問題,就是說MVCC對於幻讀的解決時不徹底的。 通過索引加鎖,間隙鎖,next key lock可以解決幻讀的問題。
3.如何解決事務的併發問題(髒讀,幻讀)(必考)
1)髒讀:(重點在於未提交)事務A修改了某個值,但是未提交,這時候事務A又讀取了這個值,事務A可能又把該值撤銷(回滾),這時候的數據可能就是無用數據。這就叫髒讀。這裏有些同學可能就要問了,既然事務A沒提交,事務B是怎麼讀取到的?如果MYSQL隔離級別設置ReadUnCommitted,這時候其他事務就可以讀取到未提交的事務。
2)幻讀:(重點在於新增和刪除,數據條數對比)相同的查詢在事務執行後,發現得到的結果不一樣,明明執行了5個操作,卻發現多了N個,或少了N個,就好像發生了幻覺一樣。
3)不可重複讀:(讀取數據本身的對比)一個事務在讀取某些數據後的一段時間後,再次讀取這個數據,發現其讀取出來的數據內容已經發生了改變,就是不可重複讀。
併發處理帶來的問題中,更新丟失可以完全避免,由應用對數據加鎖即可。髒讀、不可重讀度、幻讀,其實都是數據庫的一致性問題,必須由一定的事務隔離機制來解決。
其中一種方法是:不用加鎖,通過一定的機制生成一個數據請求時間點的一致性快照,並用這個快照來提供一個界別的一致性讀取。從用戶的角度看,好像是數據庫提偶拱了統一數據的多個版本。這種技術叫做:數據庫多版本併發控制,MVCC 多版本數據庫。事務隔離的本質是使事務在一定程度上串行化執行,顯然和併發機制是矛盾的。數據庫的事務隔離越嚴格,併發負作用越小,代價越高(影響併發訪問了唄)。
  爲了解決隔離和並大的矛盾,IOS SQL92規定了4個隔離級別。(隔離==串行)

4.MVCC多版本併發控制(必考)
答:MVCC,全稱Multi-Version Concurrency Control,即多版本併發控制。MVCC是一種併發控制的方法,一般在數據庫管理系統中,實現對數據庫的併發訪問,在編程語言中實現事務內存。MVCC在MySQL InnoDB中的實現主要是爲了提高數據庫併發性能,用更好的方式去處理讀-寫衝突,做到即使有讀寫衝突時,也能做到不加鎖,非阻塞併發讀

5.binlog,redolog,undolog都是什麼,起什麼作用
答:MySQL中有六種日誌文件,分別是:二進制日誌(binlog)、重做日誌(redo log)、回滾日誌(undo log)、錯誤日誌(errorlog)、慢查詢日誌(slow query log)、一般查詢日誌(general log),中繼日誌(relay log)。
其中重做日誌和回滾日誌與事務操作息息相關,二進制日誌也與事務操作有一定的關係,這三種日誌,對理解MySQL中的事務操作有着重要的意義。
二進制日誌(binlog):作用:1,用於複製,在主從複製中,從庫利用主庫上的binlog進行重播,實現主從同步。2,用於數據庫的基於時間點的還原。
重做日誌(redo log):作用:確保事務的持久性。防止在發生故障的時間點,尚有髒頁未寫入磁盤,在重啓mysql服務的時候,根據redo log進行重做,從而達到事務的持久性這一特性。重做日誌通過不止一種方式寫入到磁盤,重做日誌的寫盤,並不一定是隨着事務的提交才寫入重做日誌文件的,而是隨着事務的開始,逐步開始的。
回滾日誌(undo log):作用:保存了事務發生之前的數據的一個版本,可以用於回滾,同時可以提供多版本併發控制下的讀(MVCC),也即非鎖定讀。
6.InnoDB的行鎖/表鎖
答:mysql常用引擎有MYISAM和InnoDB,而InnoDB是mysql默認的引擎。MYISAM不支持行鎖,而InnoDB支持行鎖和表鎖。在mysql 的 InnoDB引擎支持行鎖,與Oracle不同,mysql的行鎖是通過索引加載的,即是行鎖是加在索引響應的行上的,要是對應的SQL語句沒有走索引,則會全表掃描,行鎖則無法實現,取而代之的是表鎖。
表鎖:不會出現死鎖,發生鎖衝突機率高,併發低。
行鎖:會出現死鎖,發生鎖衝突機率低,併發高。
7.myisam和innodb的區別,什麼時候選擇myisam
答: MyISAM:索引文件和數據文件是分離的,索引文件僅保存記錄所在頁的指針(物理位置),通過這些地址來讀取頁,進而讀取被索引的行。下圖是MyISAM的索引原理圖:(爲了簡化,一個頁內只存放了兩條記錄。)
InnoD: 與 MyISAM相同的一點是,InnoDB 也採用 B+Tree這種數據結構來實現 B-Tree索引。而很大的區別在於,InnoDB 存儲引擎採用“聚集索引”的數據存儲方式實現B-Tree索引,所謂“聚集”,就是指數據行和相鄰的鍵值緊湊地存儲在一起,注意 InnoDB 只能聚集一個葉子頁(16K)的記錄(即聚集索引滿足一定的範圍的記錄),因此包含相鄰鍵值的記錄可能會相距甚遠。
注意: innodb來說,
1: 主鍵索引 既存儲索引值,又在葉子中存儲行的數據
2: 如果沒有主鍵, 則會Unique key做主鍵
3: 如果沒有unique,則系統生成一個內部的rowid做主鍵.
4: 像innodb中,主鍵的索引結構中,既存儲了主鍵值,又存儲了行數據,這種結構稱爲”聚簇索引”
什麼時候選用myisam
myisam的主鍵索引的葉子節點只存放數據在物理磁盤上的指針,其他次索引也是一樣的;
innodb的主鍵索引的葉子節點下面直接存放數據,其他次索引的葉子節點指向主鍵id;
由此可以挖掘出一個問題,就是如果Innodb有大數據列,比如 varchar(300),這種比較多的話,那麼排序的時候用主鍵id排序會比較慢,因爲id主鍵下面放着所有數據列,而Myisam就不需要掃描數據列,要解決這個問題的話可以再建一個和主鍵id一起的聯合索引;
MyISAM表索引在處理文本索引時更具優勢,而INNODB表索引在其它類型上更具效率優勢。比如全文索引一般在CHAR、VARCHAR或TEXT列上創建,MyISAM表支持而INNODB表不支持,常見主要針對文本進行索引。同時MySQL高併發需要事務場景時,只能使用INNODB表。
8.爲什麼選擇B+樹作爲索引結構(必考)
答:因爲B+Tree所有索引數據都在葉子節點上,並且增加了順序訪問指針,每個葉子節點都有指向相鄰葉子節點的指針。進一步降低了樹的高度,這樣做是爲了提高區間效率,所以選擇它。

在MySQL中,主要有四種類型的索引,分別爲:B-Tree索引,Hash索引,Fulltext索引(MyISAM 表)和R-Tree索引。
B-樹(B樹):有序數組+平衡多叉樹;
B+樹:有序數組鏈表+平衡多叉樹;
一、Mysql索引主要有兩種結構:B+Tree索引和Hash索引
(a) Inodb存儲引擎 默認是 B+Tree索引
(b) MyISAM 存儲引擎 默認是Fulltext索引;
©Memory 存儲引擎 默認 Hash索引;
Hash索引
mysql中,只有Memory(Memory表只存在內存中,斷電會消失,適用於臨時表)存儲引擎顯示支持Hash索引,是Memory表的默認索引類型,儘管Memory表也可以使用B+Tree索引。Hash索引把數據以hash形式組織起來,因此當查找某一條記錄的時候,速度非常快。但是因爲hash結構,每個鍵只對應一個值,而且是散列的方式分佈。所以它並不支持範圍查找和排序等功能。
B+Tree索引
B+Tree是mysql使用最頻繁的一個索引數據結構,是Inodb和Myisam存儲引擎模式的索引類型。相對Hash索引,B+Tree在查找單條記錄的速度比不上Hash索引,但是因爲更適合排序等操作,所以它更受歡迎。畢竟不可能只對數據庫進行單條記錄的操作。帶順序訪問指針的B+Tree,B+Tree所有索引數據都在葉子節點上,並且增加了順序訪問指針,每個葉子節點都有指向相鄰葉子節點的指針。進一步降低了樹的高度,這樣做是爲了提高區間效率,例如查詢key爲從18到49的所有數據記錄,當找到18後,只要順着節點和指針順序遍歷就可以以此向訪問到所有數據節點,極大提高了區間查詢效率。大大減少磁盤I/O讀取數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每個節點需要一次I/O就可以完全載入。
二叉查找樹:解決了排序的基本問題,但是由於無法保證平衡,可能退化爲鏈表。
平衡二叉樹:通過旋轉解決了平衡的問題,但是旋轉操作效率太低。
紅黑樹:通過捨棄嚴格的平衡和引入紅黑節點,解決了 AVL旋轉效率過低的問題,但是在磁盤等場景下,樹仍然太高,IO次數太多。
什麼是索引
索引(Index)是幫助數據庫高效獲取數據的數據結構。索引是在基於數據庫表創建的,它包含一個表中某些列的值以及記錄對應的地址,並且把這些值存儲在一個數據結構中。最常見的就是使用哈希表、B+樹作爲索引。
一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最多的,也是最容易出問題的,還是一些複雜的查詢操作,因此對查詢語句的優化顯然是重中之重。說起加速查詢,就不得不提到索引了。
9.索引B+樹的葉子節點都可以存哪些東西(必考)
答:可能存儲的是整行數據,也有可能是主鍵的值。B+樹的葉子節點存儲了整行數據的是主鍵索引,也被稱之爲聚簇索引。而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱之爲非聚簇索引
10.查詢在什麼時候不走(預期中的)索引(必考)
1.答:模糊查詢 %like
2.索引列序
3.where對null判斷
4.where不等於
5.or操作有至少一個字段沒有索引
6.需要回表的查詢結果集過大(超過配置的範圍)
11.sql如何優化
1.對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
2.應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
1 select id from t where num is null
可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:
1 select id from t where num=0
3.應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
4.應儘量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:

1 select id from t where num=10 or num=20
可以這樣查詢:
1
2
3 select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否則會導致全表掃描,如:
1 select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
1 select id from t where num between 1 and 3
6.下面的查詢也將導致全表掃描:
1 select id from t where name like ‘%abc%’
7.應儘量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
1 select id from t where num/2=100
應改爲:
1 select id from t where num=100*2
8.應儘量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:
1 select id from t where substring(name,1,3)=‘abc’–name以abc開頭的id
應改爲:
1 select id from t where name like ‘abc%’
9.不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
10.在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,
否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。
11.不要寫一些沒有意義的查詢,如需要生成一個空表結構:
1 select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
1 create table #t(…)

12.explain是如何解析sql的
答: 使用 EXPLAIN 關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。這可以幫你分析你的查詢語句或是表結構的性能瓶頸。通過explain命令可以得到:
1. 表的讀取順序
2. 數據讀取操作的操作類型
3. 哪些索引可以使用
4. 哪些索引被實際使用
5. 表之間的引用
6. 每張表有多少行被優化器查詢
EXPLAIN字段解析:
1、table:顯示這一行的數據是關於哪張表的
2、type:這是最重要的字段之一,顯示查詢使用了何種類型。
從最好到最差的連接類型爲system、const、eq_reg、ref、range、index和ALL,一般來說,得保證查詢至少達到range級別,最好能達到ref。
type中包含的值:
system、const: 可以將查詢的變量轉爲常量. 如id=1; id爲 主鍵或唯一鍵.
eq_ref: 訪問索引,返回某單一行的數據.(通常在聯接時出現,查詢使用的索引爲主鍵或惟一鍵)
ref: 訪問索引,返回某個值的數據.(可以返回多行) 通常使用=時發生
range: 這個連接類型使用索引返回一個範圍中的行,比如使用>或<查找東西,並且該字段上建有索引時發生的情況(注:不一定好於index)
index: 以索引的順序進行全表掃描,優點是不用排序,缺點是還要全表掃描
ALL: 全表掃描,應該儘量避免
3、possible_keys:顯示可能應用在這張表中的索引。如果爲空,表示沒有可能應用的索引。
4、key:實際使用的索引。如果爲NULL,則沒有使用索引。
MySQL很少會選擇優化不足的索引,此時可以在SELECT語句中使用FORCE INDEX(index_name)來強制使用一個索引或者用IGNORE INDEX(index_name)來強制忽略索引。
MySQL強制使用和不使用索引:https://www.cnblogs.com/lcngu/p/6023179.html
5、key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好
6、ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數
7、rows:MySQL認爲必須檢索的用來返回請求數據的行數
8、Extra:關於MySQL如何解析查詢的額外信息,主要有以下幾種
Extra中包含的值:
using index: 只用到索引,可以避免訪問表,性能很高。
using where: 使用到where來過濾數據, 不是所有的where clause都要顯示using where. 如以=方式訪問索引。
using tmporary: 用到臨時表去處理當前的查詢。
using filesort: 用到額外的排序,此時mysql會根據聯接類型瀏覽所有符合條件的記錄,並保存排序關鍵字和行指針,然後排序關鍵字並按順序檢索行。(當使用order by v1,而沒用到索引時,就會使用額外的排序)。
range checked for eache record(index map:N): 沒有好的索引可以使用。
Using index for group-by:表明可以在索引中找到分組所需的所有數據,不需要查詢實際的表。explain select user_id from t_order group by user_id;
見到Using temporary和Using filesort,就意味着MySQL根本不能使用索引,結果是檢索會很慢,需要優化sql了。
13.order by原理
1.利用索引的有序性獲取有序數據
2.利用內存/磁盤文件排序獲取結果
1) 雙路排序:是首先根據相應的條件取出相應的排序字段和可以直接定位行數據的行指針信息,然後在
sortbuffer 中進行排序。
2)單路排序:是一次性取出滿足條件行的所有字段,然後在sort buffer中進行排序.
14.事務的併發問題
3.髒讀:事務A讀取了事務B更新的數據,然後B回滾操作,那麼A讀取到的數據是髒數據
4.不可重複讀:事務A多次讀取同一數據,事務B在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
5.幻讀:A事務讀取了B事務已經提交的新增數據。注意和不可重複讀的區別,這裏是新增,不可重複讀是更改(或刪除)。select某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,此時就發生了幻讀。
15.SQL執行順序
SQL的執行順序:from—where–group by—having—select—order by
JVM

1.運行時數據區域(內存模型)(必考)
1.程序計數器:程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。是線程私有”的內存。
2.Java虛擬機棧:與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀 ,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
3.本地方法棧:本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。
4.Java堆:對於大多數應用來說,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。
2.垃圾回收機制(必考)
1.引用計數法:引用計數法是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用計數器,當有引用連接至對象時,引用計數加1。當引用離開作用域或被置爲null時,引用計數減1。雖然管理引用計數的開銷不大,但這項開銷在整個程序生命週期中將持續發生。垃圾回收器會在含有全部對象的列表上遍歷,當發現某個對象引用計數爲0時,就釋放其佔用的空間。
2.可達性分析算法:這個算法的基本思路就是通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

3.垃圾回收算法(必考)
1.停止-複製:先暫停程序的運行,然後將所有存活的對象從當前堆複製到另一個堆,沒有被複制的對象全部都是垃圾。當對象被複制到新堆時,它們是一個挨着一個的,所以新堆保持緊湊排列,然後就可以按前述方法簡單,直接的分配了。缺點是一浪費空間,兩個堆之間要來回倒騰,二是當程序進入穩定態時,可能只會產生極少的垃圾,甚至不產生垃圾,儘管如此,複製式回收器仍會將所有內存自一處複製到另一處。
2.標記-清除:同樣是從堆棧和靜態存儲區出發,遍歷所有的引用,進而找出所有存活的對象。每當它找到一個存活的對象,就會給對象一個標記,這個過程中不會回收任何對象。只有全部標記工作完成的時候,清理動作纔會開始。在清理過程中,沒有標記的對象會被釋放,不會發生任何複製動作。所以剩下的堆空間是不連續的,垃圾回收器如果要希望得到連續空間的話,就得重新整理剩下的對象。
3.標記-整理:它的第一個階段與標記/清除算法是一模一樣的,均是遍歷GC Roots,然後將存活的對象標記。移動所有存活的對象,且按照內存地址次序依次排列,然後將末端內存地址以後的內存全部回收。因此,第二階段才稱爲整理階段。
4.分代收集算法:把Java堆分爲新生代和老年代,然後根據各個年代的特點採用最合適的收集算法。新生代中,對象的存活率比較低,所以選用複製算法,老年代中對象存活率高且沒有額外空間對它進行分配擔保,所以使用“標記-清除”或“標記-整理”算法進行回收。

4.Minor GC和Full GC觸發條件
答:Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
i.調用System.gc時,系統建議執行Full GC,但是不必然執行
ii.老年代空間不足
iii.方法區空間不足
iv.通過Minor GC後進入老年代的平均大小大於老年代的可用內存
v.由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小
5.GC中Stop the world(STW)
答:在執行垃圾收集算法時,Java應用程序的其他所有除了垃圾收集收集器線程之外的線程都被掛起。此時,系統只能允許GC線程進行運行,其他線程則會全部暫停,等待GC線程執行完畢後才能再次運行。這些工作都是由虛擬機在後臺自動發起和自動完成的,是在用戶不可見的情況下把用戶正常工作的線程全部停下來,這對於很多的應用程序,尤其是那些對於實時性要求很高的程序來說是難以接受的。
但不是說GC必須STW,你也可以選擇降低運行速度但是可以併發執行的收集算法,這取決於你的業務。

6.各垃圾回收器的特點及區別
各垃圾收集器的特點和作用:如圖
在這裏插入圖片描述
相關名詞解釋
Stop The World
在垃圾回收器進行回收之前,JVM會對內存中的對象進行一次可達性分析,也就是哪些是可回收的,哪些是不可回收的,但是在這個判斷的過程中,要求JVM中的對象是不可變得,也就是要求一個快照,所以在這個時候就會暫停所有的工作線程,也就是所說的Stop The World。
垃圾收集算法
JVM中常用的垃圾收集算法有標記清除算法,複製算法,標記整理算法,具體有哪些特點,就不一一列舉,自行了解。
7.雙親委派模型
答:雙親委派的意思是如果一個類加載器需要加載類,那麼首先它會把這個類請求委派給父類加載器去完成,每一層都是如此。一直遞歸到頂層,當父加載器無法完成這個請求時,子類纔會嘗試去加載。
8.JDBC和雙親委派模型關係
問題一:雙親委派模型是什麼
如果一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給父類加載器,每一個層次的類加載器都是如此,因此所有的類加載請求最終都會傳送到頂端的啓動類加載器;只有當父類加載器在其搜索範圍內無法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去自己加載。
問題二:JDBC爲什麼要破壞雙親委派模型
因爲類加載器受到加載範圍的限制,在某些情況下父類加載器無法加載到需要的文件,這時候就需要委託子類加載器去加載class文件。JDBC的Driver接口定義在JDK中,其實現由各個數據庫的服務商來提供,比如MySQL驅動包。DriverManager 類中要加載各個實現了Driver接口的類,然後進行管理,但是DriverManager位於 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap類加載器加載,而其Driver接口的實現類是位於服務商提供的 Jar 包,根據類加載機制,當被裝載的類引用了另外一個類的時候,虛擬機就會使用裝載第一個類的類裝載器裝載被引用的類。也就是說BootStrap類加載器還要去加載jar包中的Driver接口的實現類。我們知道,BootStrap類加載器默認只負責加載 $JAVA_HOME中jre/lib/rt.jar 裏所有的class,所以需要由子類加載器去加載Driver實現,這就破壞了雙親委派模型。
9.JVM鎖優化和鎖膨脹過程
1.答:自旋鎖:自旋鎖其實就是在拿鎖時發現已經有線程拿了鎖,自己如果去拿會阻塞自己,這個時候會選擇進行一次忙循環嘗試。也就是不停循環看是否能等到上個線程自己釋放鎖。自適應自旋鎖指的是例如第一次設置最多自旋10次,結果在自旋的過程中成功獲得了鎖,那麼下一次就可以設置成最多自旋20次。
2.鎖粗化:虛擬機通過適當擴大加鎖的範圍以避免頻繁的拿鎖釋放鎖的過程。
3.鎖消除:通過逃逸分析發現其實根本就沒有別的線程產生競爭的可能(別的線程沒有臨界量的引用),或者同步塊內進行的是原子操作,而“自作多情”地給自己加上了鎖。有可能虛擬機會直接去掉這個鎖。
4.偏向鎖:在大多數的情況下,鎖不僅不存在多線程的競爭,而且總是由同一個線程獲得。因此爲了讓線程獲得鎖的代價更低引入了偏向鎖的概念。偏向鎖的意思是如果一個線程獲得了一個偏向鎖,如果在接下來的一段時間中沒有其他線程來競爭鎖,那麼持有偏向鎖的線程再次進入或者退出同一個同步代碼塊,不需要再次進行搶佔鎖和釋放鎖的操作。
5.輕量級鎖:當存在超過一個線程在競爭同一個同步代碼塊時,會發生偏向鎖的撤銷。當前線程會嘗試使用CAS來獲取鎖,當自旋超過指定次數(可以自定義)時仍然無法獲得鎖,此時鎖會膨脹升級爲重量級鎖。
6.重量級鎖:重量級鎖依賴對象內部的monitor鎖來實現,而monitor又依賴操作系統的MutexLock(互斥鎖)。當系統檢查到是重量級鎖之後,會把等待想要獲取鎖的線程阻塞,被阻塞的線程不會消耗CPU,但是阻塞或者喚醒一個線程,都需要通過操作系統來實現。

Java基礎

1.HashMap和ConcurrentHashMap區別(必考)
答:由於HashMap是線程不同步的,雖然處理數據的效率高,但是在多線程的情況下存在着安全問題,因此設計了CurrentHashMap來解決多線程安全問題。
HashMap在put的時候,插入的元素超過了容量(由負載因子決定)的範圍就會觸發擴容操作,就是rehash,這個會重新將原數組的內容重新hash到新的擴容數組中,在多線程的環境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現同時在同一數組下用鏈表表示,造成閉環,導致在get時會出現死循環,所以HashMap是線程不安全的。
HashMap的環:若當前線程此時獲得ertry節點,但是被線程中斷無法繼續執行,此時線程二進入transfer函數,並把函數順利執行,此時新表中的某個位置有了節點,之後線程一獲得執行權繼續執行,因爲併發transfer,所以兩者都是擴容的同一個鏈表,當線程一執行到e.next = new table[i] 的時候,由於線程二之前數據遷移的原因導致此時new table[i] 上就有ertry存在,所以線程一執行的時候,會將next節點,設置爲自己,導致自己互相使用next引用對方,因此產生鏈表,導致死循環。
在JDK1.7版本中,ConcurrentHashMap維護了一個Segment數組,Segment這個類繼承了重入鎖ReentrantLock,並且該類裏面維護了一個 HashEntry<K,V>[] table數組,在寫操作put,remove,擴容的時候,會對Segment加鎖,所以僅僅影響這個Segment,不同的Segment還是可以併發的,所以解決了線程的安全問題,同時又採用了分段鎖也提升了併發的效率。在JDK1.8版本中,ConcurrentHashMap摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,併發控制使用Synchronized和CAS來操作,整個看起來就像是優化過且線程安全的HashMap。
2.ConcurrentHashMap的數據結構(必考)
答:由於HashMap是線程不同步的,雖然處理數據的效率高,但是在多線程的情況下存在着安全問題,因此設計了CurrentHashMap來解決多線程安全問題。
HashMap在put的時候,插入的元素超過了容量(由負載因子決定)的範圍就會觸發擴容操作,就是rehash,這個會重新將原數組的內容重新hash到新的擴容數組中,在多線程的環境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現同時在同一數組下用鏈表表示,造成閉環,導致在get時會出現死循環,所以HashMap是線程不安全的。在JDK1.8版本的ConcurrentHashMap的數據結構已經接近HashMap,相對而言,ConcurrentHashMap只是增加了同步的操作來控制併發

3.高併發HashMap的環是如何產生的
答:若當前線程此時獲得ertry節點,但是被線程中斷無法繼續執行,此時線程二進入transfer函數,並把函數順利執行,此時新表中的某個位置有了節點,之後線程一獲得執行權繼續執行,因爲併發transfer,所以兩者都是擴容的同一個鏈表,當線程一執行到e.next = new table[i] 的時候,由於線程二之前數據遷移的原因導致此時new table[i] 上就有ertry存在,所以線程一執行的時候,會將next節點,設置爲自己,導致自己互相使用next引用對方,因此產生鏈表,導致死循環。
4.volatile作用(必考)
答:volatile在多處理器開發中保證了共享變量的“ 可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。(共享內存,私有內存)
5.Atomic類如何保證原子性(CAS操作)(必考)
答:CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較並替換。CAS需要有3個操作數:內存地址V,舊的預期值A,即將要更新的目標值B。CAS指令執行時,當且僅當內存地址V的值與預期值A相等時,將內存地址V的值修改爲B,否則就什麼都不做。整個比較並替換的操作是一個原子操作。如 Intel 處理器,比較並交換通過指令的 cmpxchg 系列實現。
6.synchronized和Lock的區別(必考)
1.答:首先synchronized是java內置關鍵字在jvm層面,Lock是個java類。
2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖,並且可以主動嘗試去獲取鎖。
3.synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。
4.用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了。
5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
7.爲什麼要使用線程池(必考)
1.答:減少創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線程的數目,放置因爲消耗過多的內存,而把服務器累趴下
8.核心線程池ThreadPoolExecutor的參數(必考)
3.答:corePoolSize:指定了線程池中的線程數量
4.maximumPoolSize:指定了線程池中的最大線程數量
5.keepAliveTime:線程池維護線程所允許的空閒時間
6.unit: keepAliveTime 的單位。
7.workQueue:任務隊列,被提交但尚未被執行的任務。
8.threadFactory:線程工廠,用於創建線程,一般用默認的即可。
9.handler:拒絕策略。當任務太多來不及處理,如何拒絕任務。
9.ThreadPoolExecutor(線程池)的工作流程(必考)
一個新的任務到線程池時,線程池的處理流程如下:
1.線程池判斷核心線程池裏的線程是否都在執行任務。 如果不是,創建一個新的工作線程來執行任務。如果核心線程池裏的線程都在執行任務,則進入下個流程。
2.線程池判斷阻塞隊列是否已滿。 如果阻塞隊列沒有滿,則將新提交的任務存儲在阻塞隊列中。如果阻塞隊列已滿,則進入下個流程。
3.線程池判斷線程池裏的線程是否都處於工作狀態。 如果沒有,則創建一個新的工作線程來執行任務。如果已滿,則交給飽和策略來處理這個任務。
線程池的核心實現類是ThreadPoolExecutor類,用來執行提交的任務。因此,任務提交到線程池時,具體的處理流程是由ThreadPoolExecutor類的execute()方法去完成的。
1.如果當前運行的線程少於corePoolSize,則創建新的工作線程來執行任務(執行這一步驟需要獲取全局鎖)。
2.如果當前運行的線程大於或等於corePoolSize,而且BlockingQueue未滿,則將任務加入到BlockingQueue中。
3.如果BlockingQueue已滿,而且當前運行的線程小於maximumPoolSize,則創建新的工作線程來執行任務(執行這一步驟需要獲取全局鎖)。
4.如果當前運行的線程大於或等於maximumPoolSize,任務將被拒絕,並調用RejectExecutionHandler.rejectExecution()方法。即調用飽和策略對任務進行處理。
5.工作線程(Worker): 線程池在創建線程時,會將線程封裝成工作線程Woker。Woker在執行完任務後,不是立即銷燬而是循環獲取阻塞隊列裏的任務來執行。

ThreadPoolExecutor的執行主要圍繞Worker,Worker 實現了 AbstractQueuedSynchronizer 並繼承了 Runnable
1.ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新提交被拒絕的任務
4.ThreadPoolExecutor.CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務

10.線程之間如何通信
答:合理地使用wait()、notify()和notifyAll()方法確實能夠很好地解決線程間通信的問題。但是,也應該瞭解到這些方法是更復雜的鎖定、排隊和併發性代碼的構件。尤其是使用 notify()來代替notifyAll()時是有風險的。除非確實知道每一個線程正在做什麼,否則最好使用notifyAll()。
11.Boolean佔幾個字節
答:未精確定義字節。Java語言表達式所操作的boolean值,在編譯之後都使用Java虛擬機中的int數據類型來代替,而boolean數組將會被編碼成Java虛擬機的byte數組,每個元素boolean元素佔8位。
12.jdk1.8/jdk1.7都分別新增了哪些特性
jdk1.7新特性
1、泛型實例的創建可以通過類型推斷來簡化,可以去掉後面new部分的泛型類型,只用<>就可以了。
2、併發工具增強: fork-join框架最大的增強,充分利用多核特性,將大問題分解成各個子問題,由多個cpu 可以同時 解決多個子問題,最後合併結果,繼承RecursiveTask,實現compute方法,然後調用fork計算,最後用join合併結果。
3、try-with-resources語句是一種聲明瞭一種或多種資源的try語句。資源是指在程序用完了之後必須要關閉的對象。try-with-resources語句保證了每個聲明瞭的資源在語句結束的時候都會被關閉。任何實現了java.lang.AutoCloseable接口的對象,和實現了java .io .Closeable接口的對象,都可以當做資源使用。
4、Catch多個異常:在Java 7中,catch代碼塊得到了升級,用以在單個catch塊中處理多個異常。如果你要捕獲多個異常並且它們包含相似的代碼,使用這一特性將會減少代碼重複度。下面用一個例子來理解。
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
dk1.8新特性知識點:
1 jdk1.8對hashMap等map集合的優化
2 Lambda表達式
3 函數式接口
4 方法引用和構造器調用
5 Stream API
6 並行流和串行流
7 Optional容器
Java 8引入Optional類來防止空指針異常,Optional類最先是由Google的Guava項目引入的。Optional類實際上是個容器:它可以保存類型T的值,或者保存null。使用Optional類我們就不用顯式進行空指針檢查了。
8 接口中的默認方法和靜態方法
9 新時間日期API
10 定義可重複的註解
在Java 5中使用註解有一個限制,即相同的註解在同一位置只能聲明一次。Java 8引入重複註解,這樣相同的註解在同一地方也可以聲明多次。重複註解機制本身需要用@Repeatable註解。Java 8在編譯器層做了優化,相同註解會以集合的方式保存,因此底層的原理並沒有變化。
11 擴展註解的支持
Java 8擴展了註解的上下文,幾乎可以爲任何東西添加註解,包括局部變量、泛型類、父類與接口的實現,連方法的異常也能添加註解。
12 jvm中的方法區變成了元數據區(PermGen變成了Metaspace)
13 更好的類型推測機制(不需要太多的強制類型轉換了)
14 編譯器優化:Java 8 將方法的參數名加入了字節碼中,這樣在運行時 通過反射 就能獲取到參數名,只需要在編譯時使用-parameters參數。

13.Exception和Error
答:Exception和Error都是繼承了Throwable類,在java中只有Throwable類型的實例纔可以被拋出(throw)或者捕獲(catch),他是異常處理機制的基本組成類型。
Exception和Error體現了java平臺設計者對不同異常情況的分類,Exception是程序正常運行中,可以預料的意外情況,可能並且應該被捕獲,進行相應的處理。
Error是指正常情況下,不大可能出現的情況,絕大部分的Error都會導致程序(比如JVM自身)處於非正常狀態,不可恢復狀態。既然是非正常情況,所以不便於也不需要捕獲,常見的比如OutOfMemoryError之類,都是Error的子類。
Exception又分爲可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源碼裏必須顯示的進行捕獲處理,這裏是編譯期檢查的一部分。前面我們介紹的不可查的Error,是Throwable不是Exception。
不檢查異常就是所謂的運行時異常,類似NullPointerException,ArrayIndexOutOfBoundsExceptin之類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯器強制要求。
Spring

1.Spring的IOC/AOP的實現(必考)
答:IOC:控制反轉,就是把對象的創建交給Spring來做
SpringIoc所使用的技術
1、xml配置文件
2、dom4j解析XML文件
3、工廠設計模式
4、反射
AOP(Aspect-Oriented Programming,面向切面編程),可以說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行爲的一個集合。當我們需 要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日 志代碼往往水平地散佈在所有對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種 散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。
而AOP技術則恰恰相反,它利用一種稱爲“橫切”的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲 “Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低 模塊間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行爲; 那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”了。然後它又以巧奪天功的妙手 將這些剖開的切面復原,不留痕跡。
使用“橫切”技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫 切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處都基本相似。比如權限認證、日誌、事務處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。”
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的 方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。然而殊途同歸,實現AOP的技術特性卻是相同的,分別爲:
1、join point(連接點):是程序執行中的一個精確執行點,例如類中的一個方法。它是一個抽象的概念,在實現AOP時,並不需要去定義一個join point。
2、point cut(切入點):本質上是一個捕獲連接點的結構。在AOP中,可以定義一個point cut,來捕獲相關方法的調用。
3、advice(通知):是point cut的執行代碼,是執行“方面”的具體邏輯。
4、aspect(方面):point cut和advice結合起來就是aspect,它類似於OOP中定義的一個類,但它代表的更多是對象間橫向的關係。
5、introduce(引入):爲對象引入附加的方法或屬性,從而達到修改對象結構的目的。有的AOP工具又將其稱爲mixin。
AOP應用到的橫切技術,通常分爲兩種類型:動態橫切和靜態橫切。
2.動態代理的實現方式(必考)
答:無需聲明代理類。是使用反射和字節碼的技術,在運行期創建指定接口或類的子類(即動態代理類)以及其實例對象的技術。通過動態代理技術可以無侵入地對代碼進行增強。
Java領域中,常用的動態代理實現方式有兩種,一種是利用JDK反射機制生成代理,另外一種是使用CGLIB代理。
jdk動態代理:利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。JDK代理必須要提供接口,而CGLIB則不需要,可以直接代理類。下面分別舉例說明
CGlib動態代理:利用ASM(開源的Java字節碼編輯庫,操作字節碼)開源包,將代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
區別:JDK代理只能對實現接口的類生成代理;CGlib是針對類實現代理,對指定的類生成一個子類,並覆蓋其中的方法,這種通過繼承類的實現方式,不能代理final修飾的類。
3.Spring如何解決循環依賴(三級緩存)(必考)
1.第一級緩存:單例緩存池singletonObjects。
2.第二級緩存:早期提前暴露的對象緩存earlySingletonObjects。(屬性還沒有值對象也沒有被初始化)
3.第三級緩存:singletonFactories單例對象工廠緩存。
4.Spring的後置處理器
答:BeanPostProcessor也就是後置處理器的作用是在Bean對象在實例化和依賴注入完畢後,在顯示調用初始化方法的前後添加我們自己的邏輯。注意是Bean實例化完畢後及依賴注入完成後觸發的。
1.ApplicationContextAware
類實現了ApplicationContextAware接口,可以取得上下文ApplicationContex,用於自己業務操作。ApplicationContextAware對應的後置處理器是ApplicationContextAwareProcessor。
2.BeanValidationPostProcessor
BeanValidationPostProcessor用來做數據校驗的,比如對加了@Validated類按照JSR提供的校驗註解(比如@Null)進行校驗。
3.InitDestroyAnnotationBeanPostProcessor
後置處理器繼承於BeanPostProcessor,主要在實例化bean前後工作; AOP創建代理對象就是通過該接口實現。是對@PostConstruct, @PreDestroy進行處理。
4.BeanFactoryPostProcessor
Bean工廠的後置處理器,在bean定義(bean definitions)加載完成後,bean尚未初始化前執行。
5.BeanDefinitionRegistryPostProcessor
繼承於BeanFactoryPostProcessor。其自定義的方法postProcessBeanDefinitionRegistry會在bean定義(bean definitions)將要加載,bean尚未初始化前真執行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被調用。

5.Spring的@Transactional如何實現的(必考)
答:Transactional是spring中定義的事務註解,在方法或類上加該註解開啓事務。主要是通過反射獲取bean的註解信息,利用AOP對編程式事務進行封裝實現。

6.Spring的事務傳播級別
1.REQUIRED(默認):支持使用當前事務,如果當前事務不存在,創建一個新事務。
2.SUPPORTS:支持使用當前事務,如果當前事務不存在,則不使用事務。
3.MANDATORY:強制,支持使用當前事務,如果當前事務不存在,則拋出Exception。
4.REQUIRES_NEW:創建一個新事務,如果當前事務存在,把當前事務掛起。
5.NOT_SUPPORTED:無事務執行,如果當前事務存在,把當前事務掛起。
6.NEVER:無事務執行,如果當前有事務則拋出Exception。
7.NESTED:嵌套事務,如果當前事務存在,那麼在嵌套的事務中執行。如果當前事務不存在,則表現跟REQUIRED一樣。
7.BeanFactory和ApplicationContext的聯繫和區別
1.BeanFactory是Spring裏面最低層的接口,提供了最簡單的容器的功能,只提供了實例化對象和拿對象的功能。
2.ApplicationContext應用上下文,繼承BeanFactory接口,它是Spring的一各更高級的容器,提供了更多的有用的功能。如國際化,訪問資源,載入多個(有繼承關係)上下文 ,使得每一個上下文都專注於一個特定的層次,消息發送、響應機制,AOP等。
3.BeanFactory在啓動的時候不會去實例化Bean,中有從容器中拿Bean的時候纔會去實例化。ApplicationContext在啓動的時候就把所有的Bean全部實例化了。它還可以爲Bean配置lazy-init=true來讓Bean延遲實例化

消息隊列

1.爲什麼需要消息隊列
答:解耦,異步處理,削峯/限流 (海量)日誌處理,消息通訊

2.Kafka的文件存儲機制
答:Kafka中消息是以topic進行分類的,生產者通過topic向Kafka broker發送消息,消費者通過topic讀取數據。然而topic在物理層面又能以partition爲分組,一個topic可以分成若干個partition。partition還可以細分爲segment,一個partition物理上由多個segment組成,segment文件由兩部分組成,分別爲“.index”文件和“.log”文件,分別表示爲segment索引文件和數據文件。這兩個文件的命令規則爲:partition全局的第一個segment從0開始,後續每個segment文件名爲上一個segment文件最後一條消息的offset值。
3.Kafka 如何保證可靠性
答:如果我們要往 Kafka 對應的主題發送消息,我們需要通過 Producer 完成。前面我們講過 Kafka 主題對應了多個分區,每個分區下面又對應了多個副本;爲了讓用戶設置數據可靠性, Kafka 在 Producer 裏面提供了消息確認機制。也就是說我們可以通過配置來決定消息發送到對應分區的幾個副本纔算消息發送成功。可以在定義 Producer 時通過 acks 參數指定。這個參數支持以下三種值:
1.acks = 0:意味着如果生產者能夠通過網絡把消息發送出去,那麼就認爲消息已成功寫入 Kafka 。在這種情況下還是有可能發生錯誤,比如發送的對象無能被序列化或者網卡發生故障,但如果是分區離線或整個集羣長時間不可用,那就不會收到任何錯誤。在 acks=0 模式下的運行速度是非常快的(這就是爲什麼很多基準測試都是基於這個模式),你可以得到驚人的吞吐量和帶寬利用率,不過如果選擇了這種模式, 一定會丟失一些消息。
2.acks = 1:意味若 Leader 在收到消息並把它寫入到分區數據文件(不一定同步到磁盤上)時會返回確認或錯誤響應。在這個模式下,如果發生正常的 Leader 選舉,生產者會在選舉時收到一個 LeaderNotAvailableException 異常,如果生產者能恰當地處理這個錯誤,它會重試發送悄息,最終消息會安全到達新的 Leader 那裏。不過在這個模式下仍然有可能丟失數據,比如消息已經成功寫入 Leader,但在消息被複制到 follower 副本之前 Leader發生崩潰。
3.acks = all(這個和 request.required.acks = -1 含義一樣):意味着 Leader 在返回確認或錯誤響應之前,會等待所有同步副本都收到悄息。如果和min.insync.replicas 參數結合起來,就可以決定在返回確認前至少有多少個副本能夠收到悄息,生產者會一直重試直到消息被成功提交。不過這也是最慢的做法,因爲生產者在繼續發送其他消息之前需要等待所有副本都收到當前的消息。

4.Kafka消息是採用Pull模式,還是Push模式
答:Kafka最初考慮的問題是,customer應該從brokes拉取消息還是brokers將消息推送到consumer,也就是pull還push。在這方面,Kafka遵循了一種大部分消息系統共同的傳統的設計:producer將消息推送到broker,consumer從broker拉取消息。push模式下,當broker推送的速率遠大於consumer消費的速率時,consumer恐怕就要崩潰了。最終Kafka還是選取了傳統的pull模式。Pull模式的另外一個好處是consumer可以自主決定是否批量的從broker拉取數據。Pull有個缺點是,如果broker沒有可供消費的消息,將導致consumer不斷在循環中輪詢,直到新消息到t達。爲了避免這點,Kafka有個參數可以讓consumer阻塞知道新消息到達。

5.Kafka是如何實現高吞吐率的
1.順序讀寫:kafka的消息是不斷追加到文件中的,這個特性使kafka可以充分利用磁盤的順序讀寫性能
2.零拷貝:跳過“用戶緩衝區”的拷貝,建立一個磁盤空間和內存的直接映射,數據不再複製到“用戶態緩衝區”
3.文件分段:kafka的隊列topic被分爲了多個區partition,每個partition又分爲多個段segment,所以一個隊列中的消息實際上是保存在N多個片段文件中
4.批量發送:Kafka允許進行批量發送消息,先將消息緩存在內存中,然後一次請求批量發送出去
5.數據壓縮:Kafka還支持對消息集合進行壓縮,Producer可以通過GZIP或Snappy格式對消息集合進行壓縮
6.Kafka判斷一個節點還活着的兩個條件
1.節點必須可以維護和 ZooKeeper 的連接,Zookeeper 通過心跳機制檢查每個節點的連接
2.如果節點是個 follower,他必須能及時的同步 leader 的寫操作,延時不能太久

Dubbo

1.Dubbo的容錯機制
1.失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。可通過 retries=“2” 來設置重試次數
2.快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
3.失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。
4.失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。
5.並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks=“2” 來設置最大並行數。
6.廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息
2.Dubbo註冊中心掛了還可以繼續通信麼
可以,因爲剛開始初始化的時候,消費者會將提供者的地址等信息拉取到本地緩存,所以註冊中心掛了可以繼續通信。
3.Dubbo框架設計結構
1.服務接口層:該層是與實際業務邏輯相關的,根據服務提供方和服務消費方的業務設計對應的接口和實現。
2.配置層:對外配置接口,以ServiceConfig和ReferenceConfig爲中心,可以直接new配置類,也可以通過spring解析配置生成配置類。
3.服務代理層:服務接口透明代理,生成服務的客戶端Stub和服務器端Skeleton,以ServiceProxy爲中心,擴展接口爲ProxyFactory。
4.服務註冊層:封裝服務地址的註冊與發現,以服務URL爲中心,擴展接口爲RegistryFactory、Registry和RegistryService。可能沒有服務註冊中心,此時服務提供方直接暴露服務。
5.集羣層:封裝多個提供者的路由及負載均衡,並橋接註冊中心,以Invoker爲中心,擴展接口爲Cluster、Directory、Router和LoadBalance。將多個服務提供方組合爲一個服務提供方,實現對服務消費方來透明,只需要與一個服務提供方進行交互。
6.監控層:RPC調用次數和調用時間監控,以Statistics爲中心,擴展接口爲MonitorFactory、Monitor和MonitorService。
7.遠程調用層:封將RPC調用,以Invocation和Result爲中心,擴展接口爲Protocol、Invoker和Exporter。Protocol是服務域,它是Invoker暴露和引用的主功能入口,它負責Invoker的生命週期管理。Invoker是實體域,它是Dubbo的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起invoke調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集羣實現。
8.信息交換層:封裝請求響應模式,同步轉異步,以Request和Response爲中心,擴展接口爲Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
9.網絡傳輸層:抽象mina和netty爲統一接口,以Message爲中心,擴展接口爲Channel、Transporter、Client、Server和Codec。
10.數據序列化層:可複用的一些工具,擴展接口爲Serialization、 ObjectInput、ObjectOutput和ThreadPool。

操作系統

1.進程和線程
1.進程是操作系統資源分配的最小單位,線程是CPU任務調度的最小單位。一個進程可以包含多個線程,所以進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。
2.不同進程間數據很難共享,同一進程下不同線程間數據很易共享。
3.每個進程都有獨立的代碼和數據空間,進程要比線程消耗更多的計算機資源。線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器,線程之間切換的開銷小。
4.進程間不會相互影響,一個線程掛掉將導致整個進程掛掉。
5.系統在運行的時候會爲每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。
2.進程的組成部分
進程由進程控制塊(PCB)、程序段、數據段三部分組成。
3.進程的通信方式
1.無名管道:半雙工的,即數據只能在一個方向上流動,只能用於具有親緣關係的進程之間的通信,可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不屬於其他任何文件系統,並且只存在於內存中。
2.FIFO命名管道:FIFO是一種文件類型,可以在無關的進程之間交換數據,與無名管道不同,FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在於文件系統中。
3.消息隊列:消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標識。
4.信號量:信號量是一個計數器,信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通信數據。
5.共享內存:共享內存指兩個或多個進程共享一個給定的存儲區,一般配合信號量使用。
4.進程間五種通信方式的比較
1.管道:速度慢,容量有限,只有父子進程能通訊。
2.FIFO:任何進程間都能通訊,但速度慢。
3.消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題。
4.信號量:不能傳遞複雜消息,只能用來同步。
5.共享內存區:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當於線程中的線程安全,當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存。
5.死鎖的4個必要條件
1.互斥條件:一個資源每次只能被一個線程使用;
2.請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放;
3.不剝奪條件:進程已經獲得的資源,在未使用完之前,不能強行剝奪;
4.循環等待條件:若干線程之間形成一種頭尾相接的循環等待資源關係。
6.如何避免(預防)死鎖
1.破壞“請求和保持”條件:讓進程在申請資源時,一次性申請所有需要用到的資源,不要一次一次來申請,當申請的資源有一些沒空,那就讓線程等待。不過這個方法比較浪費資源,進程可能經常處於飢餓狀態。還有一種方法是,要求進程在申請資源前,要釋放自己擁有的資源。
2.破壞“不可搶佔”條件:允許進程進行搶佔,方法一:如果去搶資源,被拒絕,就釋放自己的資源。方法二:操作系統允許搶,只要你優先級大,可以搶到。
3.破壞“循環等待”條件:將系統中的所有資源統一編號,進程可在任何時刻提出資源申請,但所有申請必須按照資源的編號順序提出(指定獲取鎖的順序,順序加鎖)。
計算機網路

1.tcp和udp區別
1.TCP面向連接,UDP是無連接的,即發送數據之前不需要建立連接。
2.TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付。
3.TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流,UDP是面向報文的,UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
4.每一條TCP連接只能是點到點的,UDP支持一對一,一對多,多對一和多對多的交互通信。
5.TCP首部開銷20字節,UDP的首部開銷小,只有8個字節。
6.TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。
2.Http請求的完全過程
1.瀏覽器根據域名解析IP地址(DNS),並查DNS緩存
2.瀏覽器與WEB服務器建立一個TCP連接
3.瀏覽器給WEB服務器發送一個HTTP請求(GET/POST):一個HTTP請求報文由請求行(request line)、請求頭部(headers)、空行(blank line)和請求數據(request body)4個部分組成。
4.服務端響應HTTP響應報文,報文由狀態行(status line)、相應頭部(headers)、空行(blank line)和響應數據(response body)4個部分組成。
5.瀏覽器解析渲染
3.tcp和udp的優點
TCP的優點: 可靠,穩定 TCP的可靠體現在TCP在傳遞數據之前,會有三次握手來建立連接,而且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完後,還會斷開連接用來節約系統資源。 TCP的缺點: 慢,效率低,佔用系統資源高,易被攻擊 TCP在傳遞數據之前,要先建連接,這會消耗時間,而且在數據傳遞時,確認機制、重傳機制、擁塞控制機制等都會消耗大量的時間,而且要在每臺設備上維護所有的傳輸連接,事實上,每個連接都會佔用系統的CPU、內存等硬件資源。 而且,因爲TCP有確認機制、三次握手機制,這些也導致TCP容易被人利用,實現DOS、DDOS、CC等攻擊。
UDP的優點: 快,比TCP稍安全 UDP沒有TCP的握手、確認、窗口、重傳、擁塞控制等機制,UDP是一個無狀態的傳輸協議,所以它在傳遞數據時非常快。沒有TCP的這些機制,UDP較TCP被攻擊者利用的漏洞就要少一些。但UDP也是無法避免攻擊的,比如:UDP Flood攻擊…… UDP的缺點: 不可靠,不穩定 因爲UDP沒有TCP那些可靠的機制,在數據傳遞時,如果網絡質量不好,就會很容易丟包。 基於上面的優缺點,那麼: 什麼時候應該使用TCP: 當對網絡通訊質量有要求的時候,比如:整個數據要準確無誤的傳遞給對方,這往往用於一些要求可靠的應用,比如HTTP、HTTPS、FTP等傳輸文件的協議,POP、SMTP等郵件傳輸的協議。 在日常生活中,常見使用TCP協議的應用如下: 瀏覽器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件傳輸。什麼時候應該使用UDP: 當對網絡通訊質量要求不高的時候,要求網絡通訊速度能儘量的快,這時就可以使用UDP。 比如,日常生活中,常見使用UDP協議的應用如下: QQ語音 QQ視頻 TFTP。
4.Get和Post區別
1.Get是不安全的,因爲在傳輸過程,數據被放在請求的URL中;Post的所有操作對用戶來說都是不可見的。
2.Get傳送的數據量較小,這主要是因爲受URL長度限制;Post傳送的數據量較大,一般被默認爲不受限制。
3.Get限制Form表單的數據集的值必須爲ASCII字符;而Post支持整個ISO10646字符集。
4.Get執行效率卻比Post方法好。Get是form提交的默認方法。
5.GET產生一個TCP數據包;POST產生兩個TCP數據包。(非必然,客戶端可靈活決定)
5.三次握手
第一次握手:建立連接時,客戶端發送syn包(syn=x)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。
6.爲什麼不能兩次握手
TCP是一個雙向通信協議,通信雙方都有能力發送信息,並接收響應。如果只是兩次握手, 至多隻有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認
7.四次揮手
1.客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號爲seq=u(等於前面已經傳送過來的數據的最後一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
2.服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
3.客戶端收到服務器的確認請求後,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最後的數據)。
4.服務器將最後的數據發送完畢後,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號爲seq=w,此時,服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。
5.客戶端收到服務器的連接釋放報文後,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2∗∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。
6.服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB後,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些
8.爲什麼連接的時候是三次握手,關閉的時候卻是四次握手
因爲當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,“你發的FIN報文我收到了”。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。

其他

1.高併發系統的限流如何實現
答:在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。
1.緩存:緩存比較好理解,在大型高併發系統中,如果沒有緩存數據庫將分分鐘被爆,系統也會瞬間癱瘓。使用緩存不單單能夠提升系統訪問速度、提高併發訪問量,也是保護數據庫、保護系統的有效方式。大型網站一般主要是“讀”,緩存的使用很容易被想到。在大型“寫”系統中,緩存也常常扮演者非常重要的角色。比如累積一些數據批量寫入,內存裏面的緩存隊列(生產消費),以及HBase寫數據的機制等等也都是通過緩存提升系統的吞吐量或者實現系統的保護措施。甚至消息中間件,你也可以認爲是一種分佈式的數據緩存。
2.降級:服務降級是當服務器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。降級往往會指定不同的級別,面臨不同的異常等級執行不同的處理。根據服務方式:可以拒接服務,可以延遲服務,也有時候可以隨機服務。根據服務範圍:可以砍掉某個功能,也可以砍掉某些模塊。總之服務降級需要根據不同的業務需求採用不同的降級策略。主要的目的就是服務雖然有損但是總比沒有好。
3.限流:限流可以認爲服務降級的一種,限流就是限制系統的輸入和輸出流量已達到保護系統的目的。一般來說系統的吞吐量是可以被測算的,爲了保證系統的穩定運行,一旦達到的需要限制的閾值,就需要限制流量並採取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。
2.RPC簡介
HTTP協議
http協議是基於tcp協議的,tcp協議是流式協議,包頭部分可以通過多出的\r\n來分界,包體部分如何分界呢?這是協議本身要解決的問題。目前一般有兩種方式,第一種方式就是在包頭中有個content-Length字段,這個字段的值的大小標識了POST數據的長度,服務器收到一個數據包後,先從包頭解析出這個字段的值,再根據這個值去讀取相應長度的作爲http協議的包體數據。
瀏覽器connect 80端口

RPC
進程間通信(IPC,Inter-Process Communication),指至少兩個進程或線程間傳送數據或信號的一些技術或方法。進程是計算機系統分配資源的最小單位。每個進程都有自己的一部分獨立的系統資源,彼此是隔離的。爲了能使不同的進程互相訪問資源並進行協調工作,纔有了進程間通信。這些進程可以運行在同一計算機上或網絡連接的不同計算機上。 進程間通信技術包括消息傳遞、同步、共享內存和遠程過程調用。 IPC是一種標準的Unix通信機制。
有兩種類型的進程間通信(IPC)。
本地過程調用(LPC)LPC用在多任務操作系統中,使得同時運行的任務能互相會話。這些任務共享內存空間使任務同步和互相發送信息。
遠程過程調用(RPC)RPC類似於LPC,只是在網上工作。RPC開始是出現在Sun微系統公司和HP公司的運行UNIX操作系統的計算機中。
爲什麼RPC呢?就是無法在一個進程內,甚至一個計算機內通過本地調用的方式完成的需求,比如比如不同的系統間的通訊,甚至不同的組織間的通訊。由於計算能力需要橫向擴展,需要在多臺機器組成的集羣上部署應用。
RPC的核心並不在於使用什麼協議。RPC的目的是讓你在本地調用遠程的方法,而對你來說這個調用是透明的,你並不知道這個調用的方法是部署哪裏。通過RPC能解耦服務,這纔是使用RPC的真正目的。RPC的原理主要用到了動態代理模式,至於http協議,只是傳輸協議而已。簡單的實現可以參考spring remoting,複雜的實現可以參考dubbo。
簡單的說,
RPC就是從一臺機器(客戶端)上通過參數傳遞的方式調用另一臺機器(服務器)上的一個函數或方法(可以統稱爲服務)並得到返回的結果。
RPC 會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊) RPC 是一個請求響應模型。
客戶端發起請求,服務器返回響應(類似於Http的工作方式) RPC 在使用形式上像調用本地函數(或方法)一樣去調用遠程的函數(或方法)。

3.SpringCloud與Dubbo區別
SpringCloud和Dubbo都是當下流行的RPC框架,各自都集成了服務發現和治理組件。SpringCloud用Eureka,Dubbo用Zookeeper,這篇博客就將將這兩個組件在各自系統中的作用機制的區別。
區別:
1.註冊的服務的區別
Dubbo是基於java接口及Hession2序列化的來實現傳輸的,Provider對外暴露接口,Consumer根據接口的規則調用。也就是Provider向Zookeeper註冊的是接口信息,Consumer從Zookeeper發現的是接口的信息,通過接口的name,group,version來匹配調用。Consumer只關注接口是否匹配,而對此接口屬於什麼應用不關心。當然接口的註冊信息裏會包含應用的ip,hostname等。
SpringCloud的服務發現是基於Http協議來實現的,Provider對外暴露的是應用信息,比如應用名稱,ip地址等等,Consumer發現的是應用的信息,當調用的時候隨機選擇一個Provider的IP地址,應用名稱,然後依據Http協議發送請求。Consumer關注的是應用名稱,根據應用名稱來決定調用的是哪個服務集羣,然後對此名稱對應的服務集羣做負載均衡。Provider接受到請求後,根據內置的SpringMVC來匹配路由處理請求。
2 . Server集羣服務信息同步的區別
Dubbo使用Zookeeper做服務發現和治理,Zookeeper是一個分佈式協調框架,其有很多很實用的功能,服務發現僅僅是其中的一個。Zookeeper基於著名的CAP理論中的C(一致性),P(分區可用性)實現,它的ZAB(zookeeper atomic broadcast protocol)協議,保證了集羣裏狀態的一致性。Client的每一個事務操作都由Leader廣播給所有Follower,當超過半數的Follower都返回執行成功後,才執行事務的ack。對於因網絡崩潰或者宕機等問題而執行失敗的zookeeper節點,zookeeper會基於zab的崩潰恢復機制來處理,這裏不再講述。每一個操作都需要過半數的zookeeper節點執行成功才確認成功,那麼當zookeeper集羣過半數節點出現問題時,服務發現功能就不可用。
SpringCloud使用Eureka做服務發現和治理,它是一個專門用於服務發現和治理的框架,其基於CAP理論中的A(可用性),P(分區可用性)實現。EurekaServer節點間的服務信息同步是基於異步Http實現的。每隔Server節點在接收Client的服務請求時,立即處理請求,然後將此次請求的信息拷貝,封裝成一個Task,存入Queue中。Server初始化時會啓動一個線程定期的從TaskQueue中批量提取Task,然後執行。服務同步不保證一定成功,雖然有失敗重試,但超過一定時限後就放棄同步。當然其有一個特性,當服務丟失後,同步的操作返回400,404後會立即將最新的服務信息同步過去,因此即使中途同步失敗,不會對後續的同步有影響。
3 . 服務更新機制的區別
Dubbo使用Zookeeper做服務發現和治理,訂閱Zookeeper下相應的znode。當節點發生變化,比如有新的元素增加,或者舊的元素移除,Zookeeper會通知所有訂閱此節點的Client,將當前的全量數據同步給各Client,Dubbo里根據最新的數據來做相應處理,移除下線的,初始化新增的。每次更新都同步全量數據。
Eureka在啓動時向Server進行一次全量拉取,獲取所有的可用服務信息,之後默認情況下都是進行增量拉取。Server會將有變化的服務信息放在一個Queue裏,Client每次同步時僅獲取增量信息,根據信息裏的操作類型,服務信息來對當前持有的服務做相應的處理,移除下線的,初始化新增的等。每次更新僅同步增量數據,也就是更新的數據。
4 . 服務更新反饋機制的區別
Dubbo訂閱Zookeeper下相應的節點,當節點的狀態發生改變時,Zookeeper會立即反饋訂閱的Client,實時性很高。
Eureka Server在接收到Client的更新操作,或者移除服務信息時,僅僅會將更新消息存放入recentlyChangedQueue中,不會主動的反饋其他Client。其他Client只有在拉取服務增量信息時纔會感知到某個服務的更新,延時最大爲30S,也就是拉取週期。
5 . 服務信息回收機制的區別
Dubbo Provider初始化時會創建一個Zookeeper Client,專門用於與Zookeeper集羣交互。維持與集羣間的長連接,定時發送心跳,維護Zookeeper上自身節點的存在。節點類型是臨時節點,也就是當心跳超時或者長連接斷開時,會立即移除Provider對應的節點。
Dubbo Consumer初始化時也會創建一個Zookeeper Client,專門用於與Zookeeper集羣交互。維持長連接,創建EvenetListener,監聽Provider節點的變動情況。當Provider節點新增或者移除時,Zookeeper會廣播這個事件,然後將此節點的當前值(剩下的所有接口信息)發送給那些註冊了此節點監聽器的Client。Consumer獲取到對應Provider節點下的所有接口信息後,移除已下線的,創建新增的。
Zookeeper對服務信息的維護實時性和一致性比較高,但也可能因爲網絡問題或者集羣問題導致服務不可用。
SpringCloud的服務信息回收僅基於心跳超時,與長連接無關。當心跳超時後,EurekaServer回收服務信息,然後將此動作同步給其他Server節點。當然可能一個服務信息會存在多個Server上,多次回收操作的同步具備冪等性。也就是說服務回收只需要通知一個Server節點就可以了,回收動作會通過Server節點傳播開來。EurekaServer能夠回收服務信息由個重要前提:上一分鐘內正常發送心跳的服務的比列超過總數的85%,如果因爲網絡波動等原因造成大量服務的心跳超時,那麼EurekaServer會觸發自我保護機制,放棄回收那些心跳超時的服務信息。服務發現組件應該優先保證可用性,Consumer能夠發現Provider,即使發現的是非可用的Provider,但因爲Conusmer一般具備容錯機制,不會對服務的正常調用有太多影響。從這點上看Eureka的服務發現機制要比Zookeeper稍微合理一點的。
6 . 節點性質的區別
Dubbo只有Consumer訂閱Provider節點,也就是Consumer發現Provider節點信息
Eureka不區分Consumer或者Provider,兩者都統稱爲Client,一個Client內可能同時含有Provider,Consumer,通過服務發現組件獲取的是其他所有的Client節點信息,在調用時根據應用名稱來篩選節點
7 . 使用方式的區別
Dubbo使用Zookeeper作爲服務發現和治理的組件,所以需要搭建Zookeeper集羣作爲依賴。
SpringCloud使用Eureka作爲服務發現和治理組件,在Spring應用中整合Eureka還是很簡單的,引入依賴,加個註解,指定集羣Server的serviceUrl,其他的都可以使用默認配置即可,啓動應用,Eureka集羣就搭建好了。同時配合SpringCloudConfg,能夠統一管理Eureka的集羣配置信息,可以動態的增加或減少EurekaServer的集羣節點。Eurerka會每隔15分鐘根據配置上的集羣信息重新生成集羣節點,覆蓋之前的。這種機制比Zookeeper要更優秀一些,畢竟Eureka算是Spring生態裏的一環,已經被整合的非常好了,能夠以很多匪夷所思的方式來使用。

4.try catch finally執行順序
 異常處理中,try、catch、finally的執行順序,大家都知道是按順序執行的。即,如果try中沒有異常,則順序爲try→finally,如果try中有異常,則順序爲try→catch→finally。但是當try、catch、finally中加入return之後,就會有幾種不同的情況出現,下面分別來說明一下。也可以跳到最後直接看總結。

一、try中帶有return

1 private int testReturn1() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 return i;
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 } finally {
11 i++;
12 System.out.println(“finally:” + i);
13 }
14 return i;
15 }

輸出:
try:2
finally:3
2
  因爲當try中帶有return時,會先執行return前的代碼,然後暫時保存需要return的信息,再執行finally中的代碼,最後再通過return返回之前保存的信息。所以,這裏方法返回的值是try中計算後的2,而非finally中計算後的3。但有一點需要注意,再看另外一個例子:

1 private List testReturn2() {
2 List list = new ArrayList<>();
3 try {
4 list.add(1);
5 System.out.println(“try:” + list);
6 return list;
7 } catch (Exception e) {
8 list.add(2);
9 System.out.println(“catch:” + list);
10 } finally {
11 list.add(3);
12 System.out.println(“finally:” + list);
13 }
14 return list;
15 }

輸出:
try:[1]
finally:[1, 3]
[1, 3]
  看完這個例子,可能會發現問題,剛提到return時會臨時保存需要返回的信息,不受finally中的影響,爲什麼這裏會有變化?其實問題出在參數類型上,上一個例子用的是基本類型,這裏用的引用類型。list裏存的不是變量本身,而是變量的地址,所以當finally通過地址改變了變量,還是會影響方法返回值的。

二、catch中帶有return

1 private int testReturn3() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 int x = i / 0 ; s
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 return i;
11 } finally {
12 i++;
13 System.out.println(“finally:” + i);
14 }
15 return i;
16 }

輸出:
try:2
catch:3
finally:4
3
  catch中return與try中一樣,會先執行return前的代碼,然後暫時保存需要return的信息,再執行finally中的代碼,最後再通過return返回之前保存的信息。所以,這裏方法返回的值是try、catch中累積計算後的3,而非finally中計算後的4。

三、finally中帶有return

1 private int testReturn4() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 return i;
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 return i;
11 } finally {
12 i++;
13 System.out.println(“finally:” + i);
14 return i;
15 }
16 }

輸出:
try:2
finally:3
3
  當finally中有return的時候,try中的return會失效,在執行完finally的return之後,就不會再執行try中的return。這種寫法,編譯是可以編譯通過的,但是編譯器會給予警告,所以不推薦在finally中寫return,這會破壞程序的完整性,而且一旦finally裏出現異常,會導致catch中的異常被覆蓋。

總結:

1、finally中的代碼總會被執行。
2、當try、catch中有return時,也會執行finally。return的時候,要注意返回值的類型,是否受到finally中代碼的影響。
3、finally中有return時,會直接在finally中退出,導致try、catch中的return失效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章