Java面試題沖刺第二十五天–實戰編程2

面試題1:當你發現一條SQL很慢,你的處理思路是什麼?

  • 發現Bug
  • 確定Bug不是自己造成的,如果無法確定,不要理會步驟1
  • 向組內宣傳“程序裡有一個未知Bug,錯不在我”
  • 誰響應,誰對Bug負責
  • 沒人響應,就要求特定人員配合調試
  • 如果不配合,就是特定人員對Bug負責
  • 如果特定人員配合,就相當於特定人員發現瞭一個Bug
  • 讓特定人員看步驟1
  • 完美,無懈可擊

在這裡插入圖片描述

我們是如何發現慢SQL的?除瞭慢查詢日志和人為發現之外,一般出現慢查詢時會有如下三個特征:

  • 數據庫CPU負載高。一般是查詢語句中有很多計算邏輯,或並發處理線程較多,導致數據庫cpu負載。
  • IO過高導致服務器卡住,這個一般和全表查詢沒索引有關系,問題出在處理的數據量太大。
  • 查詢語句正常,索引正常但是還是慢。如果表面上索引都配置瞭,但是查詢慢,那得看看索引是否生效。

有些SQL雖然出現在慢查詢日志中,但未必是其本身的性能問題,可能是因為鎖等待,服務器壓力高等等。

需要分析SQL語句真實的執行計劃,而不是看重新執行一遍SQL時,看是不是變快瞭(查詢緩存都不帶考慮的?)。。而是使用Explain工具來逐步調優,瞭解 MySQL 在執行這條數據時的一些細節,比如是否進行瞭優化、是否使用瞭索引、索引選擇器是否正確選擇等等。基於 Explain 的返回結果我們就可以根據 MySQL 的執行細節進一步優化語法,使索引能被正確使用,實現性能需求。

關於索引的創建及優化原則,美團點評技術團隊的幾點總結,引用一下:

1.最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到范圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整;

2.=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式,當然,好習慣要靠自己保持;

3.盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄;

4.索引列不能參與計算,保持列“幹凈”,比如from_unixtime(create_time) = ‘2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(‘2014-05-29’);

5.盡量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼隻需要修改原來的索引即可。

當然,我們知道還有單表數據量過大、大字段檢索、模糊匹配效率、時間維度統計效果差等MySQL硬性問題,俗稱硬傷,那我們就得基於實際情況來進行分庫分表、切換檢索引擎(如ES、FastDFS)等方式來處理瞭,術業有專攻,總不能在一棵樹上吊死對吧。

面試題2:怎麼理解負載均衡的?你處理負載均衡都有哪些途徑?

負載均衡(Load Balance)是集群技術(Cluster)的一種應用。負載均衡可以將工作任務分攤到多個處理單元,從而提高並發處理能力。下面是一組普通的web架構,我理解做負載均衡的切入點一般在web層和數據層。

在這裡插入圖片描述

目前最常見的負載均衡應用是Web層負載均衡。根據實現的原理不同,常見的web負載均衡技術包括:DNS輪詢、IP負載均衡、CDN、反向代理以及http重定向等等。其中IP負載均衡可以使用硬件設備或軟件方式來實現。

對於數據層負載均衡,我們常用的分佈式架構、多節點集群、消息中間件(如Mycat)等來控制集群負載均衡狀況,同樣是橫向擴展,但側重點在於對集群的管理和災備(高可用)方面。因此,實際大多負載均衡的工作還是在Web層。

在這裡插入圖片描述

高性能集群:將單個重負載的請求分散到多個節點進行處理,最後再將處理結果進行匯總;
高可用集群:提高冗餘單元,避免單點故障;
負載均衡集群:將大量的並發請求分擔到多個處理節點。由於單個處理節點的故障不影響整個服務,負載均衡集群同時也實現瞭高可用性。

Web層負載均衡

任何的負載均衡技術都要想辦法建立某種一對多的映射機制:一個請求的入口映射到多個處理請求的節點,從而實現分而治之(Divide and Conquer)。

1、【協議層】http重定向

當http代理(比如瀏覽器)向web服務器請求某個URL後,web服務器可以通過http響應頭信息中的Location標記來返回一個新的URL。這意味著HTTP代理需要繼續請求這個新的URL,完成自動跳轉。

在這裡插入圖片描述

這種負載均衡方案的有點是比較簡單,缺點是瀏覽器需要兩次請求服務器才能完成一次訪問,性能較差;同時,重定向服務器本身的處理能力有可能成為瓶頸,整個集群的伸縮性規模有限;使用HTTP返回碼302重定向,有可能使搜索引擎判斷為SEO作弊,降低搜索排名。因此實踐中很少使用這種負載均衡方案來部署。

2、【協議層】DNS輪詢

DNS負責提供域名解析服務,當訪問某個站點時,實際上首先需要通過該站點域名的DNS服務器來獲取域名指向的IP地址,在這一過程中,DNS服務器完成瞭域名到IP地址的映射,同樣,這樣映射也可以是一對多的,這時候,DNS服務器便充當瞭負載均衡調度器,它就像http重定向轉換策略一樣,將用戶的請求分散到多臺服務器上,但是它倆的實現機制完全不同。

相比http重定向,基於DNS的負載均衡完全節省瞭所謂的主站點,或者說DNS服務器已經充當瞭主站點的職能。但不同的是,作為調度器,DNS服務器本身的性能幾乎不用擔心。因為DNS記錄可以被用戶瀏覽器或者互聯網接入服務商的各級DNS服務器緩存,隻有當緩存過期後才會重新向域名的DNS服務器請求解析。也說是DNS不存在http的吞吐率限制,理論上可以無限增加實際服務器的數量。

優勢:

1.可以根據用戶IP來進行智能解析。DNS服務器可以在所有可用的A記錄中尋找離用記最近的一臺服務器。

2.動態DNS:在每次IP地址變更時,及時更新DNS服務器。當然,因為緩存,一定的延遲不可避免。

不足:

1.沒有用戶能直接看到DNS解析到瞭哪一臺實際服務器,對運維人員的調試帶來瞭不便。

2.策略的局限性。例如你無法將HTTP請求的上下文引入到調度策略中,而在前面介紹的基於HTTP重定向的負載均衡系統中,調度器工作在HTTP層面,它可以充分理解HTTP請求後根據站點的應用邏輯來設計調度策略,比如根據請求不同的URL來進行合理的過濾和轉移。

3、【協議層】CDN

CDN(Content Delivery Network,內容分發網絡)。通過發佈機制將內容同步到大量的緩存節點,並在DNS服務器上進行擴展,找到裡用戶最近的緩存節點作為服務提供節點。

因為很難自建大量的緩存節點,所以通常使用CDN運營商的服務。目前國內的服務商很少,而且按流量計費,價格昂貴。

4、【協議層】反向代理負載均衡

在客戶端明確的前提下,大量訪問請求(QPS)湧入。我們後臺通過Nginx代理瞭20個服務器,高QPS打進來後先打到Nginx中,通過Nginx的負載均衡來把請求分發給這20臺服務器,減輕瞭單臺服務器負擔。這種客戶端 → Nginx → 服務器 的模式稱為反向代理,如下圖:

在這裡插入圖片描述

N個客戶端給服務器發送的請求,Nginx服務器接收到之後,按照一定的規則均衡分發給瞭後端的業務處理服務器進行處理瞭。此時,請求的客戶端是明確的,但是請求具體由哪臺服務器處理的並不明確瞭,Nginx扮演的就是一個反向代理角色。

1.客戶端是無感知代理的存在的,反向代理對外都是透明的,訪問者並不知道自己訪問的是一個代理。因為客戶端不需要任何配置就可以訪問。

2.反向代理,它代理的是服務端,代服務端接收請求,主要用於服務器集群分佈式部署的情況下,反向代理隱藏瞭服務器的信息。

反向代理的作用:

(1)保證內網的安全,通常將反向代理服務器配置為公網訪問地址,代理的Web服務器是內網IP。

(2)負載均衡,通過反向代理服務器來優化每個單機服務實例的負載。

5、【網絡層】IP負載均衡

在這裡插入圖片描述

1.客戶端會向一個ip地址發出請求,這個ip地址是一個VIP(虛擬IP),這也是調度器向外公佈的一個地址。

2.請求達到調度器,調度器會根據負載均衡算法從RealServer列表中選取一個負載不高的服務器,然後把請求報文的目標地址,也就是VIP和端口通過iptables進行NAT轉換成選中的服務器的真實ip地址。最後,調度器會把其連接保存在一個hash表中,隻要這個連接下次再發請求報文過來就會把其分發到上次選定的服務器中。

3.RealServer收到報文之後,會把響應返回給調度器。

4.調度器收到報文之後,會把源地址和源端口改為虛擬ip和端口,最後再返回給客戶端。

特點

  • RealServer和調度器必須位於一個ip網絡之中。
  • 調度器位於RealServer和客戶端之間,處理進出的通信。
  • RIP通常是內部地址,僅用於集群之間通信。
  • RealServer的網關必須指向調度器。
  • 支持端口映射,RealServer沒必要跟調度器一個端口。

限制

響應報文一般比較大,每一次都需要NAT轉換的話,大流量的時候,會導致調度器成為一個瓶頸。

面試題3:你平時是怎樣定位線上問題的?

本題回答摘自朱曄的《Java業務開發常見錯誤100例》

定位問題,首先要定位問題出在哪個層次上。比如,是 Java 應用程序自身的問題還是外部因素導致的問題。我們可以先查看程序是否有異常,異常信息一般比較具體,可以馬上定位到大概的問題方向;如果是一些資源消耗型的問題可能不會有異常,我們可以通過指標監控配合顯性問題點來定位。

一般情況下,程序的問題來自以下三個方面。

第一,程序發佈後的 Bug,回滾後可以立即解決。這類問題的排查,可以回滾後再慢慢分析版本差異。

第二,外部因素,比如主機、中間件或數據庫的問題。這類問題的排查方式,按照主機層面的問題、中間件或存儲(統稱組件)的問題分為兩類。

主機層面的問題,可以使用工具排查:

  • CPU 相關問題:可以使用 top、vmstat、pidstat、ps 等工具排查;
  • 內存相關問題:可以使用 free、top、ps、vmstat、cachestat、sar 等工具排查;
  • IO 相關問題:可以使用 lsof、iostat、pidstat、sar、iotop、df、du 等工具排查;
  • 網絡相關問題:可以使用 ifconfig、ip、nslookup、dig、ping、tcpdump、iptables 等工具排查。

組件的問題,可以從以下幾個方面排查:

  • 排查組件所在主機是否有問題;
  • 排查組件進程基本情況,觀察各種監控指標;
  • 查看組件的日志輸出,特別是錯誤日志;
  • 進入組件控制臺,使用一些命令查看其運作情況。

第三,因為系統資源不夠造成系統假死的問題,通常需要先通過重啟和擴容解決問題,之後再進行分析,不過最好能留一個節點作為現場。系統資源不夠,一般體現在 CPU 使用高、內存泄漏或 OOM 的問題、IO 問題、網絡相關問題這四個方面。

對於 CPU 使用高的問題,如果現場還在,具體的分析流程是:

  • 首先,在 Linux 服務器上運行top -Hp pid命令,來查看進程中哪個線程 CPU 使用高;
  • 然後,輸入大寫的 P 將線程按照 CPU 使用率排序,並把明顯占用 CPU 的線程 ID 轉換為 16 進制;
  • 最後,在 jstack 命令輸出的線程棧中搜索這個線程 ID,定位出問題的線程當時的調用棧。

如果沒有條件直接在服務器上運行 top 命令的話,我們可以用采樣的方式定位問題:間隔固定秒數(比如 10 秒)運行一次 jstack 命令,采樣幾次後,對比采樣得出哪些線程始終處於運行狀態,分析出問題的線程。

如果現場沒有瞭,我們可以通過排除法來分析。CPU 使用高,一般是由下面的因素引起的:

  • 突發壓力。這類問題,我們可以通過應用之前的負載均衡的流量或日志量來確認,諸如 Nginx 等反向代理都會記錄 URL,可以依靠代理的 Access Log 進行細化定位,也可以通過監控觀察 JVM 線程數的情況。壓力問題導致 CPU 使用高的情況下,如果程序的各資源使用沒有明顯不正常,之後可以通過壓測 +Profiler(jvisualvm 就有這個功能)進一步定位熱點方法;如果資源使用不正常,比如產生瞭幾千個線程,就需要考慮調參。
  • GC。這種情況,我們可以通過 JVM 監控 GC 相關指標、GC Log 進行確認。如果確認是 GC 的壓力,那麼內存使用也很可能會不正常,需要按照內存問題分析流程做進一步分析。
  • 程序中死循環邏輯或不正常的處理流程。這類問題,我們可以結合應用日志分析。一般情況下,應用執行過程中都會產生一些日志,可以重點關註日志量異常部分。

對於內存泄露或 OOM 的問題,最簡單的分析方式,就是堆轉儲後使用MAT分析。堆轉儲,包含瞭堆現場全貌和線程棧信息,一般觀察支配樹圖、直方圖就可以馬上看到占用大量內存的對象,可以快速定位到內存相關問題。

需要註意的是,Java 進程對內存的使用不僅僅是堆區,還包括線程使用的內存(線程個數 * 每一個線程的線程棧)和元數據區。每一個內存區都可能產生 OOM,可以結合監控觀察線程數、已加載類數量等指標分析。另外,我們需要註意看一下,JVM 參數的設置是否有明顯不合理的地方,限制瞭資源使用。

問題來瞭,JVM 哪塊內存區域不會發生內存溢出?

程序計數器:程序計數器是一塊內存較小的區域,它用於存儲線程的每個執行指令,每個線程都有自己的程序計數器,此區域不會有內存溢出的情況。

IO 相關的問題,除非是代碼問題引起的資源不釋放等問題,否則通常都不是由 Java 進程內部因素引發的。網絡相關的問題,一般也是由外部因素引起的。

對於連通性問題,結合異常信息通常比較容易定位;對於性能或瞬斷問題,可以先嘗試使用 ping 等工具簡單判斷,如果不行再使用 tcpdump 或 Wireshark 來分析。

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: