天天看點

阿裡java進階工程師面試100題

大型網站架構技術QQ群:368614849

1,java堆,分新生代老年代,新生代有Eden,from surviver,to surviver三個空間,堆被所有線程共。eden記憶體不足時,發生一次minor GC,會把from survivor和eden的對象複制到to survivor,這次的to survivor就變成了下次的from survivor,經過多次minor GC,預設15次,達到次數的對象會從survivor進行老年代。1次new如果新生代裝不下,則直接進入老年代。

2,HashMap和HashTable是使用數組+連結清單結構實作,根據Hash和table長度計算數組的下标index做操作,hashMap預設數組長度為16,hashMap對null值的key都放在table[0]的位置,table[index]形成1個連結清單,當然在新版jdk中連結清單節點數>8會變成紅黑樹結構。hashMap達到最大數量會擴容,擴容table長度變為2倍,每個元素(table中)但重新計算index放到新的table中。

3,堆的年輕代和老年代。

堆的年輕代大則老年代小,GC少,但是每次時間會比較長。年輕代小則老年代大,會縮短每次GC的時間,但是次數頻繁。可以讓老年代盡量緩存常用對象,JVM預設年輕代和老年代的大小比例為1:2,。觀察峰值老年代記憶體,不影響full GC,加大老年代可調1:1,但是要給老年代預留三分之一的空間。減少使用全局變量和大對象 ,調整新生代,老年代到最合适。

4,位元組流不會用到記憶體緩沖區,檔案本身直接操作。字元流操作使用記憶體緩存區,用緩存存操作檔案。字元流在輸出前将所有内容暫時儲存到記憶體中,即緩存區暫時存儲,如果想不關閉也将字元流輸出則可以使用flush方法強制刷出。位元組字元轉化可能存在系統編碼lang,要制定編碼。getbyte位元組流使用更加廣泛。

5,中文占用2個位元組,read()函數讀1個位元組把A會讀入的原因。ASCII碼是8位,A在ASCII碼中有對應碼,A隻要8位就能表示,但是unicode是支援ASCII碼的,在unicode中表示A是使用低8位的ASCII碼,補上高8位的0,read()1分位元組就已經讀入A的ASCII碼,列印時會給其高8位補上0,是以顯示正常A。

6,喚醒一個阻塞的線程

如因為Sleep,wait,join等阻塞,可以使用interrupted exception異常喚醒。

7,記憶體溢出可能原因和解決。

原因可能是A,資料加載過多,如1次從資料庫中取出過多資料   B,集合類中有對對象的引用,用完後沒有清空或者集合對象未置空導緻引用存在等,是的JVM無法回收  C,死循環,過多重複對象 D,第三方軟體的bug       E,啟動參數記憶體值設定的過小。

例如方法:修改JVM啟動參數,加記憶體(-Xms,-Xmx);錯誤日志,是否還有其他錯誤;代碼走查

8,redis使用單線程模型,資料順序送出,redis支援主從模式,mencache隻支援一緻性hash做分布式;redis支援資料落地,rdb定時快照和aof實時記錄操作指令的日志備份,memcache不支援;redis資料類型豐富,有string,hash,set,list, sort set,而memcache隻支援簡單資料類型;memcache使用cas樂觀鎖做一緻性。

jedis操作Hash:hmset, hmget, hdel, hkeys

jedis操作List: lpush,lrange按照範圍取出,rpush, del, sort等keyjedis操作Set:sadd,srem移除noname,smembers, sismember, scard等。

使用場景例如

Hash:存儲讀取更新使用者多個屬性

List:微網誌TimeLine,消息清單

Set:共同好友,二度好友,用唯一性可以統計網站所有獨立IP,好友推薦根據tag求交集,大于threshold就可以推薦。

sortset:set增加1個權重score參數

其他場景:A訂閱釋出系統,redis對某個key消息釋出及訂閱,當1個key消息釋出後,所有訂閱它的用戶端都會收到相應消息,例如實時消息系統,即時聊天,群聊等。

事務-常用EX,EC送出執行的指令,在server不出問題,可以保證一連串的指令是順序執行額;提供1個watch功能,對1個key作watch,然後再執行transation。

9,Class.forName()将類加載到JVM,還會對類解釋,執行static塊,而ClassLoader也加載到JVM,但是不會執行static塊,并且隻有調用了new Instance方法才會調用構造函數。

10,java反射機制。

可以在運作時判斷一個對象所屬的類,構造一個類的對象,判斷類具有的成員變量和方法,調用1個對象的方法。4個關鍵的類:Class,Constructor,Field,Method。    getConstructor獲得構造函數/getDeclardConstructor; getField/getFields/getDeclardFields獲得類所生命的所有字段;getMethod/getMethods/getDeclardMethod獲得類聲明的所有方法,正常方法是一個類建立對象,而反射是1個對象找到1個類。

11,Object類中的方法:clone(),但是使用該方法必須實作Java.lang.Cloneable接口,equal()方法判斷引用是否一緻,指向同一對象,即相等于==,隻有覆寫了equals()方法之後,才可以說不同。hashcode(),對象的位址, toString(), finalize()。

12,序列化和反序列化

序列化和反序列化即對象和位元組序列間的轉化,程序間傳送文本圖檔音頻等以二進制傳送。JDK中ObjectOuputStream和ObjectInputStream為輸出輸入流,隻有實作SeriaLizable/Externalizable接口的類才能被序列化。如Person對象傳遞給記憶體流使用DataConstractJsonSeralizer, MemoryStream stream = new MemoryStream(); DataConstractJsonSeralizer SER = new DataConstractJsonSeralizer(typeof(person));  ser.writeObjectStream(stream, person);顯示json輸出,StramReader sr = new StreamReader(stream1); sr.ReadToEnd()。

13,講講分布式唯一ID。

确定ID存儲用64位,1個64位二進制1是這樣的00000000.....1100......0101,切割64位,某段二進制表示成1個限制條件,前41位為毫秒時間,後緊接9位為IP,IP之後為自增的二進制,記錄目前面位數相同情況下是第幾個id,如現在有10台機器,這個id生成器生成id極限是同台機器1ms内生成2的14次方個ID。

分布式唯一ID = 時間戳 << 41位, int類型伺服器編号 << 10,序列自增sequence。每個時間戳内隻能生成固定數量如(10萬)個自增号,達到最大值則同步等待下個時間戳,自增從0開始。将毫秒數放在最高位,保證生成的ID是趨勢遞增的,每個業務線、每個機房、每個機器生成的ID都是不同的。如39bit毫秒數|4bit業務線|2bit機房|預留|7bit序列号。高位取2016年1月1日1到現在的毫秒數,系統運作10年,至少需要10年x365天x24小時x3600秒x1000毫秒=320x10~9,差不多39bit給毫秒數,每秒單機高峰并發小于100,差不多7bit給每毫秒的自增号,5年内機房小于100台機器,預留2bit給機房,每個機房小于100台機器,預留7bit給每個機房,業務線小于10個,預留4bit給業務線辨別。

64bit分布式ID(42bit毫秒+5bit機器ID+12位自增)等

生成分布式ID的方式:A,2個自增表,步長互相隔開   B,時間的毫秒或者納秒  C,UUID         D,64位限制條件(如上)

14,NIO和IO的差別

第一點,NIO少了1次從核心空間到使用者空間的拷貝。

ByteBuffer.allocateDirect()配置設定的記憶體使用的是本機記憶體而不是Java堆上的記憶體,和網絡或者磁盤互動都在作業系統的核心空間中發生。

allocateDirect()

的差別在于這塊記憶體不由java堆管理, 但仍然在同一使用者程序内。

第二點,NIO以塊處理資料,IO以流處理資料

第三點,非阻塞,NIO1個線程可以管理多個輸入輸出通道

15,記憶體洩漏

未對廢棄資料記憶體單元置為null,盡早釋放無用對象的引用,使用臨時變量時,讓引用變量在推出活動域後自動設定為null,暗示垃圾收集器收集;程式避免用String拼接,用StringBuffer,因為每個String會占用記憶體一塊區域;盡量少用靜态變量(全局不會回收);不要集中建立對象尤其大對象,可以使用流操作;盡量使用對象池,不再循環中建立對象,優化配置;建立對象到單例getInstance中,對象無法回收被單例引用;伺服器session時間設定過長也會引起記憶體洩漏。

16,對象克隆和實作方式

克隆的對象可能包含一些已經修改過的屬性,而new1個對象屬性都還是初始化時候的值,被複制克隆的類要實作Clonable接口,覆寫clone()方法,通路修飾符為public,方法中調用super.clone()得到所需要的複制方法,類中的屬性類也需要實作Clonable接口,覆寫clone()方法,并在super中也調用子屬性類的clone()複制,才可以實作深拷貝。

或者寫到流中序列化的方式來實作,不必考慮引用類型中還包含引用類型,直接用序列化來實作對象的深複制拷貝,即将對象寫到流,再從流中讀出來,需要實作seriazation接口。

17,redis記憶體資料上升到一定大小會執行資料淘汰政策,redis提供了6種資料淘汰政策。

LRU:從已設定過期時間的資料集合中挑選最近最少使用的資料淘汰

random:從已設定過期時間的資料中挑選任意資料淘汰

ttl:從已設定過期時間的資料集合中挑選将要過期的資料淘汰。

notenvision:禁止驅逐資料

如mysql中有2千萬資料,redis隻存儲20萬的熱門資料。LRU或者TTL都滿足熱點資料讀取較多,不太可能逾時特點。

redis特點:速度塊,O(1),豐富的資料類型,支援事物原子性,可用于緩存,比memecache速度塊,可以持久化資料。

常見問題和解決:Master最好不做持久化如RDB快照和AOF日志檔案;如果資料比較重要,某分slave開啟AOF備份資料,政策為每秒1次,為了主從複制速度及穩定,MS主從在同一區域網路内;主從複制不要用圖狀結構,用單向連結清單更為穩定 M-S-S-S-S。。。。;redis過期采用懶漢+定期,懶漢即get/set時候檢查key是否過期,過期則删除key,定期周遊每個DB,檢查制定個數個key;結合伺服器性能調節并發情況。

過期淘汰,資料寫入redis會附帶1個有效時間,這個有效時間内該資料被認為是正确的并不關心真實情況,例如對支付等業務采用版本号實作,redis中每一份資料都維持1個版本号,DB中也維持1份,隻有當redis的與DB中的版本一緻時,才會認為redis為有效的,不過仍然每次都要通路DB,隻需要查詢version版本字段即可。

18,異步化,生産接口每秒鐘10萬并發,消費者用異步慢慢消費。緩存模式空間換時間,把1兩億的資料名單打到緩存。服務降級,把不重要的任務放棄;靜态資源離線包下載下傳機制,在wify下會主動提前把靜态下載下傳前端層保護可請将使用者請求延長,點選後主動給它随機等待2s的時間/2分鐘之内不能請求;後端做部分接口的開關,設定超短耗時時間,原來隻用5ms的隻給20ms。

系統一段時間内會自動重試,重試多次後就認為是失敗了,檢查支付接口傳回該訂單的錢,支付操作如果回複錯誤則復原扣庫存的事務,沒傳回則會記錄進行中pendding狀态,結束整個過程,等通知失敗/成功,AB系統之間會出現死循環補償,如B退單不成功,一般就是記錄錯誤日志了。逾時每隔一段時間去定時回調服務定時復原,一定次數還是逾時則提示使用者聯系客服,訂單庫存可以不會滾,記錄狀态,如果一直調用支付不成功,則讓使用者自己去處理聯系客服,可以不復原使用者的資料,金額扣了才算真正完成,是一種簡單粗暴的做法。

公共配置抽象成存儲到zookeeper配置中心或者redis等,DB也存儲一份,各應用監聽ZK的配置變化,可以建一個配置web管理頁面。

19,dubbo用ProxyFactoty代理工廠将HelloServiceImpl封裝成1個Inoke執行,即ProxyFactory.getInvoke(ref, (Class)接口,注冊URL,解碼參數),并将Invoke導出成1個Exporter,包括去注冊中心ZK注冊服務。Invoke有本地執行的Invoke,遠端通信執行的Invoke。

20,每次扣減庫存時加上1個請求流水編号,上層請求扣減庫存沒拿到結果的話,重新查詢1次做重試操作,量不大都是加鎖處理。減少鎖的時間,犧牲幂等性,扣減為DB下地操作,查詢扣減和設定合成1步,中間沒有網絡請求。利用緩存,通過寫log記錄操作,異步合并日志及更新,重新開機時cache失效,讀log恢複,避免重複送出,寫操作不建議重試快速失敗。多個商品同時增減庫存,可使用訂單号做幂等處理,應用層對單個商品減庫存,操作排隊,商品消息ID路由在1個應用server處理,讀本地緩存,失效再redis,DB采用樂觀鎖,組送出,1次減庫存多個訂單的購買量。可将同一個key下庫存m分為n組k1......kn,每組數為m/n,扣減依次在各組扣減,減少并發沖突。隊列裝滿後關閉隊列進入,然後使用者輪訓自己是否搶到了異步ajax,使用者資源隊列固定長度。2個隊列,1個銷售的資源隊列放入redis,有另外1個隊列用來裝搶購的會員的uid。

紅包狀态正常,并成功将狀态改為“已領取”,且消息發送成功,使用者端開始消費該消息,如果消費失敗/逾時,用MQ做重試做幂等,直到成功,每條消息有唯一編号且保證消息處理成功與去重表的日志同時出現。

熱點将hot data拆分,分在不同庫和不同表,分散熱點Data,減輕DB并發更新熱點帶來RT升高和應用連接配接逾時。SQL在mysql層加以限制,SQL逾時/thradrunning到1定值則拒絕SQL執行,一定時間異步将結果寫入DB,nginx對IP做限制,可能誤殺。

21,SpringAOP,XML配置<aop:config>,切面<aop:aspect>切點<aop:pointcut>,連接配接切點和通知方法<aop:before>和<aop:after>等,注解可以直接使用@before執行方法@after ,@before(“pointcut()”) ,@after("pointcut"), @Aroud("excutete()),@AfteReturning,@AfterThrowing,可作日志事務,權限等待,AOP即通過把具體的類建立對應的 代理類,從代理類來對具體進行操作。                      

目标實作了接口,預設采用JDK實作AOP,也可以強制使用CGlib來實作AOP,目标沒有實作接口的話,則必須采用CGlib,Spring自動在JDK和CGlib切換。如果要求spring強制使用CGlib實作AOP,則可以配置,添加Cglib庫。。。jar, Spring配置檔案中加入<aop:aspecj-autoproxy proxy-target-Class=true>                                                                                                                                                                                   

22,MyISM采用表級鎖,對Myism表讀不會阻塞讀,會阻塞同表寫,對Myism寫則會阻塞讀和寫,即一個線程獲得1個表的寫鎖後,隻有持有鎖的線程可以對表更新操作,其他線程的讀和寫都會等待。

InnoDB,采用行級鎖,支援事務,例如隻對a列加索引,如果update ...where a=1 and b=2其實也會鎖整個表, select 使用共享鎖,update insert delete采用排它鎖,commit會把鎖取消,當然select by id for update也可以制定排它鎖。

23,實時隊列采用雙隊列模式,生産者将行為記錄寫入Queue1,worker服務從Queue1消費新鮮資料,如果異常則寫入Queue2(主要儲存異常資料),RetryWorker會監聽Queue2,消費異常資料,如果還未處理成功按照一定的政策等待或者将異常資料再寫入Queue2,如果資料發生積壓可以調整worker的消費遊标,從最新資料重新開始消費,保證了最新data得到處理,中間未處理的一段則可以啟動backupWorker指定起止遊标在消費完指定區間的資料後,backupWorker會自動停止。

DB降級開關後,可直接寫入redis(storm),同時将資料寫入一份到Retry隊列,在開啟DB降級開關後消費Retry隊列中的資料,進而把資料寫入到mysql中,達到最終一緻性。MYSQL切分為分片為2的N次方,例如原來分為兩個庫d0和d1均放在s0伺服器上,s0同時有備機s1,擴容隻要幾步驟:確定s0到s1伺服器同步順利,沒有明顯延遲;s0暫時關閉讀寫權限;確定s1已經完全同步到s0更新;s1開放讀寫權限;d1的dns由s0切換到s1;s0開放讀寫權限。

24,DB的特性和隔離級别

4大特性:原子性,一緻性,分離性,持久性

隔離級别:

讀送出:寫事務禁止讀

讀未送出:寫事務允許讀

可重複讀:寫事務禁止讀事務,讀禁止寫

序列化:全部禁止

詳細說明:讀送出1個事務開始寫則全部禁止其他事務通路該行。讀未送出1個事務開始寫則不允許其他事務同時寫,但可以讀。可重複讀 讀事務會禁止寫事務,寫事物則禁止其他任何事務。序列化性能最低,全部禁止,串行執行。 MYSQL預設的是可重複讀。

25,文章服務、中繼資料服務、文章搜尋服務,提供索引資料存儲,tid和uid查詢直接從文章服務從中繼資料傳回,其他檢索查詢有文章搜尋服務從索引資料檢索并傳回,文章服務增删改查用MQ同步到文章搜尋服務,搜尋服務修改索引的資料(索引樹,倒排表),索引表t_mapping(tid,uid)。

300億資料在全量索引庫中,數百萬一天内修改過的資料在一天庫中,50萬小時内修改過的資料在小時庫中,在update請求時,隻會操作最低級别的索引例如小時庫。小時庫,1小時合并一次,合并到天庫,天庫一天合并1次,合并到全量庫中。

26,講一下NIO和網絡傳輸

NIO Reactor反應器模式,例如汽車是乘客通路的實體reactor,乘客上車後到售票員處Acceptor登記,之後乘客便可休息睡覺了,到達乘客目的地後,售票員Aceptor将其喚醒即可。持久TCP長連結每個client和server之間有存在一個持久連接配接,當CCU(使用者并發數量)上升,阻塞server無法為每個連接配接運作1個線程,自己開發1個二進制協定,将message壓縮至3-6倍,傳輸雙向且消息頻率高,假設server連結了2000個client,每個client平均每分鐘傳輸1-10個message,1個messaged的大小為幾百位元組/幾千位元組,而server也要向client廣播其他玩家的目前資訊,需要高速處理消息的能力。Buffer,網絡位元組存放傳輸的地方,從channel中讀寫,從buffer作為中間存儲格式,channel是網絡連接配接與buffer間資料通道,像之前的socket的stream。

27,緩存擊透

預加載;

加載DB時同步,其他則等待;

DB端做SQL合并,Queue合并排隊處理;

部分緩存設定為永不過期;

先清除緩存,讀取資料時候則等待500ms,500ms緩存應該已經加載完成;

采用雙key緩存,A1為原始緩存,A2為拷貝緩存;

如果DB為空null則g給redis設定1個NFC空nei容。

28,Dubbo源碼使用了哪些設計模式

A,工廠模式,ExtenstionLoader.getExtenstionLoader(Protocol.class).getAdaptiveExtenstion()

B,裝飾器模式+責任鍊,以provider的調用鍊為例,具體調用鍊代碼是在protocolFilterWrapper的buildInvokeChain完成的,将注解中含有group=provider的Filter實作,調用順序為EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter -> ExceptionFilter -> TimeoutFilter -> MonitorFilter -> TraceFilter。裝飾器模式和責任鍊混合使用,Echo是回聲測試請求,ClassLoaderFilter則隻是在其主功能上添加了功能。

C,觀察者模式,provider啟動時需要與注冊中心互動,先注冊自己的服務,再訂閱自己的服務,訂閱時采用了觀察者模式,注冊中心每5s定時檢查是否有服務更新,有更新則向服務提供者發送1個notify消息後即可運作NotifyListener的notity方法,執行監聽器方法。

D,動态代理模式。  擴充JDK的ExtensionLoaderdeAdaptive實作,根據調用階段動态參數決定調用哪個類,生成代理類的代碼是ExtensionLoader的createAdaptiveExtenstionClassLoader方法。

29,平衡二叉樹,左右高度之差不超過1,Add/delete可能造成高度>1,此時要旋轉,維持平衡狀态,避免二叉樹退化為連結清單,讓Add/Delete時間複雜度但控制在O(log2N),旋轉算法2個方法,1是求樹的高度,2是求2個高度最大值,1個空樹高度為-1,隻有1個根節點的樹的高度為0,以後每一層+1,平衡樹任意節點最多有2個兒子,是以高度不平衡時,此節點的2棵子樹高度差為2。例如單旋轉,雙旋轉,插入等。

紅黑樹放棄完全平衡,追求大緻平衡,保證每次插入最多要3次旋轉就能平衡。

30,多線程同步鎖

A,RentrantLock,可重入的互斥鎖,可中斷可限時,公平鎖,必須在finally釋放鎖,而synchronize由JVM釋放。可重入但是要重複退出,普通的lock()不能響應中斷,lock.lockInterruptbly()可響應中斷,可以限時tryLock(),逾時傳回false,不會永久等待構成死鎖。

B,Confition條件變量,signal喚醒其中1個在等待的線程,signalall喚醒所有在等待的線程await()等待并釋放鎖,與lock結合使用。

C,semaphore信号量,多個線程比(額度=10)進入臨界區,其他則阻塞在臨界區外。

D,ReadWriteLock,讀讀不互斥,讀寫互斥,寫寫互斥。

E,CountDownLantch倒數計時器,countdown()和await()

F,CyCliBarrier

G,LockSupport,方法park和unpark

31,棧溢出的原因

是否遞歸的調用;大量循環;全局變量是否過多;數組,List,Map資料是否過大;用DDMS工具檢查地方。

記憶體溢出的原因

過多使用了static;static最好隻用int和string等基本類型;大量的遞歸或者死循環;大資料項的查詢,如傳回表的所有記錄,應該采用分頁查詢。檢查是否有數組、List、map中存放的是對象的引用而不是對象,這些引用會讓對應對象不能被釋放。

棧過大會導緻記憶體占用過多,頻繁頁交換阻礙效率。

32,說一下http/2

Http/2采用二進制格式而不是文本

Http/2是完全多路複用的,而非有序并阻塞的。

Http/2使用報頭壓縮

Http/2讓伺服器可以将響應主動推送到用戶端緩存中。

33,說一下記憶體洩露

A,HashMap,vector等容易(靜态集合類), 和應用程式生命周期一樣,所引用的所有對象Object也不能釋放。

B,當集合類裡面的對象屬性被修改後,再調用remove()不起作用,hashcode值發生了改變

C,其對象add監聽器,但是往往釋放對象時忘記去删除這些監聽器

D,各種連接配接記得關閉

E,内部類的引用

F,調用其他子產品,對象作用參數

G,單例模式,持有外部對象引用無法收回。

記憶體洩露例子

Vector<String> A = new Vector<String>();

for(int i = 0; i < 100; i++){

Object o = new Object ();

A.add(o);

o = null;

}

 ........

記憶體溢出的例子

StringBuffer b = new StringBuffer ();

for(int i =0; i < 100; i++){

for(int j =0; i < 100; j++){

b.append(*);

}

}

34,SpirngMVC的生命周期 和 SpringBean的生命周期

SpirngMVC的生命周期 :

A,DispatcherSerlvet(前端控制器)

B,-》 HandlerMapping(處理器映射器),根據xml注解查找對應的Hander -》 傳回Handler

C,-》處理器擴充卡去執行Handler

D,-》Handler執行完成後給處理器擴充卡傳回ModelAndView

E,-》前端控制器請求視圖解析器去執行視圖解析,根據邏輯視圖名解析成真正的視圖JSP,向前端控制器傳回view

F,-》前端控制器進行視圖渲染,将模型資料放到request-》傳回給使用者

SpringBean的生命周期:

Instance執行個體化-》設定屬性值-》調用BeanNameAware的setBeanName方法-》調用BeanPostProsessor的預初始化方法-》調用InitializationBean的afterPropertiesSet()的方法-》調用定制的初始化方法callCustom的init-method-》調用BeanPostProsessor的後初始化方法-》Bean可以使用了 -》 容器關閉-》 調用DisposableBean的destroy方法-》調用定制的銷毀方法CallCustom的destroy-method。

35,AQS,抽象隊列同步器

AQS定義2種資源共享方式:獨占與share共享

獨占:隻能有1個線程運作

share共享:多個線程可以同p執行如samphore/countdownlanch

AQS負責擷取共享state的入隊和/喚醒出隊等,AQS在頂層已經實作好了,AQS有幾種方法:acquire()是獨占模式下線程共享資源的頂層入口,如擷取到資源,線程直接傳回,否則進入等待隊列,直到擷取到資源為止。tryAcquire()将線程加入等待隊列的尾部,并标志為獨占。acquireQueued()使線程在等待隊列中擷取資源,一直到擷取資源後不傳回,如果過程被中斷也傳回true,否則false。

線程在等待過程中被中斷是不響應的,擷取資源才補上中斷。将線程添加到隊列尾部用了CAS自旋(死循環直到成功),類似于AutomicInteger的CAS自旋volatile變量。

start->tryAcquire -> 入隊 -> 找安全點 -> park等待狀态 -> 目前節點成對頭 -> End

36,單例模式的7種寫法

懶漢2種,枚舉,餓漢2種,靜态内部類,雙重校驗鎖(推薦)。

37,lucence反向索引

三個檔案:字典檔案,頻率檔案,位置檔案。詞典檔案不僅儲存有每個關鍵詞,還保留了指向頻率檔案和位置檔案的指針,通過指針可以找到該關鍵字的頻率資訊和位置資訊。

field的概念,用于表達資訊所在位置(如标題中,文章中,url中),在建索引中,該field資訊也記錄在詞典檔案中,每個關鍵詞都有一個field資訊(因為每個關鍵字一定屬于一個或多個field)。

關鍵字是按字元順序排列的(lucene沒有使用B樹結構),是以lucene可以用二進制搜尋算法快速定位關鍵詞。

假設要查詢單詞 “live”,lucene先對詞典二進制查找、找到該詞,通過指向頻率檔案的指針讀出所有文章号,然後傳回結果。詞典通常非常小,因而,整個過程的時間是毫秒級的。   

對詞典檔案中的關鍵詞進行了壓縮,關鍵詞壓縮為<字首長度,字尾>,例如:目前詞為“阿拉伯語”,上一個詞為“阿拉伯”,那麼“阿拉伯語”壓縮為<3,語>。對數字的壓縮,數字隻儲存與上一個值的內插補點。

38,ZooKeeper分布式高可用

ZooKeeper 運作期間,叢集中至少有過半的機器儲存了最新資料。叢集超過半數的機器能夠正常工作,叢集就能夠對外提供服務。

zookeeper可以選出N台機器作主機,它可以實作M:N的備份;keepalive隻能選出1台機器作主機,是以keepalive隻能實作M:1的備份。

通常有以下兩種部署方案:雙機房部署(一個穩定性更好、裝置更可靠的機房,這個機房就是主要機房,而另外一個機房則更加廉價一些,例如,對于一個由 7 台機器組成的 ZooKeeper 叢集,通常在主要機房中部署 4 台機器,剩下的 3 台機器部署到另外一個機房中);三機房部署(無論哪個機房發生了故障,剩下兩個機房的機器數量都超過半數。在三個機房中都部署若幹個機器來組成一個 ZooKeeper 叢集。假設機器總數為 N,各機房機器數:N1 = (N-1)/2 ,N2=1~(N-N1)/2 ,N3 = N - N1 - N2 )。

水準擴容就是向叢集中添加更多機器,Zookeeper2種方式(不完美),一種是叢集整體重新開機,另外一種是逐台進行伺服器的重新開機。

39,如何将資料分布在redis第幾個庫?

答:redis 本身支援16個資料庫,通過 資料庫id 設定,預設為0。

例如jedis用戶端設定。一:JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database);

第一種通過指定構造函數database字段選擇庫,不設定則預設0庫。二:jedis.select(index);調用jedis的select方法指定。

40,類加載器的雙親委派加載機制?

答:當一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父類去完成,每一個層次類加載器都是如此,是以所有的加載請求都應該傳送到啟動類加載其中,隻有當父類加載器回報自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載。

41,kafka高性能的原因?

答:

A,Broker NIO異步消息處理,實作了IO線程與業務線程分離;

B,磁盤順序寫;

C, 零拷貝(跳過使用者緩沖區的拷貝,建立一個磁盤空間和記憶體的直接映射,資料不再複制到使用者态緩沖區);

D,分區/分段(每次檔案操作都是對一個小檔案的操作,非常輕便,同時也增加了并行處理能力);

F,批量發送 (可以指定緩存的消息達到某個量的時候就發出去,或者緩存了固定的時間後就發送出去,大大減少服務端的I/O次數)

E,資料壓縮

42,幂等的處理方式?

答:一、查詢與删除操作是天然幂等

二、唯一索引,防止新增髒資料

三、token機制,防止頁面重複送出

四、悲觀鎖  for update

五、樂觀鎖(通過版本号/時間戳實作, 通過條件限制where avai_amount-#subAmount# >= 0)

六、分布式鎖

七、狀态機幂等(如果狀态機已經處于下一個狀态,這時候來了一個上一個狀态的變更,理論上是不能夠變更的,這樣的話,保證了有限狀态機的幂等。)

八、select + insert(并發不高的背景系統,或者一些任務JOB,為了支援幂等,支援重複執行)

43,HTTPS工作流程?

a、用戶端發送自己支援的加密規則給伺服器,代表告訴伺服器要進行連接配接了

b、伺服器從中選出一套加密算法和hash算法以及自己的身份資訊(位址等)以證書的形式發送給浏覽器,證書中包含伺服器資訊,加密公鑰,證書的辦法機構

c、用戶端收到網站的證書之後要做下面的事情: 

        c1、驗證證書的合法性

        c2、如果驗證通過證書,浏覽器會生成一串随機數作為密鑰K,并用證書中的公鑰進行加密

        c3、用約定好的hash算法計算握手消息,然後用生成的密鑰K進行加密,然後一起發送給伺服器

d、伺服器接收到用戶端傳送來的資訊,要求下面的事情: 

       d1、用私鑰解析出密碼,用密碼解析握手消息,驗證hash值是否和浏覽器發來的一緻

       d2、使用密鑰加密消息,回送

如果計算法hash值一緻,握手成功

44,RabbitMQ消息堆積怎麼處理?

答:

  • 增加消費者的處理能力(例如優化代碼),或減少釋出頻率
  • 單純更新硬體不是辦法,隻能起到一時的作用
  • 考慮使用隊列最大長度限制,RabbitMQ 3.1支援
  • 給消息設定年齡,逾時就丢棄
  • 預設情況下,rabbitmq消費者為單線程串行消費,設定并發消費兩個關鍵屬性concurrentConsumers和prefetchCount,concurrentConsumers設定的是對每個listener在初始化的時候設定的并發消費者的個數,prefetchCount是每次一次性從broker裡面取的待消費的消息的個數
  • 建立新的queue,消費者同時訂閱新舊queue
  • 生産者端緩存資料,在mq被消費完後再發送到mq
  • 打破發送循環條件,設定合适的qos值,當qos值被用光,而新的ack沒有被mq接收時,就可以跳出發送循環,去接收新的消息;消費者主動block接收程序,消費者感受到接收消息過快時主動block,利用block和unblock方法調節接收速率,當接收線程被block時,跳出發送循環。
  • 建立一個topic,partition是原來的10倍;然後寫一個臨時的分發資料的consumer程式,這個程式部署上去消費積壓的資料,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的10倍數量的queue;接着臨時征用10倍的機器來部署consumer,每一批consumer消費一個臨時queue的資料;等快速消費完積壓資料之後,得恢複原先部署架構,重新用原先的consumer機器來消費消息;

45,RabbitMQ的消息丢失解決方案?

答:

  • 消息持久化:Exchange 設定持久化:durable:true;Queue 設定持久化;Message持久化發送。
  • ACK确認機制:消息發送确認;消息接收确認。

46,負載均衡算法?

常見6種負載均衡算法:輪詢,随機,源位址哈希,權重輪詢,權重随機,最小連接配接數。

nginx5種負載均衡算法:輪詢,weight,ip_hash,fair(響應時間),url_hash

dubbo負載均衡算法:随機,輪詢,最少活躍調用數,一緻性Hash

47,JVM記憶體區域劃分?

答:

  • 堆:Java中的堆是用來存儲對象本身的以及數組(當然,數組引用是存放在Java棧中的),是Java垃圾收集器管理的主要區域。堆是被所有線程共享的,在JVM中隻有一個堆。
  • 虛拟機棧:虛拟機棧中存放的是一個個的棧幀,每個棧幀對應一個被調用的方法,在棧幀中包括局部變量表、操作數棧、指向目前方法所屬的類的運作時常量池的引用、方法傳回位址和一些額外的附加資訊。當線程執行一個方法時,就會随之建立一個對應的棧幀,并将建立的棧幀壓棧。當方法執行完畢之後,便會将棧幀出棧。
  • 本地方法棧:本地方法棧則是為執行本地方法(Native Method)服務的,在HotSopt虛拟機中直接就把本地方法棧和Java棧合二為一
  • 方法區:方法區與堆一樣,是被線程共享的區域。方法區存儲了類的資訊(包括類的名稱、方法資訊、字段資訊)、靜态變量、常量以及編譯器編譯後的代碼等。在方法區中有一個非常重要的部分就是運作時常量池,它是每一個類或接口的常量池的運作時表示形式,在類和接口被加載到JVM後,對應的運作時常量池就被建立出來。當然并非Class檔案常量池中的内容才能進入運作時常量池,在運作期間也可将新的常量放入運作時常量池中,比如String的intern方法。當方法區無法滿足記憶體配置設定需求時,則抛出OutOfMemoryError異常。在HotSpot虛拟機中,用永久代來實作方法區,将GC分代收集擴充至方法區,但是這樣容易遇到記憶體溢出的問題。JDK1.7中,已經把放在永久代的字元串常量池移到堆中。JDK1.8撤銷永久代,引入元空間。
  • 程式計數器(線程私有):是目前線程所執行的位元組碼的行号訓示器,每條線程都要有一個獨立的程式計數器,這類記憶體也稱為“線程私有”的記憶體。正在執行java方法的話,計數器記錄的是虛拟機位元組碼指令的位址(目前指令的位址)。如果還是Native方法,則為空。
  • 直接記憶體:在JDK1.4中新加入的NOI類,引入了一種基于通道與緩沖區的I/O方式,它可以使用Native函數直接配置設定堆外記憶體,然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。

48,jvm YGC和FGC發生的具體場景?

答:

正在處理的實作事務功能,下次自動復原。

隊列實作持久化儲存,下次啟動自動載入。

添加标志位,未處理 0,進行中 1,已處理 2。每次啟動的時候,把所有狀态為 1 的,置為 0。

關鍵性的應用就給電腦配個 UPS。

YGC :對新生代堆進行gc。頻率比較高,因為大部分對象的存活壽命較短,在新生代裡被回收。性能耗費較小。

FGC :全堆範圍的gc。預設堆空間使用到達80%(可調整)的時候會觸發fgc。以我們生産環境為例,一般比較少會觸發fgc,有時10天或一周左右會有一次。

YGC發生場景:edn空間不足

FGC發生場景:old空間不足,perm空間不足,調用方法System.gc() ,ygc時的悲觀政策, dump live的記憶體資訊時(jmap –dump:live)

49,一個線程池正在處理服務如果忽然斷電該怎麼辦?

答:

隊列實作持久化儲存,下次啟動自動載入。

但是實際需要看情況,大體思路是這樣。

添加标志位,未處理 0,進行中 1,已處理 2。每次啟動的時候,把所有狀态為 1 的,置為 0。或者定時器處理

關鍵性的應用就給電腦配個 UPS。

50,SpringBoot的優點?

答:

快速建構項目,極大的提高了開發、部署效率。

對主流開發架構的無配置內建。

項目可獨立運作,無須外部依賴Servlet容器。

提供運作時的應用監控。

51,DoS,DDoS,DRDoS攻擊分别是什麼?

答:DoS是Denial of Service的簡寫就是拒絕服務。

DDoS就是Distributed Denial of Service的簡寫就是分布式拒絕服務。

DRDoS就是Distributed Reflection Denial of Service的簡寫,分布反射式拒絕服務。

DoS、DDos以及DRDoS攻擊手段和防範措施

52,服務限流的方式?

答:

  • 漏桶:水(請求)先進入到漏桶裡,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(通路頻率超過接口響應速率),然後就拒絕請求。
  • 令牌桶算法:系統會按恒定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶裡加入Token,如果桶已經滿了就不再加了.新請求來臨時,會各自拿走一個Token,如果沒有Token就拒絕服務。
  • 基于redis實作的限流:假設每分鐘通路次數不能超過10次,在Redis中建立一個鍵,過期60秒,對此服務接口的通路就把鍵值加1,在60秒内增加到10的時候,禁止通路服務接口。
  • 計數器,滑動視窗

53,Quartz實作原理?

答:A、scheduler是一個計劃排程器容器(總部),容器裡面可以盛放衆多的JobDetail和trigger,當容器啟動後,裡面的每個JobDetail都會根據trigger按部就班自動去執行。

B、JobDetail是一個可執行的工作,它本身可能是有狀态的。

C、Trigger代表一個排程參數的配置,什麼時候去調。

D、當JobDetail和Trigger在scheduler容器上注冊後,形成了裝配好的作業(JobDetail和Trigger所組成的一對兒),就可以伴随容器啟動而排程執行了。

E、scheduler是個容器,容器中有一個線程池,用來并行排程執行每個作業,這樣可以提高容器效率。

54,資料庫的鎖?

答:行鎖(共享鎖和排他鎖),表鎖,頁級鎖,頁級鎖,意向鎖,讀鎖,寫鎖,悲觀鎖,樂觀鎖等

55,簡述ThreadPoolExecutor内部工作原理?

答:

先檢視目前運作狀态,如果不是RUNNING 狀态會拒絕執行任務,如果是RUNNING狀态,就會檢視目前運作的線程數量,如果小于核心線程數,會建立新的線程來執行這個任務,如果不小于核心線程,會将這個任務放到阻塞隊列去等代執行,直到上一個任務執行完再來執行這個任務。如果失敗會建立一個非核心線程來執行這個任務如果目前線程數大于最大線程數,會直接拒絕該任務。

56,聚集索引和非聚集索引的差別?

答:

聚集索引:

索引中鍵值的邏輯順序決定了表中相應行的實體順序(索引中的資料實體存放位址和索引的順序是一緻的),可以這麼了解:隻要是索引是連續的,那麼資料在存儲媒體上的存儲位置也是連續的。

比方說:想要到字典上查找一個字,我們可以根據字典前面的拼音找到該字,注意拼音的排列時有順序的。

聚集索引就像我們根據拼音的順序查字典一樣,可以大大的提高效率。在經常搜尋一定範圍的值時,通過索引找到第一條資料,根據實體位址連續存儲的特點,然後檢索相鄰的資料,直到到達條件截至項。

非聚集索引

索引的邏輯順序與磁盤上的實體存儲順序不同。非聚集索引的鍵值在邏輯上也是連續的,但是表中的資料在存儲媒體上的實體順序是不一緻的,即記錄的邏輯順序和實際存儲的實體順序沒有任何聯系。索引的記錄節點有一個資料指針指向真正的資料存儲位置。

總結如下:

如果一個主鍵被定義了,那麼這個主鍵就是作為聚集索引

如果沒有主鍵被定義,那麼該表的第一個唯一非空索引被作為聚集索引

如果沒有主鍵也沒有合适的唯一索引,那麼innodb内部會生成一個隐藏的主鍵作為聚集索引,這個隐藏的主鍵是一個6個位元組的列,改列的值會随着資料的插入自增。

InnoDB引擎會為每張表都加一個聚集索引,而聚集索引指向的的資料又是以實體磁盤順序來存儲的,自增的主鍵會把資料自動向後插入,避免了插入過程中的聚集索引排序問題。如果對聚集索引進行排序,這會帶來磁盤IO性能損耗是非常大的。

57,java并發包下有哪些類?

答:ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentNavigableMap

CopyOnWriteArrayList

BlockingQueue,BlockingDeque (ArrayBlockingQueue,LinkedBlockingDeque,LinkedBlockingQueue,DelayQueue,PriorityBlockingQueue,SynchronousQueue)

ConcurrentLinkedDeque,ConcurrentLinkedQueue,TransferQueue,LinkedTransferQueue

CopyOnWriteArraySet,ConcurrentSkipListSet

CyclicBarrier,CountDownLatch

Lock(ReetrantLock,ReetrantReadWriteLock)

Atomic包

58,threadlocal為什麼會出現oom?

答:ThreadLocal裡面使用了一個存在弱引用的map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal執行個體。這個Map的确使用了弱引用,不過弱引用隻是針對key。每個key都弱引用指向threadlocal。 當把threadlocal執行個體置為null以後,沒有任何強引用指向threadlocal執行個體,是以threadlocal将會被gc回收。 

但是,我們的value卻不能回收,而這塊value永遠不會被通路到了,是以存在着記憶體洩露。因為存在一條從current thread連接配接過來的強引用。隻有目前thread結束以後,current thread就不會存在棧中,強引用斷開,Current Thread、Map value将全部被GC回收。最好的做法是将調用threadlocal的remove方法。

在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap裡所有key為null的value,但是這些被動的預防措施并不能保證不會記憶體洩漏:

(1)使用static的ThreadLocal,延長了ThreadLocal的生命周期,可能導緻記憶體洩漏。 

(2)配置設定使用了ThreadLocal又不再調用get(),set(),remove()方法,那麼就會導緻記憶體洩漏,因為這塊記憶體一直存在。

59,mysql資料庫鎖表怎麼解決?

答:查詢鎖表資訊

目前運作的所有事務

select * from information_schema.innodb_trx

目前出現的鎖

select * from information_schema.innodb_locks

鎖等待的對應關系

select * from information_schema.innodb_lock_waits  

通過 select * from information_schema.innodb_trx 查詢 trx_mysql_thread_id然後執行 kill 線程ID

KILL   8807;//後面的數字即時程序的ID

60,java 判斷對象是否是某個類的類型方法?

  • instanceof 運算符是用來在運作時指出對象是否是特定類的一個執行個體。instanceof通過傳回一個布爾值來指出,這個對象是否是這個特定類或者是它的子類的一個執行個體。
  • getClass判斷,如o.getClass().equals(ClassA.class)。(使用instanceof來判斷一個對象是不是屬于某個類,但是有時候這個類是繼承于一個父類的,是以,不能嚴格判斷出是不是自己的類,而不是自己的父類。) 

61,Spring+MyBatis實作讀寫分離簡述?

答:

  • 方案一:通過MyBatis配置檔案建立讀寫分離兩個DataSource,每個SqlSessionFactoryBean對象的mapperLocations屬性制定兩個讀寫資料源的配置檔案。将所有讀的操作配置在讀檔案中,所有寫的操作配置在寫檔案中。
  • 方案二:通過Spring AOP在業務層實作讀寫分離,在DAO層調用前定義切面,利用Spring的AbstractRoutingDataSource解決多資料源的問題,實作動态選擇資料源
  • 方案三:通過Mybatis的Plugin在業務層實作資料庫讀寫分離,在MyBatis建立Statement對象前通過攔截器選擇真正的資料源,在攔截器中根據方法名稱不同(select、update、insert、delete)選擇資料源。
  • 方案四:通過spring的AbstractRoutingDataSource和mybatis Plugin攔截器實作非常友好的讀寫分離,原有代碼不需要任何改變。推薦第四種方案

62,紅黑樹的特點?

答:(1)每個節點或者是黑色,或者是紅色。

(2)根節點是黑色。

(3)每個葉子節點(NIL)是黑色。 [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!]

(4)如果一個節點是紅色的,則它的子節點必須是黑色的。

(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。[這裡指到葉子節點的路徑]

63,kafka消息會不會丢失?

答:Kafka消息發送分同步(sync)、異步(async)兩種方式。預設是使用同步方式,可通過producer.type屬性進行配置;Kafka保證消息被安全生産,有三個選項分别是0,1,-1。

通過request.required.acks屬性進行配置:

0代表:不進行消息接收是否成功的确認(預設值);

1代表:當Leader副本接收成功後,傳回接收成功确認資訊;

-1代表:當Leader和Follower副本都接收成功後,傳回接收成功确認資訊;

  • 網絡異常

    acks設定為0時,不和Kafka叢集進行消息接受确認,當網絡發生異常等情況時,存在消息丢失的可能;

  • 用戶端異常

    異步發送時,消息并沒有直接發送至Kafka叢集,而是在Client端按一定規則緩存并批量發送。在這期間,如果用戶端發生當機等情況,都會導緻消息的丢失;

  • 緩沖區滿了

    異步發送時,Client端緩存的消息超出了緩沖池的大小,也存在消息丢失的可能;

  • Leader副本異常

    acks設定為1時,Leader副本接收成功,Kafka叢集就傳回成功确認資訊,而Follower副本可能還在同步。這時Leader副本突然出現異常,新Leader副本(原Follower副本)未能和其保持一緻,就會出現消息丢失的情況;

以上就是消息丢失的幾種情況,在日常應用中,我們需要結合自身的應用場景來選擇不同的配置。

想要更高的吞吐量就設定:異步、ack=0;想要不丢失消息資料就選:同步、ack=-1政策

64,kafka的leader副本選舉?

答:如果某個分區patition的Leader挂了,那麼其它跟随者将會進行選舉産生一個新的leader,之後所有的讀寫就會轉移到這個新的Leader上,在kafka中,其不是采用常見的多數選舉的方式進行副本的Leader選舉,而是會在Zookeeper上針對每個Topic維護一個稱為ISR(in-sync replica,已同步的副本)的集合,顯然還有一些副本沒有來得及同步。隻有這個ISR清單裡面的才有資格成為leader(先使用ISR裡面的第一個,如果不行依次類推,因為ISR裡面的是同步副本,消息是最完整且各個節點都是一樣的)。

  通過ISR,kafka需要的備援度較低,可以容忍的失敗數比較高。假設某個topic有f+1個副本,kafka可以容忍f個不可用,當然,如果全部ISR裡面的副本都不可用,也可以選擇其他可用的副本,隻是存在資料的不一緻。

65,kafka消息的檢索?

答:其實很簡單主要是用二分查找算法,比如我們要查找一條offest=10000的檔案,kafka首先會在對應分區下的log檔案裡采用二分檢視定位到某個記錄該offest

=10000這條消息的log,然後從相應的index檔案定位其偏移量,然後拿着偏移量到log裡面直接擷取。這樣就完成了一個消息的檢索過程。

66,RabbitMQ 叢集方式?

答:

1)普通叢集:

以兩個節點(rabbit01、rabbit02)為例來進行說明。

    rabbit01和rabbit02兩個節點僅有相同的中繼資料,即隊列的結構,但消息實體隻存在于其中一個節點rabbit01(或者rabbit02)中。

    當消息進入rabbit01節點的Queue後,consumer從rabbit02節點消費時,RabbitMQ會臨時在rabbit01、rabbit02間進行消息傳輸,把A中的消息實體取出并經過B發送給consumer。是以consumer應盡量連接配接每一個節點,從中取消息。即對于同一個邏輯隊列,要在多個節點建立實體Queue。否則無論consumer連rabbit01或rabbit02,出口總在rabbit01,會産生瓶頸。當rabbit01節點故障後,rabbit02節點無法取到rabbit01節點中還未消費的消息實體。如果做了消息持久化,那麼得等rabbit01節點恢複,然後才可被消費;如果沒有持久化的話,就會産生消息丢失的現象。

2)鏡像叢集:

 在普通叢集的基礎上,把需要的隊列做成鏡像隊列,消息實體會主動在鏡像節點間同步,而不是在用戶端取資料時臨時拉取,也就是說多少節點消息就會備份多少份。該模式帶來的副作用也很明顯,除了降低系統性能外,如果鏡像隊列數量過多,加之大量的消息進入,叢集内部的網絡帶寬将會被這種同步通訊大大消耗掉。是以在對可靠性要求較高的場合中适用

    由于鏡像隊列之間消息自動同步,且内部有選舉master機制,即使master節點當機也不會影響整個叢集的使用,達到去中心化的目的,進而有效的防止消息丢失及服務不可用等問題

67,ElasticSearch如何解決深度分頁的問題?

答:使用scroll(有狀态)和search after(無狀态)的遊标方式。

68,java代碼優化(一)

答:盡量指定類、方法的final修飾符。

盡量重用對象。

盡可能使用局部變量。

及時關閉流。

盡量減少對變量的重複計算。

盡量采用懶加載的政策,即在需要的時候才建立。

慎用異常。

不要在循環中使用try...catch...,應該把其放在最外層。

如果能估計到待添加的内容長度,為底層以數組方式實作的集合、工具類指定初始長度。

當複制大量資料時,使用System.arraycopy()指令。

乘法和除法使用移位操作。

循環内不要不斷建立對象引用。

基于效率和類型檢查的考慮,應該盡可能使用array,無法确定數組大小時才使用ArrayList。

盡量使用HashMap、ArrayList、StringBuilder,除非線程安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,後三者由于使用同步機制而導緻了性能開銷。

不要将數組聲明為public static final。

69,java代碼優化(二)

答:盡量在合适的場合使用單例。

盡量避免随意使用靜态變量。

及時清除不再需要的會話。

實作RandomAccess接口的集合比如ArrayList,應當使用最普通的for循環而不是foreach循環來周遊

使用同步代碼塊替代同步方法。

将常量聲明為static final,并以大寫命名。

不要建立一些不使用的對象,不要導入一些不使用的類。

程式運作過程中避免使用反射。

使用資料庫連接配接池和線程池。

使用帶緩沖的輸入輸出流進行IO操作。

順序插入和随機通路比較多的場景使用ArrayList,元素删除和中間插入比較多的場景使用LinkedList。

不要讓public方法中有太多的形參。

字元串變量和字元串常量equals的時候将字元串常量寫在前面。

請知道,在java中if (i == 1)和if (1 == i)是沒有差別的,但從閱讀習慣上講,建議使用前者。

不要對數組使用toString()方法。

不要對超出範圍的基本資料類型做向下強制轉型。

70,java代碼優化(三)

答:公用的集合類中不使用的資料一定要及時remove掉。

把一個基本資料類型轉為字元串,基本資料類型.toString()是最快的方式、String.valueOf(資料)次之、資料+""最慢

使用最有效率的方式去周遊Map。

對資源的close()建議分開操作。

對于ThreadLocal使用前或者使用後一定要先remove。

切記以常量定義的方式替代魔鬼數字,魔鬼數字的存在将極大地降低代碼可讀性,字元串常量是否使用常量定義可以視情況而定。

long或者Long初始指派時,使用大寫的L而不是小寫的l,因為字母l極易與數字1混淆,這個點非常細節,值得注意。

所有重寫的方法必須保留@Override注解。

推薦使用JDK7中新引入的Objects工具類來進行對象的equals比較,直接a.equals(b),有空指針異常的風險。

循環體内不要使用"+"進行字元串拼接,而直接使用StringBuilder不斷append。

不捕獲Java類庫中定義的繼承自RuntimeException的運作時異常類。

避免Random執行個體被多線程使用,雖然共享該執行個體是線程安全的,但會因競争同一seed導緻的性能下降,JDK7之後,可以使用ThreadLocalRandom來擷取随機數。

靜态類、單例類、工廠類将它們的構造函數置為private。

71,單點登入原理與簡單實作?

答:相比于單系統登入,sso需要一個獨立的認證中心,隻有認證中心能接受使用者的使用者名密碼等安全資訊,其他系統不提供登入入口,隻接受認證中心的間接授權。間接授權通過令牌實作,sso認證中心驗證使用者的使用者名密碼沒問題,建立授權令牌,在接下來的跳轉過程中,授權令牌作為參數發送給各個子系統,子系統拿到令牌,即得到了授權,可以借此建立局部會話,局部會話登入方式與單系統的登入方式相同。這個過程,也就是單點登入的原理,用下圖說明

阿裡java進階工程師面試100題

單點登入自然也要單點登出,在一個子系統中登出,所有子系統的會話都将被銷毀,用下面的圖來說明

阿裡java進階工程師面試100題

72,MQ做資料同步也會造成不一緻,又需要引入監控,實時計算2個叢集的資料同步,做一緻性同步。大部分來說,同步es和solr不要在代碼中去同步,同步失敗無法保證事務,而且業務耦合。可以使用Databug和cancel等工具去做代碼解耦,MQ支援重試,存儲失敗後抛出異常下次再處理。資料做異構,對外服務時任意拼裝,MYSQL在半同步複制上做了一些優化,保證了一緻性,引入了諸如paxos等主流算法保證強一緻性問題。

當DB(監聽從庫),binlog有變化,cancel監聽到時候解析過濾發送MQ(表名字,主鍵等)到變化的實時從庫中查詢資料同步到ES聚合表,MQ可以重試,系統解耦。事務log挖掘縣城會對DB的事務log監聽,并把這些事件釋出到消息代理。

73,分布式服務調用可以實作跟蹤系統,可以在業務日志中添加調用鍊ID,各個環節RPC均添加調用時延,QPS等。

非業務元件應該少加入業務代碼,服務調用采用買點,也會采用配置采樣率方式,買點即目前節點的上下文資訊,包含TraceId,RPCId,開始結束時間,類型,協定,調用方IP,端口,服務名等,以及其他異常資訊,封包等擴充,日志采用離線+實時的如flume結合kafka等,應按照TraceId彙總日志後按RPCId順序整理。

74,Sentinel 工作原理?

答:(1)每個 Sentinel 以每秒鐘一次的頻率向它所知的 Master,Slave 以及其他 Sentinel 執行個體發送一個 PING 指令;

(2)如果一個執行個體(instance)距離最後一次有效回複 PING 指令的時間超過 down-after-milliseconds 選項所指定的值, 則這個執行個體會被 Sentinel 标記為主觀下線;

(3)如果一個 Master 被标記為主觀下線,則正在監視這個 Master 的所有 Sentinel 要以每秒一次的頻率确認 Master 的确進入了主觀下線狀态;

(4)當有足夠數量的 Sentinel(大于等于配置檔案指定的值)在指定的時間範圍内确認 Master 的确進入了主觀下線狀态,則 Master 會被标記為客觀下線;

(5)在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有 Master,Slave 發送 INFO 指令;

當 Master 被 Sentinel 标記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發送 INFO 指令的頻率會從 10 秒一次改為每秒一次;

(6)若沒有足夠數量的 Sentinel 同意 Master 已經下線, Master 的客觀下線狀态就會被移除;

(7)若 Master 重新向 Sentinel 的 PING 指令傳回有效回複, Master 的主觀下線狀态就會被移除。

監控( Monitoring ): Redis Sentinel 實時監控主伺服器和從伺服器運作狀态;

自動故障轉移:如果一個 master 不正常運作了,哨兵可以啟動一個故障轉移程序,将一個 slave 更新成為 master,其他的 slave 被重新配置使用新的 master,并且應用程式使用 Redis 服務端通知的新位址;

75,高性能統計UV的方式?

(1)使用redis的set集合

(2)使用redis的bitmap(注意記憶體消耗)

76,Hbase二級索引,索引海量資料實作方案?

答:

(1) 方案1:使用開源的hbase-indexer,是借助于hbase的WAL實作,不會影響hbase性能

   https://blog.csdn.net/xiahoujie_90/article/details/53400044

(2)  方案2:基于ES自己實作,利用habse的協處理器實作,會影響hbase性能

關鍵注意點:因為資料是存在Hbase中,ES充當的是索引角色,是以在建立ES的mapping時,

應指定_source為enabled:false。關閉存儲原始文檔。

https://wenku.baidu.com/view/422722fdd0f34693daef5ef7ba0d4a7303766c71.html

77,Elasticsearch分片使用優化?

答:(1)拆分叢集

對于存在明顯分界線的業務,可以按照業務、地域使用不同叢集,這種拆分叢集的思路是非常靠譜的。對于我們的場景,已經按照地域拆分了叢集,且同一地域的子業務間分界線不明顯,拆分過多的叢集維護成本較高。

(2)調整滾動周期

根據保留時長調整index滾動周期是最簡單有效的思路。例如保留3天的資料按天滾動,保留31天的資料按周滾動,保留一年的資料按月滾動。合理的滾動周期,可以在存儲成本增加不大的情況下,大幅降低分片數量。

對于我們的場景,大部分資料保留31天,在按周滾動的情況下,叢集的總分片數可以下降到6.5w~個。

(3)合理設定分片數和副本數

除個别子業務壓力較高外,大部分業務壓力較小,合理設定單Index的分片數效果也不錯。我們的經驗是單個分片的大小在10GB~30GB之間比較合适,對于壓力非常小的業務可以直接配置設定1個分片。其他使用者可結合具體場景考慮,同時注意單分片的記錄條數不要超過上限2,147,483,519。

在平衡我們的業務場景對資料可靠性的要求 及 不同副本數對存儲成本的開銷 兩個因素之後,我們選擇使用一主一從的副本政策。

目前我們叢集單Index的平均配置設定數為3,叢集的總分片數下降到3w~個。

(4)分片配置設定流程優化

預設情況下,ES在配置設定分片時會考慮分片relocation對磁盤空間的影響。在分片數較少時,這個優化處理的副作用不明顯。但随着單機分片數量的上升,這個優化處理涉及的多層循環嵌套過程耗時愈發明顯。可通過cluster.routing.allocation.disk.include_relocations: false關閉此功能,這對磁盤均衡程度影響不明顯。

(5)預建立Index

對于單叢集3w分片的場景,集中在每周某天0點建立Index,對叢集的壓力還是較大,且存儲空間存在波動。考慮到叢集的持續擴充能力和可靠性,我們采用預建立方式提前建立分片,并把按Index的建立時間均勻打散到每周的每一天。

(6)持續調整分片數

對于叢集分片的調整,通常不是一蹴而就的。随着業務的發展,不斷新增的子業務 或 原有子業務規模發生突變,都需要持續調整分片數量。

預設情況下,新增的子業務會有預設的分片數量,如果不足,會在測試階段及上線初期及時發現。随着業務發展,系統會考慮Index近期的資料量、寫入速度、叢集規模等因素,動态調整分片數量。

78,如何編寫高品質代碼151建議?

答:

一、Java開發中通用的方法和準則

不要在常量和變量中出現易混淆的字母;

莫讓常量蛻變成變量;

三元操作符的類型務必一緻;

避免帶有變長參數的方法重載;

别讓null值和空值威脅到變長方法;

覆寫變長方法也要循規蹈矩;

警惕字增的陷阱;

不要讓舊文法困擾你;

少用靜态導入;

不要在本類中覆寫靜态導入的變量和方法;

養成良好習慣,顯示聲明UID;

避免用序列化類在構造函數中為不變量指派;

避免為final變量複雜指派;

使用序列化類的私有方法巧妙解決部分屬性持久化問題;

break萬萬不可忘;

易變業務使用腳本語言編寫;

慎用動态編譯;

避免instantceof非預期結果;

斷言對決不是雞肋;

不要隻替換一個類;

二、基本類型

使用偶判斷,不用奇判斷;

用整數類型處理貨币;

不要讓類型默默轉換;

邊界,邊界,還是邊界;

不要讓四舍五入虧了一方;

提防包裝類型的null值;

謹慎包裝類型的大小比較;

優先使用整型池;

優先選擇基本類型;

不要随便設定随機種子;

三、類、對象及方法

在接口中不要存在實作代碼;

靜态變量一定要先聲明後指派;

不要覆寫靜态方法;

構造函數盡量簡化;

避免在構造函數中初始化其他類;

使用構造代碼塊精煉程式;

使用靜态内部類提供封裝性;

使用匿名類的構造函數;

匿名類的構造函數很特殊;

讓多重繼承成為現實;

讓工具類不可執行個體化;

避免對象的淺拷貝;

推薦使用序列化實作對象的拷貝;

覆寫equals方法時不要識别不出自己;

equals應該考慮null值情景;

在equals中使用getClass進行類型判斷;

覆寫equals方法必須覆寫hashCode方法;

推薦覆寫toString方法;

使用package-info類為包服務;

不要主動進行垃圾回收;

四、字元串

推薦使用String直接量指派;

注意方法中傳遞的參數要求;

正确使用String、StringBuffer、StringBuilder;

注意字元串的位置;

自由選擇字元串拼接方法;

推薦在複雜字元串操作中使用正規表達式;

強烈建議使用UTF編碼;

對字元串排序持一種寬容的心态;

五、數組和集合

性能考慮,數組是首選;

若有必要,使用變長數組;

警惕數組的淺拷貝;

在明确的場景下,為集合指定初始容量;

多種最值方法,适時選擇;

避開基本類型數組轉換清單陷阱;

asList方法産生的List對象不可更改;

不同的清單選擇不同的周遊方法;

頻繁插入和删除時使用LinkedList;

清單相等隻需關心元素資料;

推薦使用subList處理局部清單;

生成子表後不要再操作原清單;

使用Comparator進行排序;

不推薦使用binarySearch對清單進行檢索;

集合中的元素必須做到compareTo和equals同步;

集合運算時使用更優雅的方式;

使用shuffle大亂清單;

減少HashMap中元素的數量;

集合中的哈希碼不要重複;

多線程使用Vector或者HashTable;

非穩定排序推薦使用List;

六、枚舉和注解

推薦使用枚舉定義常量;

使用構造函數協助描述枚舉項;

小心switch帶來的空值異常;

在switch的default代碼塊中增加AssertionError錯誤;

使用valueOf前必須進行校驗;

用枚舉實作工廠方法模式更簡潔;

枚舉項的數量限制在64個以内;

小心注解繼承;

枚舉和注解結合使用威力更大;

注意@Override不同版本的差別;

七、枚舉和注解

Java的泛型是類型擦除的;

不能初始化泛型參數和數組;

強制聲明泛型的實際類型;

不同的場景使用不同的泛型通配符;

警惕泛型是不能協變和逆變的;

建議采用的順序是List<T>、List<?>、List<Object>;

嚴格限定泛型類型采用多重界限;

數組的真實類型必須是泛型類型的子類型;

注意Class類的特殊性;

适時選擇getDeclaredXXX和getXXX;

反射通路屬性或方法時将Accessible設定為true;

使用forName動态加載類檔案;

動态加載不合适數組;

動态代理可以使代理模式更加靈活;

反射讓模闆方法模式更強大;

不需要太多關注反射效率;

八、異常

提倡異常封裝;

采用異常鍊傳遞異常;

受檢異常盡可能轉化為非受檢異常;

不要在finally塊中處理傳回值;

使用Throwable擷取棧資訊;

異常隻為異常服務;

多使用異常,把性能問題放一邊;

九、多線程和并發

不推薦覆寫start方法;

啟動線程前stop方法是不可靠的;

不使用stop方法停止線程;

線程優先級隻使用三個等級;

使用線程異常處理器提升系統可靠性;

volatile不能保證資料同步;

異步運算多考慮使用Callable接口;

優先選擇線程池;

适時選擇不同的線程池來實作;

Lock與synchronized是不一樣的;

預防線程死鎖;

适當設定阻塞隊列長度;

使用CountDownLatch協調子線程;

CyclicBarrier讓多線程齊步走;