阿里巴巴面試題總結(java後端)-第二章

forrunning發表於2019-04-09

③,三面 1,介紹你實踐的效能優化例子,以及你優化的思路 結合自己在做的具體業務,同步變非同步(無強依賴的服務呼叫改成併發呼叫),異地快取多活,異地雙主mysql,hystrix熔斷,限流機制,99線統計,熱點資料快取化等等。

2,微服務和soa的區別,優劣勢?

SOA:微服務是細粒度的SOA(面向服務架構),一個SOA元件可以拆分成多個微服務,SOA的服務架構依賴ESB企業服務匯流排(專注應用程式服務的可重用性的最大化)。 微服務:微服務架構是一個大的系統按照功能拆分成很多獨立的子服務,每個子服務獨立實現自己的業務邏輯,各個微服務之間的關聯通過暴露api來實現。這些獨立的微服務不需要部署在同一個虛擬機器,同一個系統和同一個應用伺服器中(微服務架構專注於解耦)。

SOA架構的優點 <1>系統鬆耦合,提高傳統企業的功能模組的可重用性。 <2>決企業系統間的通訊問題,把原先散亂、無規劃的系統間的網狀結構,梳理成 規整、可治理的系統間星形結構。

SOA架構的缺點 <1>嚴重依賴比較重的ESB企業服務匯流排,企業匯流排出了問題,就導致整個架構的不可能。 <2>服務粗化,比如讀寫負載不同的功能耦合在一個服務裡面。

微服務架構模式優點: <1>由於每個服務都是獨立並且微小的,由單獨的團隊負責,採用敏捷開發模式,迭代效率很高。 <2>每一個微服務都是獨立部署的,可根據當前服務的負載程度選擇不同配置的機器。

微服務架構的缺點: <1>微服務應用作為分散式系統帶來了複雜性。各個微服務進行分散式獨立部署,由不同的技術團隊維護,因此經常出現跨團隊溝通,效率容易不可控。 <2>微服務架構一般使用各個獨立資料庫,分散式事務的實現更具挑戰性。 <3>測試微服務變得複雜,通常一個服務會依賴很多服務,測試環境下其他服務的不可靠,容易影響測試進度。

備註:企業服務匯流排(ESB)就是一條企業架構的匯流排,所有的企業服務都掛接到該匯流排上對外公佈,企業服務匯流排負責管理服務目錄,解析服務請求者的請求方法、訊息格式,並對服務提供者進行定址,轉發服務請求

3,sql慢查詢的優化方案,索引和表的優化方案 慢查詢優化方案: <0>explain檢查sql語句是否索引覆蓋 <1>能使用主鍵查詢的儘量使用主鍵查詢 <2>連線查詢代(join on)替子查詢(in)和聯合查詢(from t1,t2),避免在記憶體從形成巨大的笛卡爾積,多表連線時,儘量小表驅動大表,即小表join大表 <3>批量掃表時,每次查詢應該使用上次查詢返回的主鍵id作為下一輪的查詢條件,提高查詢效率。 <4>組合索引使用應該滿足最左原則。 <5>資料量比較的時候,儘量使用limit進行物理分頁查詢 <6>查詢時,返回結果能不要就不用,儘量寫全欄位名

索引優化: <1>只要列中含有NULL值,就最好不要在此例設定索引,複合索引如果有NULL值,此列在使用時也不會使用索引 <2>儘量使用短索引,索引欄位不易過長 <3>儘量不要使用not in和<>操作 <4>對於like語句,以%或者‘-’開頭的不會使用索引,以%結尾會使用索引 <5>儘量不要在列上進行運算(函式操作和表示式操作) <6>欄位範圍很窄(比如只有1,2,3種可能),就沒必要建立索引,對查詢效率沒有太大的提升。

表的優化: <1>表的欄位儘可能用NOT NULL <2>欄位長度固定的表查詢會更快 <3>資料量超過2000w級別的,可以考慮分表

4,Mysql和MongoDB的區別,海量資料量如何儲存? Mysql是關係型資料庫,MongoDB是非關係型文件資料庫。

備註:MongoDB將資料儲存為一個文件,資料結構由鍵值(key=>value)對組成。MongoDB 文件類似於 JSON 物件。欄位值可以包含其他文件,陣列及文件陣列。

<1>Mysql:無論資料還是索引都存放在硬碟中。到要使用的時候才交換到記憶體中,能夠處理遠超過記憶體總量的資料,單表單庫不適合儲存海量資料。

<2>MongoDB,虛擬記憶體+持久化,在適量級的記憶體的 MongoDB 的效能是非常迅速的,它將熱資料儲存在實體記憶體中,使得熱資料的讀寫變得十分快,MongoDB 的所有資料實際上是存放在硬碟的,所有要操作的資料通過mmap的方式對映到記憶體某個區域內。然後MongoDB 就在這塊區域裡面進行資料修改,避免了零碎的硬碟操作。 MongoDB的索引放在記憶體中,能夠提升隨機讀寫的效能。如果索引不能完全放在記憶體,一旦出現隨機讀寫比較高的時候,就會頻繁地進行磁碟交換,MongoDB 的效能就會急劇下降(MongoDB不支援事務)

海量資料處理:MongoDB 以BSON結構(二進位制)進行儲存,對海量資料儲存有著很明顯的優勢,支援服務端指令碼和Map/Reduce,可以實現海量資料計算,即實現雲端計算功能。

參考:blog.csdn.net/CatStarXcod…

5,快取框架,例如redis和memcache之間的區別,優劣勢 <1>儲存策略:memcached超過記憶體比例會抹掉前面的資料,而redis拒絕寫入記憶體。 <2>支援資料型別:memcached只支援string,redis支援更多。如:hash,list,set,sorted。 <3>redis支援兩種持久化策略(rdb,aof),memcached <4>災難恢復–memcache掛掉後,資料不可恢復; redis資料丟失後可以通過rdb、aof方式恢復。 <5>memcache是單程式多執行緒,redis是單執行緒的。 <6>Memcached單個key-value大小有限,一個value最大隻支援1MB,而Redis最大支援512MB <7>分散式–設定memcache叢集,利用magent做一主多從;redis可以做一主多從。都可以一主一從。 <8>memcache是兩階段hash(第一次是一致性hash先找叢集中的例項,第二次是找kv資料),redis是通過crc16演算法計算出slot中的位置,再通過hash查詢出資料。

6,請描述一下一致性hash演算法 具體演算法過程為:先構造一個長度為232次方的整數環(這個環被稱為一致性Hash環),根據節點名稱的Hash值(其分佈為[0, 232次方-1])將快取伺服器節點放置在這個Hash環上,然後根據需要快取的資料的Key值計算得到其Hash值(其分佈也為[0, 232-1]),然後在Hash環上順時針查詢距離這個Key值的Hash值最近的伺服器節點,完成Key到伺服器的對映查詢。 舉例:三個Node點分別位於Hash環上的三個位置,然後Key值根據其HashCode,在Hash環上有一個固定位置,位置固定下之後,Key就會順時針去尋找離它最近的一個Node,把資料儲存在這個Node的Cache(如memcache)伺服器中。

一致性hash演算法解決的問題:主要是考慮到分散式系統每個節點都有可能失效,或者新的節點很可能動態的增加進來的情況,我們只需要移動最少的資料,就可以保證資料的均勻分配 一致性hash演算法,減少了資料對映關係的變動,不會像hash(i)%N那樣帶來全域性的變動 普通的餘數hash,分流時機器當機會產生失敗請求,容易引起請求丟失。即使mod值(hash取模值)變化,如果是redis叢集,要重新移動key進行分配,資料遷移 普通的餘數hash(hash(比如使用者id)%伺服器機器數)演算法伸縮性很差,當新增或者下線伺服器機器時候,使用者id與伺服器的對映關係會大量失效。 一致性hash環的資料傾斜問題:一致性Hash演算法在服務節點太少時,容易因為節點分部不均勻而造成資料傾斜(被快取的物件大部分集中快取在某一臺伺服器上)問題

參考:www.cnblogs.com/lpfuture/p/…

7,分散式session的共享方案有哪些,有什麼優劣勢 背景:Session是伺服器用來儲存使用者操作的一系列會話資訊,由Web容器進行管理。單機情況下,不存在Session共享的情況,分散式情況下,如果不進行Session共享會出現請求落到不同機器要重複登入的情況,一般來說解決Session共享有以下幾種方案。

<1>、session複製 session複製是早期的企業級的使用比較多的一種伺服器叢集session管理機制。應用伺服器開啟web容器的session複製功能,在叢集中的幾臺伺服器之間同步session物件,使得每臺伺服器上都儲存所有的session資訊(不同功能應用的機器儲存了其它應用的session),這樣任何一臺當機都不會導致session的資料丟失,伺服器使用session時,直接從本地獲取。

缺點:應用叢集變的龐大以後,就會出現瓶頸,每臺都需要備份session,佔用的空間變的非常大,出現記憶體不夠用的情況。

<2>,利用cookie記錄session session記錄在客戶端,每次請求伺服器的時候,將session放在請求中傳送給伺服器,伺服器處理完請求後再將修改後的session響應給客戶端。這裡的客戶端就是cookie。

缺點:受cookie大小的限制,能記錄的資訊有限;每次請求響應都需要傳遞cookie,影響效能,如果使用者關閉cookie,訪問就不正常。

<3>session持久化(mysql) 缺點:不適合高併發的場景,mysql讀取效能受限。

<4>session繫結 利用hash演算法,比如nginx的ip_hash,使得同一個ip的請求分發到同一臺伺服器上。 缺點:這種方式不符合對系統的高可用要求,因為一旦某臺伺服器當機,那麼該機器上的session也就不復存在了,使用者請求切換到其他機器後麼有session,無法完成業務處理。

參考:www.jianshu.com/p/221f8a42b…

<5>session伺服器 session伺服器(基於redis、memcache儲存)可以解決上面的所有的問題,利用獨立部署的session伺服器(叢集)統一管理session,伺服器每次讀寫session時,都訪問session伺服器(需要我們實現session叢集服務的高可用)

8,高併發情況,系統的優化方案有哪些?以及優先順序排序

④,其它題目補充 1,自旋鎖和偏向鎖 背景:併發程式設計中synchronized是重量級鎖,但隨著JVM1.6對synchronized進行優化後,有些情況下它並不那麼重,實際上效能不亞於lock。 Synchronized的鎖升級過程:偏向鎖--》輕量級鎖--》重量級鎖 整個過程是單向的,不支援降級。

簡單理解:單執行緒(thread1)狀態是使用偏向鎖,thread1執行緒在執行過程中,別的執行緒(thread2)過來,發現鎖處於鎖處於偏向鎖狀態,thread2會把鎖改成輕量級鎖,此時thread2進入自旋狀態(類似while的死迴圈),並且不釋放cpu直至等待到鎖,實際上自旋狀態有超時限制的(避免長時間消耗cpu)。thread2執行過程中,thread3過來獲取鎖,發現鎖處於輕量級鎖狀態,會升級成重量級鎖(這種場景一般都是處於高併發狀態了)。

偏向鎖 大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。當一個執行緒訪問同步塊並獲取鎖(通過cas機制)時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入(鎖重入)和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設定成1(表示當前是偏向鎖):如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。 注意:當鎖有競爭關係的時候,需要解除偏向鎖,進入輕量級鎖。

輕量級鎖 <1>加鎖 執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。 <2>解鎖 輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到物件頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

自旋鎖 自旋鎖原理非常簡單,如果持有鎖的執行緒能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的執行緒就不需要做核心態和使用者態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的執行緒釋放鎖後即可立即獲取鎖,這樣就避免使用者執行緒和核心的切換的消耗。但是執行緒自旋是需要消耗cup的,說白了就是讓cup在做無用功,如果一直獲取不到鎖,那執行緒也不能一直佔用cup自旋做無用功,所以需要設定一個自旋等待的最大時間(最大自旋次數)。如果持有鎖的執行緒執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的執行緒在最大等待時間內還是獲取不到鎖,這時爭用執行緒會停止自旋進入阻塞狀態。

參考:www.jianshu.com/p/1ea87c152… 補充:關於cas,cmpxchg的底層講解,參考:www.cnblogs.com/luconsole/p…

2,volatile關鍵字 <1>記憶體可見性,執行緒修改完變數資料後,會立馬寫入到共享記憶體中,其它執行緒若讀取該變數,會強制從共享記憶體中獲取,但是不能保證執行緒安全(已經載入進入棧空間的變數,只有再次讀取,才會從共享記憶體取資料),volatile修飾的變數,在寫入共享記憶體的時候,是使用cpu的lock指令,這個過程是lock核心匯流排,保證資料的安全性。 <2>防止指令重排,編譯階段,編譯器在生成位元組碼時,會在指令序列中插入記憶體屏障來禁止特定型別的處理器重排序(普通的程式碼程式,編譯器在編譯期對沒有前後依賴關係的程式碼做一些重排優化)。

3,Synchronized,volatile,reentrantlock的底層實現存在的關聯 <1>Synchronized在獲取同步監視器的時候,是使用cas機制修改物件頭,cas機制在cpu實現上使用lock(cmpxchg)指令鎖住匯流排。 <2>volatile在資料寫入共享記憶體時也使用lock指令鎖住匯流排,保證共享記憶體資料可見性。 <3>reentrantlock實現使用了AQS,AQS基於cas機制實現,底層也使用了lock指令。

3,什麼是陣列和連結串列?什麼情況下使用二者

其它面試題: 1,mp.weixin.qq.com/s/Y2lxsucvk…? 2,mp.weixin.qq.com/s/bc4cc6OUE… 3,mp.weixin.qq.com/s/TarTEBF3N…? 4,mp.weixin.qq.com/s/aPSOH1VoL…

其它知識點總結 1,什麼是快取擊穿? 快取是加速系統響應的一種途徑,通常情況下只有系統的部分資料。當請求了快取中沒有的資料時,這時候就會回源到DB裡面。此時如果黑客故意對上面資料發起大量請求,則DB有可能會掛掉,這就是快取擊穿。當然快取掛掉的話,正常的使用者請求也有可能造成快取擊穿的效果。(實際應用中經常遇到第三方爬蟲,也容易導致快取擊穿) 解決快取擊穿的方案:布隆過濾器 bloom演算法類似一個hash set,用來判斷某個元素(key)是否在某個集合中。 和一般的hash set不同的是,這個演算法無需儲存key的值,對於每個key,只需要k個位元位,每個儲存一個標誌,用來判斷key是否在集合中。

補充:快取穿透前面加一層布隆過濾器,快取雪崩訪問本地兜底資料,如果兜底資料沒有命中,轉發流量到mysql(限流,實際上沒命中熱點兜底資料的流量很低),熱點資料集中失效(可以對有訪問的資料進行失效時間延長續命) 參考:juejin.im/post/5c9a67…

演算法:

  1. 首先需要k個hash函式,每個函式可以把key雜湊成為1個整數
  2. 初始化時,需要一個長度為n位元的陣列,每個位元位初始化為0
  3. 某個key加入集合時,用k個hash函式計算出k個雜湊值,並把陣列中對應的位元位置為1
  4. 判斷某個key是否在集合時,用k個hash函式計算出k個雜湊值,並查詢陣列中對應的位元位,如果所有的位元位都是1,認為在集合中。 優點:不需要儲存key,節省空間

2,netty的Reactor模式為什麼使用多執行緒模型?(參考:blog.csdn.net/xiaolang85/…)

<1>單執行緒模型存在的問題:

由於Reactor模式使用的是同步非阻塞IO,所有的IO操作都不會導致阻塞,理論上一個執行緒可以獨立處理所有IO相關的操作。從架構層面看,一個NIO執行緒確實可以完成其承擔的職責。例如,通過Acceptor類接收客戶端的TCP連線請求訊息,鏈路建立成功之後,通過Dispatch將對應的ByteBuffer派發到指定的Handler上進行訊息解碼。使用者執行緒可以通過訊息編碼通過NIO執行緒將訊息傳送給客戶端。 對於一些小容量應用場景,可以使用單執行緒模型。但是對於高負載、大併發的應用場景卻不合適,主要原因如下: 1)一個NIO執行緒同時處理成百上千的鏈路,效能上無法支撐,即便NIO執行緒的CPU負荷達到100%,也無法滿足海量訊息的編碼、解碼、讀取和傳送; 2)當NIO執行緒負載過重之後,處理速度將變慢,這會導致大量客戶端連線超時,超時之後往往會進行重發,這更加重了NIO執行緒的負載,最終會導致大量訊息積壓和處理超時,成為系統的效能瓶頸; 3)可靠性問題:一旦NIO執行緒意外跑飛,或者進入死迴圈,會導致整個系統通訊模組不可用,不能接收和處理外部訊息,造成節點故障。

備註:比如一次netty訪問一個網路io,返回的資料是二進位制流,此時如果讓selector執行緒(實際是netty中的boss和worker執行緒)負責反序列化,就會阻塞selector,無法接受更多的連線,這種模型明顯不合理的。

<2>Reactor多執行緒模型 Reactor多執行緒模型的特點: 1)有專門一個NIO執行緒-Acceptor執行緒用於監聽服務端,接收客戶端的TCP連線請求; 2)網路IO操作-讀、寫等由一個NIO執行緒池負責,執行緒池可以採用標準的JDK執行緒池實現,它包含一個任務佇列和N個可用的執行緒,由這些NIO執行緒負責訊息的讀取、解碼、編碼和傳送; 3)1個NIO執行緒可以同時處理N條鏈路,但是1個鏈路只對應1個NIO執行緒(1個NIO執行緒可以處理所有的鏈路) 在絕大多數場景下,Reactor多執行緒模型都可以滿足效能需求;但是,在極個別特殊場景中,一個NIO執行緒負責監聽和處理所有的客戶端連線可能會存在效能問題。例如併發百萬客戶端連線,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗效能。在這類場景下,單獨一個Acceptor執行緒可能會存在效能不足問題,為了解決效能問題,產生了第三種Reactor執行緒模型-主從Reactor多執行緒模型。

<3>主從多執行緒模型 主從Reactor執行緒模型的特點是:服務端用於接收客戶端連線的不再是個1個單獨的NIO執行緒,而是一個獨立的NIO執行緒池。Acceptor接收到客戶端TCP連線請求處理完成後(可能包含接入認證等),將新建立的SocketChannel註冊到IO執行緒池(sub reactor執行緒池)的某個IO執行緒上,由它負責SocketChannel的讀寫和編解碼工作。Acceptor執行緒池僅僅只用於客戶端的登陸、握手和安全認證,一旦鏈路建立成功,就將鏈路註冊到後端subReactor執行緒池的IO執行緒上,由IO執行緒負責後續的IO操作。

利用主從NIO執行緒模型,可以解決1個服務端監聽執行緒無法有效處理所有客戶端連線的效能不足問題。 它的工作流程總結如下: 從主執行緒池中隨機選擇一個Reactor執行緒作為Acceptor執行緒,用於繫結監聽埠,接收客戶端連線; Acceptor執行緒接收客戶端連線請求之後建立新的SocketChannel,將其註冊到主執行緒池的其它Reactor執行緒上,由其負責接入認證、IP黑白名單過濾、握手等操作; 步驟2完成之後,業務層的鏈路正式建立,將SocketChannel從主執行緒池的Reactor執行緒的多路複用器上摘除,重新註冊到Sub執行緒池的執行緒上,用於處理I/O的讀寫操作。

<4>服務端執行緒模型 一種比較流行的做法是服務端監聽執行緒和IO執行緒分離,類似於Reactor的多執行緒模型(Netty同時支援Reactor的單執行緒、多執行緒和主從多執行緒模型,在不同的應用中通過啟動引數的配置來啟動不同的執行緒模型) 第一步,從使用者執行緒發起建立服務端操作 通常情況下,服務端的建立是在使用者程式啟動的時候進行,因此一般由Main函式或者啟動類負責建立,服務端的建立由業務執行緒負責完成。在建立服務端的時候例項化了2個EventLoopGroup,1個EventLoopGroup實際就是一個EventLoop執行緒組,負責管理EventLoop的申請和釋放。 EventLoopGroup管理的執行緒數可以通過建構函式設定,如果沒有設定,預設取-Dio.netty.eventLoopThreads,如果該系統引數也沒有指定,則為可用的CPU核心數 × 2。 bossGroup執行緒組實際就是Acceptor執行緒池,負責處理客戶端的TCP連線請求,如果系統只有一個服務端埠需要監聽,則建議bossGroup執行緒組執行緒數設定為1。 workerGroup是真正負責I/O讀寫操作的執行緒組,通過ServerBootstrap的group方法進行設定,用於後續的Channel繫結。

第二步,Acceptor執行緒繫結監聽埠,啟動NIO服務端,相關程式碼如下: 從bossGroup中選擇一個Acceptor執行緒監聽服務端 其中,group()返回的就是bossGroup,它的next方法用於從執行緒組中獲取可用執行緒,程式碼如下: 選擇Acceptor執行緒 服務端Channel建立完成之後,將其註冊到多路複用器Selector上,用於接收客戶端的TCP連線,核心程式碼如下: 圖2-5 註冊ServerSocketChannel 到Selector 第三步,如果監聽到客戶端連線,則建立客戶端SocketChannel連線,重新註冊到workerGroup的IO執行緒上。首先看Acceptor如何處理客戶端的接入: 圖2-6 處理讀或者連線事件 呼叫unsafe的read()方法,對於NioServerSocketChannel,它呼叫了NioMessageUnsafe的read()方法,程式碼如下: 圖2-7 NioServerSocketChannel的read()方法 最終它會呼叫NioServerSocketChannel的doReadMessages方法,程式碼如下: 圖2-8 建立客戶端連線SocketChannel 其中childEventLoopGroup就是之前的workerGroup, 從中選擇一個I/O執行緒負責網路訊息的讀寫。 第四步,選擇IO執行緒之後,將SocketChannel註冊到多路複用器上,監聽READ操作。 圖2-9 監聽網路讀事件 第五步,處理網路的I/O讀寫事件,核心程式碼如下: 圖2-10 處理讀寫事件

備註:Dubbo預設的底層網路通訊使用的是Netty,服務提供方NettyServer使用兩級執行緒池,其中 EventLoopGroup(boss) 主要用來接受客戶端的連結請求,並把接受的請求分發給 EventLoopGroup(worker) 來處理,boss和worker執行緒組我們稱之為IO執行緒。

3,dubbo常見面試題(blog.csdn.net/Y0Q2T57s/ar… <1>dubbo為什麼使用執行緒池 實際上是可以選擇是否使用執行緒池,如果不使用執行緒池,對於一些長耗時的網路io(響應的資料比較大),selector執行緒(實際就是netty中的worker執行緒)會阻塞在處理結果(比如網路io響應的結果進行序列化),導致selector無法接受更多的請求,這些長耗時的邏輯應該下沉到業務執行緒池裡面,與netty執行緒隔離開。

備註:對於長耗時的服務,實際上無論非同步或者同步,都應該使用執行緒池,非同步模式是基於nio+future實現,長耗時的網路io執行結果還是需要用到執行緒池去支援序列化,不應該消耗worker執行緒。

<2>dubbo的事件派發策略和執行緒池(預設使用了執行緒池) dubbo基於netty。有5種派發策略: 預設是all:所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。 即worker執行緒接收到事件後,將該事件提交到業務執行緒池中,自己再去處理其他事。 direct:worker執行緒接收到事件後,由worker執行到底。 message:只有請求響應訊息派發到執行緒池,其它連線斷開事件,心跳等訊息,直接在 IO執行緒上執行 execution:只請求訊息派發到執行緒池,不含響應(客戶端執行緒池),響應和其它連線斷開事件,心跳等訊息,直接在 IO 執行緒上執行 connection:在 IO 執行緒上,將連線斷開事件放入佇列,有序逐個執行,其它訊息派發到執行緒池。

參考:blog.csdn.net/wanbf123/ar…

<3>Dubbo提供的執行緒池策略 擴充套件介面 ThreadPool 的SPI實現有如下幾種: fixed:固定大小執行緒池,啟動時建立執行緒,不關閉,一直持有(預設)。 cached:快取執行緒池,空閒一分鐘自動刪除,需要時重建。 limited:可伸縮執行緒池,但池中的執行緒數只會增長不會收縮。只增長不收縮的目的是為了避免收縮時突然帶來大流量引起效能問題

4,tcp粘包問題分析與對策(www.cnblogs.com/kex1n/p/650… TCP粘包是指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾,出現粘包現象的原因是多方面的,它既可能由傳送方造成,也可能由接收方造成。

實際上是傳輸層不知道報文在哪裡隔斷,也就是說傳輸層不關注邊界,應用層自己解決,因此任何基於tcp傳輸協議的通訊框架都需要自己解決粘包問題。 備註:在流傳輸中出現,UDP不會出現粘包,因為它有訊息邊界(可以理解為每條訊息)

TCP無保護訊息邊界的解決

針對這個問題,一般有3種解決方案(netty也是這樣做): (1)傳送固定長度的訊息(不夠的可以通過補0填充),這樣接收端每次從接收緩衝區中讀取固定長度的資料就自然而然的把每個資料包拆分開來。 (2)把訊息的尺寸與訊息一塊傳送(訊息頭(訊息頭包含某條訊息的長度)和訊息體一塊傳送) (3)使用特殊標記來區分訊息間隔

5,為什麼基於TCP的通訊程式需要進行封包和拆包

TCP是個"流"協議,所謂流,就是沒有界限的一串資料,大家可以想想河裡的流水,是連成一片的,其間是沒有分界線的。但一般通訊程式開發是需要定義一個個相互獨立的資料包的,比如用於登陸的資料包,用於登出的資料包。由於TCP"流"的特性以及網路狀況,在進行資料傳輸時會出現以下幾種情況。

假設我們連續呼叫兩次send分別傳送兩段資料data1和data2,在接收端有以下幾種接收情況(當然不止這幾種情況,這裡只列出了有代表性的情況).

A.先接收到data1,然後接收到data2.

B.先接收到data1的部分資料,然後接收到data1餘下的部分以及data2的全部.

C.先接收到了data1的全部資料和data2的部分資料,然後接收到了data2的餘下的資料.

D.一次性接收到了data1和data2的全部資料.

對於A這種情況正是我們需要的,不再做討論.對於B,C,D的情況就是大家經常說的"粘包",就需要我們把接收到的資料進行拆包,拆成一個個獨立的資料包,為了拆包就必須在傳送端進行封包。

另:對於UDP來說就不存在拆包的問題,因為UDP是個"資料包"協議,也就是兩段資料間是有界限的,在接收端要麼接收不到資料要麼就是接收一個完整的一段資料,不會少接收也不會多接收

6,netty針對tcp粘包的幾種解決方案 <1>回車換行解碼器:LineBasedFrameDecoder <2>特殊分隔符解碼器:DelimiterBasedFrameDecoder,回車換行解碼器實際上是一種特殊的DelimiterBasedFrameDecoder解碼器。 <3>定長解碼器:FixedLengthFrameDecoder,對於定長訊息,如果訊息實際長度小於定長,則往往會進行補位操作,它在一定程度上導致了空間和資源的浪費。但是它的優點也是非常明顯的,編解碼比較簡單,因此在實際專案中仍然有一定的應用場景 <4>基於包頭不固定長度的解碼器:LengthFieldBasedFrameDecoder,協議頭中會攜帶長度欄位,用於標識訊息體或者整包訊息的長度,例如SMPP、HTTP協議等。由於基於長度解碼需求的通用性,以及為了降低使用者的協議開發難度,Netty提供了LengthFieldBasedFrameDecoder,自動遮蔽TCP底層的拆包和粘包問題,只需要傳入正確的引數,即可輕鬆解決“讀半包“問題。LengthFieldBasedFrameDecoder比較靈活通用,由客戶端告訴接收端,我傳輸的報文有多長了,接收端根據長度來解析。

7,netty面試題(blog.csdn.net/baiye_xing/…

8,Netty 中Channel、EventLoop、Thread、EventLoopGroup之間的關係 EventLoop定義了Netty的核心抽象,用於處理連線的生命週期中所發生的事件。 一個EventLoopGroup包含一個或者多個EventLoop。 一個EventLoop在它的生命週期內只和一個Thread繫結。 所有由EventLoop處理的I/O事件都將在它專有的Thread上被處理。 一個Channel在它的生命週期內只註冊於一個EventLoop。 一個EventLoop可能會被分配給一個或多個Channel。 在這種設計中,一個給定Channel的I/O操作都是由相同的Thread執行的。

9,NIOEventLoopGroup是怎麼與Reactor關聯在一起的呢? 其實NIOEventLoopGroup就是一個執行緒池實現,通過設定不同的NIOEventLoopGroup方式就可以對應三種不同的Reactor執行緒模型。 <1>單執行緒模型 EventLoopGroup bossGroup = new NioEventLoopGroup(1); 例項化了一個NIOEventLoopGroup,構造引數是1表示是單執行緒的執行緒池 <2>多執行緒模型 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(N); bossGroup 中只有一個執行緒, 在workerGroup執行緒池中我沒有指定執行緒數量,所以預設是CPU 核心數乘以2, 因此對應的到Reactor執行緒模型中,這樣設定的 NioEventLoopGroup 其實就是Reactor多執行緒模型。

<3>主從Reactor執行緒模型 EventLoopGroup bossGroup = new NioEventLoopGroup(4); EventLoopGroup workerGroup = new NioEventLoopGroup(N); Boss可以負責鑑權等工作

參考:blog.csdn.net/u010853261/…

10,rpc的future模式 pigeon的原始碼分析,對比一下原始碼,實際上就是netty多執行緒模型的底層執行緒池(如果不開執行緒,就是selector執行緒輪詢結果)執行完畢,將結果放在當前的前程的threadLocal裡面,將來呼叫future.get獲取 參考:blog.csdn.net/ningdunquan…

11,如何利用壓測工具挖掘服務的效能?(jvm調優,gc調優) <1>比如可以通過ptest進行壓測,觀測壓測期間是否出現頻繁的fullgc,如果出現,間接反映出物件的使用不太合理,比如一個邏輯只需要獲取一個簡單的資料,但是呼叫的服務返回的是一個大物件,假設呼叫鏈很長,這樣就會長期佔用很大的堆記憶體空間,因此最好根據需求(查詢請求設定要求返回的欄位資料)返回,不需要返回多餘的資料(按需索取),提高服務的響應效能。使用堆外記憶體,也會導致機器剩餘可分配的堆內的空間少了,但是空間小了,full gc的也會變短,因此gc不需要掃描那麼多空間。

<2>本地快取由堆記憶體遷移到堆外記憶體,避免本地快取的資料長期佔用大量的堆記憶體空間和導致頻繁的full gc。 參考:www.cnblogs.com/andy-zhou/p…

12,深度理解select、poll和epoll select的缺點: <1>單個程式能夠監視的檔案描述符的數量存在最大限制,通常是1024,當然可以更改數量(如果修改,就得要自己重新編譯核心,重新安裝系統,實際很多創業公司都沒自己的核心工程師),但由於select採用輪詢的方式掃描檔案描述符,檔案描述符數量越多,效能越差;(在linux核心標頭檔案中,有這樣的定義:#define __FD_SETSIZE    1024 <2>核心/使用者空間記憶體拷貝問題,select需要複製大量的控制程式碼資料結構,產生巨大的開銷; <3>select返回的是含有整個控制程式碼的陣列,應用程式需要遍歷整個陣列才能發現哪些控制程式碼發生了事件,實際上這個時間是o(n),時間開銷比較高(select中,當有事件就緒時,核心修改引數以通知使用者,使用者需要遍歷所有的fd判斷是哪個fd就緒,應用程式索引就緒檔案描述符的時間複雜度是O(n),IO效率隨著監聽的fd的數目增加而線性下降。) <4>select的觸發方式是水平觸發,應用程式如果沒有完成對一個已經就緒的檔案描述符進行IO操作,那麼之後每次select呼叫還是會將這些檔案描述符通知程式。

poll的缺點: 相比select模型,poll使用連結串列儲存檔案描述符,因此沒有了監視檔案數量的限制,但其他三個缺點依然存在(不斷輪詢所有的控制程式碼陣列,耗時還是很長)

參考:blog.csdn.net/wendy_keepi…

epoll的特點: epoll中註冊了回撥函式,當有就緒事件發生的時候,裝置驅動程式呼叫回撥函式,將就緒的fd新增到就緒連結串列rdllist中,呼叫epoll_wait時,將rdllist上就緒的fd傳送給使用者,應用程式索引就緒檔案描述符的時間複雜度是O(1),IO效率與fd的數目無關,

1)沒有最大併發連線的限制,能開啟FD的上限遠大於1024(1G的記憶體上能監聽約10萬個埠);

2)效率提升。不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會呼叫callback函式;

即epoll最大的優點就在於它只管你“活躍”的連線,而跟連線總數無關,因此在實際的網路環境中,epoll的效率就會遠遠高於select和poll。

3)記憶體對映。epoll通過核心和使用者空間共享一塊記憶體來實現訊息傳遞的。利用mmap()檔案對映記憶體加速與核心空間的訊息傳遞;即epoll使用mmap 減少複製開銷。epoll保證了每個fd在整個過程中只會拷貝一次(select,poll每次呼叫都要把fd集合從使用者態往核心態拷貝一次)。

補充: epoll永遠比select高效嗎? 不一定! epoll適用於連線較多,活動數量較少的情況。 (1)epoll為了實現返回就緒的檔案描述符,維護了一個紅黑樹和好多個等待佇列,核心開銷很大。如果此時監聽了很少的檔案描述符,底層的開銷會得不償失;

(2)epoll中註冊了回撥函式,當有時間發生時,伺服器裝置驅動呼叫回撥函式將就緒的fd掛在rdllist上,如果有很多的活動,同一時間需要呼叫的回撥函式數量太多,伺服器壓力太大。

select適用於連線較少的情況。 當select上監聽的fd數量較少,核心通知使用者現在有就緒事件發生,應用程式判斷當前是哪個fd就緒所消耗的時間複雜度就會大大減小。

todo: LT:level trigger, 水平觸發模式 ET:edge trigger, 邊緣觸發模式

14,根據OSI參考模型分為(從上到下):物理層->資料鏈路層->網路層->傳輸層->會話層->表示層->應用層。 TCP/IP層次模型共分為四層:應用層->傳輸層->網路層->資料鏈路層。 參考:www.cnblogs.com/kevingrace/…

15,spring bean生命週期 Spring框架中,一旦把一個Bean納入Spring IOC容器之中,這個Bean的生命週期就會交由容器進行管理,一般擔當管理角色的是BeanFactory或者ApplicationContext,認識一下Bean的生命週期活動,對更好的利用它有很大的幫助:

下面以BeanFactory為例,說明一個Bean的生命週期活動 Bean的建立, 由BeanFactory讀取Bean定義檔案,並生成各個例項 Setter注入,執行Bean的屬性依賴注入 BeanNameAware的setBeanName(), 如果實現該介面,則執行其setBeanName方法 BeanFactoryAware的setBeanFactory(),如果實現該介面,則執行其setBeanFactory方法 BeanPostProcessor的processBeforeInitialization(),如果有關聯的processor,則在Bean初始化之前都會執行這個例項的processBeforeInitialization()方法 InitializingBean的afterPropertiesSet(),如果實現了該介面,則執行其afterPropertiesSet()方法 Bean定義檔案中定義init-method BeanPostProcessors的processAfterInitialization(),如果有關聯的processor,則在Bean初始化之前都會執行這個例項的processAfterInitialization()方法 DisposableBean的destroy(),在容器關閉時,如果Bean類實現了該介面,則執行它的destroy()方法 Bean定義檔案中定義destroy-method,在容器關閉時,可以在Bean定義檔案中使用“destory-method”定義的方法

備註:BeanPostProcessor介面的作用:如果我們需要在Spring容器完成Bean的例項化、配置和其他的初始化前後新增一些自己的邏輯處理,我們就可以定義一個或者多個BeanPostProcessor介面的實現,然後註冊到容器中去。

public class TestBeanPostProcessor implements BeanPostProcessor {

/**
 * 例項化之後進行處理
 */
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}

/**
 * 例項化之前進行處理
 */
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}
複製程式碼

} 前者在初始化程式碼呼叫之後呼叫。 後者在例項化及依賴注入完成後,在任何初始化程式碼(比如配置檔案中的init-method)呼叫之前呼叫。(參考:www.cnblogs.com/libra0920/p…

16,類裝載器ClassLoader 工作機制: 類裝載器就是尋找類的位元組碼檔案並構造出類在JVM內部表示的物件元件。在Java中,類裝載器裝入JVM中,要經過以下步驟: 1.裝載:查詢和匯入Class檔案 2.連結:執行校驗、準備和解析步驟,其中解析步驟是可以選擇的: 校驗:檢查載入Class檔案資料的正確性(校驗是否是合法的位元組碼檔案) 準備:給類的靜態變數分配儲存空間 解析:將符號引用轉成直接引用 3.初始化:對類的靜態變數、靜態程式碼塊執行初始化工作 4,在Java堆中生成一個代表這個類的java.lang.Class物件 類載入器工作由ClassLoader及其子類負責,ClassLoader是一個重要的Java執行時系統元件,它負責在執行時查詢和裝入Class位元組碼檔案。JVM在執行時會產生三個ClassLoadre:根裝載器、ExtClassLoader和AppClassLoader。根裝載器不是ClassLoader的子類,負責裝載JRE的核心類庫。ExtClassLoader和AppClassLoader都是ClassLoader的子類。其中,EctClassLoader負責裝載JRE擴充套件目錄ext中的JAR類包,AppClassLoader負責裝載Classpath路徑下的類包。

JVM裝載類時使用“雙親委託機制”,“雙親委託”是指當一個ClassLoader裝載一個類時,除非顯式地使用另一個ClassLoader,該類所依賴及引用的類也由這個ClassLoader載入:“委託機制”是指先委託父裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查詢並裝載目標類。 ClassLoader的重要方法: Class loadClass(String name):name引數指定類裝載器需要裝載類的名字,必須使用全限定類名。該方法有一個過載方法loadClass(String name,boolean resolve),resolve引數告訴類裝載器是否需要解析該類。在初始化類之前,應考慮進行類解析的工作,但並不是所有的類都需要解析,若JVM值需知道該類是否存在或找出該類的超類,那麼就不需要進行解析。 Class defineClass(String name,byte[] b,int off,int len):將類檔案的位元組陣列轉換成JVM內部的java.lang.Class物件。位元組陣列可以從本地檔案系統、遠端網路獲取。name為位元組陣列對應的全限定類名。 Class findSystemClass(String name):從本地檔案系統載入Class檔案,若本地檔案系統更不存在該Class檔案,將丟擲ClassNotFoundException異常。 Class findLoadedClass():呼叫該方法來檢視ClassLoader是否已裝入某個類。若已裝入,則返回java.lang.Class物件,否則返回null。 ClassLoader getParent():獲取類裝載器的父裝載器。

參考:www.cnblogs.com/fengbs/p/70…

17,常見的限流演算法有:令牌桶、漏桶、計數器。

1.令牌桶限流 令牌桶是一個存放固定容量令牌的桶,按照固定速率往桶裡新增令牌,填滿了就丟棄令牌,請求是否被處理要看桶中令牌是否足夠,當令牌數減為零時則拒絕新的請求。令牌桶允許一定程度突發流量,只要有令牌就可以處理,支援一次拿多個令牌。令牌桶中裝的是令牌。

2.漏桶限流(基於佇列容器指定空間) 漏桶一個固定容量的漏桶,按照固定常量速率流出請求,流入請求速率任意,當流入的請求數累積到漏桶容量時,則新流入的請求被拒絕。漏桶可以看做是一個具有固定容量、固定流出速率的佇列,漏桶限制的是請求的流出速率。漏桶中裝的是請求。

3.計數器限流(hystrix是改良版的時間視窗計數,把一個時間段分為多個時間段計數,更加平滑均勻) 有時我們還會使用計數器來進行限流,主要用來限制一定時間內的總併發數,比如資料庫連線池、執行緒池、秒殺的併發數;計數器限流只要一定時間內的總請求數超過設定的閥值則進行限流,是一種簡單粗暴的總數量限流,而不是平均速率限流。

漏桶和令牌桶的比較 令牌桶可以在執行時控制和調整資料處理的速率,處理某時的突發流量。放令牌的頻率增加可以提升整體資料處理的速度,而通過每次獲取令牌的個數增加或者放慢令牌的發放速度和降低整體資料處理速度。而漏桶不行,因為它的流出速率是固定的,程式處理速度也是固定的。

整體而言,令牌桶演算法更優(可動態調整),令牌桶演算法能平滑流量,但是實現更為複雜一些。

1、計數器演算法

採用計數器實現限流有點簡單粗暴,一般我們會限制一秒鐘的能夠通過的請求數,比如限流qps為100,演算法的實現思路就是從第一個請求進來開始計時,在接下去的1s內,每來一個請求,就把計數加1,如果累加的數字達到了100,那麼後續的請求就會被全部拒絕。等到1s結束後,把計數恢復成0,重新開始計數。具體的實現可以是這樣的:對於每次服務呼叫,可以通過 AtomicLong#incrementAndGet()方法來給計數器加1並返回最新值,通過這個最新值和閾值進行比較。這種實現方式,相信大家都知道有一個弊端:如果我在單位時間1s內的前10ms,已經通過了100個請求,那後面的990ms,只能眼巴巴的把請求拒絕,我們把這種現象稱為“突刺現象”。

2、漏桶演算法(基於佇列容器指定空間) 為了消除"突刺現象",可以採用漏桶演算法實現限流,漏桶演算法這個名字就很形象,演算法內部有一個容器,類似生活用到的漏斗,當請求進來時,相當於水倒入漏斗,然後從下端小口慢慢勻速的流出。不管上面流量多大,下面流出的速度始終保持不變。不管服務呼叫方多麼不穩定,通過漏桶演算法進行限流,每10毫秒處理一次請求。因為處理的速度是固定的,請求進來的速度是未知的,可能突然進來很多請求,沒來得及處理的請求就先放在桶裡,既然是個桶,肯定是有容量上限,如果桶滿了,那麼新進來的請求就丟棄。在演算法實現方面,可以準備一個佇列,用來儲存請求,另外通過一個執行緒池定期從佇列中獲取請求並執行,可以一次性獲取多個併發執行。這種演算法,在使用過後也存在弊端:無法應對短時間的突發流量。

3、令牌桶演算法 從某種意義上講,令牌桶演算法是對漏桶演算法的一種改進,桶演算法能夠限制請求呼叫的速率,而令牌桶演算法能夠在限制呼叫的平均速率的同時還允許一定程度的突發呼叫。在令牌桶演算法中,存在一個桶,用來存放固定數量的令牌。演算法中存在一種機制,以一定的速率往桶中放令牌。每次請求呼叫需要先獲取令牌,只有拿到令牌,才有機會繼續執行,否則選擇選擇等待可用的令牌、或者直接拒絕。放令牌這個動作是持續不斷的進行,如果桶中令牌數達到上限,就丟棄令牌,所以就存在這種情況,桶中一直有大量的可用令牌,這時進來的請求就可以直接拿到令牌執行,比如設定qps為100,那麼限流器初始化完成一秒後,桶中就已經有100個令牌了,這時服務還沒完全啟動好,等啟動完成對外提供服務時,該限流器可以抵擋瞬時的100個請求。所以,只有桶中沒有令牌時,請求才會進行等待,最後相當於以一定的速率執行。(Guava RateLimiter提供了令牌桶演算法實現)

4、叢集限流 前面討論的幾種演算法都屬於單機限流的範疇,但是業務需求五花八門,簡單的單機限流,根本無法滿足他們。 比如為了限制某個資源被每個使用者或者商戶的訪問次數,5s只能訪問2次,或者一天只能呼叫1000次,這種需求,單機限流是無法實現的,這時就需要通過叢集限流進行實現。如何實現?為了控制訪問次數,肯定需要一個計數器,而且這個計數器只能儲存在第三方服務,比如redis。大概思路:每次有相關操作的時候,就向redis伺服器傳送一個incr命令,比如需要限制某個使用者訪問/index介面的次數,只需要拼接使用者id和介面名生成redis的key,每次該使用者訪問此介面時,只需要對這個key執行incr命令,在這個key帶上過期時間,就可以實現指定時間的訪問頻率。

參考:blog.csdn.net/qq_35642036… blog.csdn.net/p312011150/…

相關文章