某上市500強互聯網企業-Java開發崗位筆試題和解析(一)

               某上市500強互聯網企業-Java開發崗位筆試題和解析(一)                                                                                                                           

                                                                                                                              作者:田超凡

版權所有,轉載請註明原作者,仿冒侵權必究法律責任

 

1. Java 基礎部分

1.1 談談單元測試和集成測試的定義和理解?如果一個方法中有遠程調用,那麼如何進行單元測試?如何使用單元測試測試遠程調用異常的情況?

答:單元測試我的理解是後臺開發人員在編寫完某一個模塊的功能代碼之後,對每一個模塊的功能代碼進行測試,主要是使用一些給定的測試參數用來排查後臺代碼容易產生的各類異常問題,保證某一個功能模塊後臺代碼的健壯性。單元測試的計量單位是模塊和方法,嚴格來說指的是開發的每一個模塊都需要進行單元測試,一般情況下根據項目架構的不同,對於模塊劃分的定義也是不同的。

比如在面向服務的SOA架構或者微服務架構項目中,我理解的模塊是每一個服務項目中的每一個業務模塊,具體的單元測試的每一個模塊除了從項目架構的角度上來考慮劃分的粒度之外,還要考慮到實際的業務場景,可以簡單理解爲對自己開發的業務模塊的最細粒度的一種測試方式,根據業務複雜度可以考慮到針對模塊中的每一個方法進行測試。單元測試是一種最細粒度的測試,是檢查功能模塊的代碼健壯性的主要實現方式,是白盒測試最細粒度的一種實現,由開發人員直接在後臺進行模塊化測試,是保證開發代碼每一個模塊、每一個方法健壯性的最有效實現方案。優點是測試粒度很細,出現問題可以及時快速定位到問題,缺點是如果測試的粒度沒有確定好,業務模塊和方法數量多的情況下,效率很低。

集成測試我理解的是分爲後臺功能集中測試和前後臺集成測試。後臺功能集中測試對每一個服務的所有功能進行測試,集成測試一般會安排在單元測試之後進行,一方面集成測試是對單元測試結果的檢驗,可以暴露某一個單元測試的問題,一方面由於前後臺分離開發,即使單元測試都能通過,也不能保證前後臺請求響應、數據交互都是完全正常的,比如我經常遇到前後臺參數界定不清晰,前臺傳遞的參數後臺沒有準確接收,後臺返回的響應數據前臺沒有正確渲染等等問題,那麼爲了解決這類問題,除了使用一些既定的方式比如Swagger API接口文檔可視化、針對每次服務開發編寫後臺代碼請求和響應接口文檔之外,還需要使用集成測試來首先進行後臺開發的所有模塊的集成測試,一方面解決RPC遠程調用服務通信異常、單元測試沒有發現的問題,然後在進行前後臺集成測試來確保模塊正常進行前後臺交互。

如果一個方法進行RPC遠程調用的時候,單元測試需要定位到每一個遠程調用的地方進行測試,如果是這種問題,我會把遠程調用的方法先單獨進行單元測試,如果遠程調用的方法有問題,那麼我會去聯繫提供這個接口實現的同事去檢查他的接口實現。如果遠程調用的方法單獨測試都沒有問題,那麼我會在單獨測試我方法裏的代碼,如果沒有問題,再統一進行測試。

 

 

1.2 詳細描述一個對象在 JVM 堆內的可能的生命週期?比如一個對象在哪塊內存區域創建?YGC & FGC 之後的結果?越詳細越好,考慮的情況越多越好。

答:

JVM堆採用分代算法來管理對象的生命週期。

對象在JVM堆中的生命週期分爲新生代、老年代、永久代

新創建的對象默認優先分配到新生代Eden區,新生代主要有三塊區域組成:Eden、Survivor S0(from)、Survivor S1(to)

新生代管理對象生命週期主要是使用複製算法實現的,每個對象每次執行復制算法會年齡+1,當達到默認分代年齡(默認15,可以通過JVM參數配置)之後,會把對象分配到老年代

第一次複製:Eden區滿了之後,會進行一次YGC回收,把對象複製到S0區,

第二次複製:對象從Eden、S0區複製到S1區

第三次複製:對象從S1區複製到S0區

第4-15次複製:對象在Survivor倖存者區S0和S1之間來回複製,直到達到對象分代年齡後把對象分配到老年代

YGC是新生代的垃圾回收,每次執行YGC都會強制觸發STW,暫停所有除了當前垃圾回收線程之外的其他線程執行

FGC=MinorGC+MajorGC,會對整個堆區間進行垃圾回收,如果頻繁執行FGC會影響GC性能,需要考慮是不是出現了內存泄漏、內存溢出等問題。所以GC性能調優的最終目的就是儘量減少FGC執行冊數

新生代對象分配到老年代主要有以下幾種情況,可以理解爲晉升:

  1. 大對象直接分配到老年代(對象大小過大,新生代裝不下)
  2. 長期存活的對象直接分配到老年代(新生代對象達到分代年齡)
  3. 空間分配擔保(老年代對象建立了對新生代對象的引用,需要在老年代單獨開闢一塊空間存放新晉升來的對象數據,當然前提是老年代有可擴充的空間)
  4. 動態對象年齡(當Survivor倖存者區相同年齡的對象總數超過Survivor倖存者區所有對象總數的一半時,此時會把其他年齡大於這個年齡的對象直接分配到老年代,不需要達到默認分代年齡)

 

常用的垃圾回收算法有:引用計數器、根搜索、標記清除算法、複製算法、標記壓縮算法、分代算法

新生代回收主要使用複製算法實現,新生代對象活動比較頻繁

老年代回收主要使用標記清除和標記壓縮算法實現。

新生代常用的垃圾回收器是串行Serial、並行ParNew、吞吐優先Parralel-Scavenge

老年代常用的垃圾回收器是併發標記清除CMS

主流的HotSpotVM虛擬機默認採用ParNew新生代+CMS老年代實現垃圾回收

GC性能調優可以通過一系列JVM參數和GC命令實現,如jstack、jmap

 

1.3 以下程序會輸出幾次 Hello World. cnt = ? 爲什麼?

 

 

 

答:這個JDK1.8線程池的用法突然忘了,所以我不能亂猜結果,說下我對併發編程的理解:

併發編程這塊我目前只對IO相關、線程的基本概念、線程鎖、分佈式鎖、CAS無鎖機制有一定了解,這塊還在學習

說下對IO併發編程的理解:

BIO 默認的實現方式,同步阻塞操作,也就是如果IO操作響應時間長只能等待,線程阻塞

NIO 同步非阻塞IO操作,使用輪詢的方式獲取IO操作的響應結果,不會阻塞線程,但是高併發訪問的時候,線程池開銷大

AIO 異步IO操作,異步的方式實現IO操作,即使沒有獲取到IO操作執行結果,程序照樣正常執行。

信號驅動IO:接收到給定的信號之後纔會進行IO操作,不主動執行IO操作,必須先接收到對應的信號。

線程鎖主要是一個JVM中多個線程併發執行時的線程安全問題,屬於線程併發,可以使用悲觀鎖和樂觀鎖實現

分佈式鎖主要是多個JVM並行時JVM中的線程安全問題,屬於進程並行,可以使用zookeeper臨時節點實現,也可以用redis操作Key實現

CAS無鎖機制實現是基於Unsafe類中的public static native final compareAndSwapInt方法實現,不爲常用。

 

2. 中間件相關

2.1 列舉你所知的常用的中間件框架、組件,並描述每個中間件使用場景。

答:列舉幾個常用的:

Redis分佈式緩存:主要用來作分佈式緩存和分佈式數據共享,一般在保存Token令牌、短信驗證碼、緩存查詢數據、訪問計數器、實現分佈式鎖、延遲操作

Netty分佈式網絡通信:主要是基於NIO實現的一款支持多種網絡傳輸協議的框架,本質是對JDK內置的NIO操作API的重新封裝和完善,使得NIO操作更加易用,可以快速搭建服務器和客戶端,

主要用來做基於TCP協議的網絡通信數據傳輸。

Dubbo RPC遠程調用框架:使用RPC遠程服務調用實現服務之間的通信,底層實現是基於HTTP+Restful接口實現網絡數據傳輸,Dubbo服務器端和客戶端可以使用Netty實現,Dubbo提供了服務發現、服務治理、服務容錯、負載均衡等策略,實現高性能RPC遠程服務調用。Dubbo中的服務分爲生產者、消費者,生產者發佈服務信息到註冊中心Zookeeper,消費中從Zookeeper中獲取服務信息,典型的發佈訂閱模式實現。

Dubbo在傳統SOA架構中使用的比較多,但是微服務項目一般使用Eureka或者Nacos作爲註冊中心,因爲Dubbo配置比較繁瑣、服務機制不夠完善,不易於敏捷開發。

ActiveMQ消息隊列:發佈訂閱模式實現數據同步,當需要實現數據實時同步的場景下,比如前後臺管理系統分離的項目,前臺渲染數據和後臺管理系統變動的數據實時同步,那麼就會用MQ消息隊列實現數據同步。

ActiveMQ消息隊列提供了Queue和Topic模式實現,即點對點和點對多,區別說白了就是訂閱者可以有一個或多個,監聽主題角色的數據,當主題數據變化就會通知訂閱者實現數據同步,採用觀察者模式實現。

 

 

 

2.2 詳細描述一次 RPC 調用經過的完整鏈路。

答:首先,需要配置好註冊中心來進行服務註冊和服務治理,管理髮布者發佈的服務。

其次,需要提供服務生產者和消費者,總的實現原則是:生產者發佈消息到註冊中心,消費者訂閱註冊中心獲取服務。

RPC遠程調用最核心的部分就是通信機制,一般都是基於HTTP協議實現通信,因爲HTTP協議底層是基於TCP長連接的,比較穩定和可靠。

RPC遠程調用數據傳輸已辦都使用一些常用的通用的、跨語言、跨平臺數據交換格式,比如JSON、MessagePack、XML等等。

RPC遠程調用可以通過客戶端負載均衡和服務器負載均衡來管理調度不同的服務請求,減輕服務器的訪問壓力,比如客戶端負載均衡ribbon、feign,服務器負載均衡nginx等

發佈者通過serviceid把自己註冊到註冊中心,消費者在需要調用的時候就可以通過需要調用的serviceid獲取註冊中心對應的服務實現遠程調用。

 

 

 

3. 開放題

3.1 線上機器 CPU、Load 飆升的原因可能有哪些?

答:

內存泄漏、內存溢出、空輪訓、線程死鎖

內存泄漏:JVM堆區間新生代老年代都存滿了,超過了最大可用內存大小,此時服務會直接不可用,必須強制去定位GC日誌釋放內存後重啓服務,ERROR級別異常

內存溢出:內存溢出是指內存泄漏發生之後,程序依然在往JVM中申請內存空間,導致本來已經存不下的JVM堆區間不斷加入新的對象導致溢出,這種問題的體現就是會發現

FGC頻繁執行,並且內存大小反而還在不斷提高,意思是FGC垃圾回收不夠充分,這時就要考慮是否發生了內存溢出問題,內存溢出問題可以調整JVM堆區間大小和其他的一些GC調優方式實現

空輪訓:程序中存在大量的死循環獲取數據,比如Selector選擇器空輪訓問題。

線程死鎖:線程鎖裏面嵌套鎖,可以通過gc日誌deadLock定位問題代碼

有一個竅門,對於使用了SpringCloud的項目而言,可以使用Hystrix、Turbin等儀表盤來實時監測服務運行情況,遇見問題可以通過Sleuth+Zipkin請求服務鏈路動態追蹤快速定位問題。

 

3.2 工作過程中,經常遇到各種各樣的問題,如何快速定位問題?或者描述自己排查問題的思路?

答:

首先斷掉調試這個,就不用多說了吧

然後,對於目前做的SOA架構和微服務架構項目而言,我一般優先考慮一些官方的各類工具來及時發現和定位問題

其次,我會使用PostMan等接口監測工具傳遞一些測試參數來模擬發送請求,檢查服務器代碼問題,這個主要是因爲前後臺分離,我不可能每次都讓前臺同時發送請求過來吧,

一般情況下,如果是一些基本的代碼常見問題、經常產生的異常問題,都可以根據堆棧信息快速解決。

如果是一些之前碰到過的問題,或者有印象但是長時間沒遇到的問題,我會問同事,同時去原來的代碼中找到類似的代碼文件進行比對。

如果是一些沒有碰到過的問題,我會直接去百度,節約時間。

如果是前後臺聯調的時候發現的問題,我會直接去讓前臺同時把瀏覽器調試工具獲取到的參數發給我(這種情況一般是用於後臺數據查詢出來,但是渲染結果不對的情況,一般都是因爲參數名不一致導致),

如果是後臺接收參數不對的問題,我會通過一些日誌工具比如logback、log4j等打印日誌定位問題。

後臺問題主要還是通過日誌來發現、跟蹤、定位問題

如果發生線上和本機運行結果不一致的問題,一般會先把線上的代碼拿過來反編譯,確認一致還是沒有發現問題,就會遠程調試

 

 

3.3 你認爲系統開發過程中的難點有哪些?

答:

  1. 前後臺分離有利也有弊,優點是職責清晰,可以敏捷開發,對接方便,缺點是前後臺聯調出現各種參數不對、數據渲染錯誤問題。

這種問題一方面需要有成熟的文檔和API接口規範來保證前後臺代碼規範,一方面前後臺的單元、集成測試都必須做充分

  1. 線上和線下環境運行結果不匹配:Maven包版本衝突、JDK版本問題、跨域等問題經常發現

對於500系列問題,排查代碼,把class文件拿過來反編譯進行對比,確認有問題則改問題,如果一致,則需要檢查數據庫數據情況,是否有冗餘數據、字典不匹配的問題等等

對於404系列問題,一方面聯繫前端,看接口url是否錯誤,一方面檢查註冊中心服務是否出現問題

對於400/403系列問題,檢查參數接收和前臺傳遞的是否一致

 

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