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

③,三面 1,介紹你實踐的性能優化例子,以及你優化的思路 結合本身在作的具體業務,同步變異步(無強依賴的服務調用改爲併發調用),異地緩存多活,異地雙主mysql,hystrix熔斷,限流機制,99線統計,熱點數據緩存化等等。html

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

SOA:微服務是細粒度的SOA(面向服務架構),一個SOA組件能夠拆分紅多個微服務,SOA的服務架構依賴ESB企業服務總線(專一應用程序服務的可重用性的最大化)。 微服務:微服務架構是一個大的系統按照功能拆分紅不少獨立的子服務,每一個子服務獨立實現本身的業務邏輯,各個微服務之間的關聯經過暴露api來實現。這些獨立的微服務不須要部署在同一個虛擬機,同一個系統和同一個應用服務器中(微服務架構專一於解耦)。mysql

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

SOA架構的缺點 <1>嚴重依賴比較重的ESB企業服務總線,企業總線出了問題,就致使整個架構的不可能。 <2>服務粗化,好比讀寫負載不一樣的功能耦合在一個服務裏面。linux

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

微服務架構的缺點: <1>微服務應用做爲分佈式系統帶來了複雜性。各個微服務進行分佈式獨立部署,由不一樣的技術團隊維護,所以常常出現跨團隊溝通,效率容易不可控。 <2>微服務架構通常使用各個獨立數據庫,分佈式事務的實現更具挑戰性。 <3>測試微服務變得複雜,一般一個服務會依賴不少服務,測試環境下其餘服務的不可靠,容易影響測試進度。web

備註:企業服務總線(ESB)就是一條企業架構的總線,全部的企業服務都掛接到該總線上對外公佈,企業服務總線負責管理服務目錄,解析服務請求者的請求方法、消息格式,並對服務提供者進行尋址,轉發服務請求面試

3,sql慢查詢的優化方案,索引和表的優化方案 慢查詢優化方案: <0>explain檢查sql語句是否索引覆蓋 <1>能使用主鍵查詢的儘可能使用主鍵查詢 <2>鏈接查詢代(join on)替子查詢(in)和聯合查詢(from t1,t2),避免在內存從造成巨大的笛卡爾積,多表鏈接時,儘可能小表驅動大表,即小表join大表 <3>批量掃表時,每次查詢應該使用上次查詢返回的主鍵id做爲下一輪的查詢條件,提升查詢效率。 <4>組合索引使用應該知足最左原則。 <5>數據量比較的時候,儘可能使用limit進行物理分頁查詢 <6>查詢時,返回結果能不要就不用,儘可能寫全字段名redis

索引優化: <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是改良版的時間窗口計數,把一個時間段分爲多個時間段計數,更加平滑均勻) 有時咱們還會使用計數器來進行限流,主要用來限制必定時間內的總併發數,好比數據庫鏈接池、線程池、秒殺的併發數;計數器限流只要必定時間內的總請求數超過設定的閥值則進行限流,是一種簡單粗暴的總數量限流,而不是平均速率限流。

漏桶和令牌桶的比較 令牌桶能夠在運行時控制和調整數據處理的速率,處理某時的突發流量。放令牌的頻率增長能夠提高總體數據處理的速度,而經過每次獲取令牌的個數增長或者放慢令牌的發放速度和下降總體數據處理速度。而漏桶不行,由於它的流出速率是固定的,程序處理速度也是固定的。

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

一、計數器算法

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

二、漏桶算法(基於隊列容器指定空間) 爲了消除"突刺現象",能夠採用漏桶算法實現限流,漏桶算法這個名字就很形象,算法內部有一個容器,相似生活用到的漏斗,當請求進來時,至關於水倒入漏斗,而後從下端小口慢慢勻速的流出。無論上面流量多大,下面流出的速度始終保持不變。無論服務調用方多麼不穩定,經過漏桶算法進行限流,每10毫秒處理一次請求。由於處理的速度是固定的,請求進來的速度是未知的,可能忽然進來不少請求,沒來得及處理的請求就先放在桶裏,既然是個桶,確定是有容量上限,若是桶滿了,那麼新進來的請求就丟棄。在算法實現方面,能夠準備一個隊列,用來保存請求,另外經過一個線程池按期從隊列中獲取請求並執行,能夠一次性獲取多個併發執行。這種算法,在使用事後也存在弊端:沒法應對短期的突發流量。

三、令牌桶算法 從某種意義上講,令牌桶算法是對漏桶算法的一種改進,桶算法可以限制請求調用的速率,而令牌桶算法可以在限制調用的平均速率的同時還容許必定程度的突發調用。在令牌桶算法中,存在一個桶,用來存放固定數量的令牌。算法中存在一種機制,以必定的速率往桶中放令牌。每次請求調用須要先獲取令牌,只有拿到令牌,纔有機會繼續執行,不然選擇選擇等待可用的令牌、或者直接拒絕。放令牌這個動做是持續不斷的進行,若是桶中令牌數達到上限,就丟棄令牌,因此就存在這種狀況,桶中一直有大量的可用令牌,這時進來的請求就能夠直接拿到令牌執行,好比設置qps爲100,那麼限流器初始化完成一秒後,桶中就已經有100個令牌了,這時服務還沒徹底啓動好,等啓動完成對外提供服務時,該限流器能夠抵擋瞬時的100個請求。因此,只有桶中沒有令牌時,請求才會進行等待,最後至關於以必定的速率執行。(Guava RateLimiter提供了令牌桶算法實現)

四、集羣限流 前面討論的幾種算法都屬於單機限流的範疇,可是業務需求五花八門,簡單的單機限流,根本沒法知足他們。 好比爲了限制某個資源被每一個用戶或者商戶的訪問次數,5s只能訪問2次,或者一天只能調用1000次,這種需求,單機限流是沒法實現的,這時就須要經過集羣限流進行實現。如何實現?爲了控制訪問次數,確定須要一個計數器,並且這個計數器只能保存在第三方服務,好比redis。大概思路:每次有相關操做的時候,就向redis服務器發送一個incr命令,好比須要限制某個用戶訪問/index接口的次數,只須要拼接用戶id和接口名生成redis的key,每次該用戶訪問此接口時,只須要對這個key執行incr命令,在這個key帶上過時時間,就能夠實現指定時間的訪問頻率。

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

相關文章
相關標籤/搜索