日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为你总结了N个真实线上故障,从容应对面试官!

發布時間:2023/12/10 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为你总结了N个真实线上故障,从容应对面试官! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

很多人在面試時,會被問到這樣的問題:遇到過什么系統故障?怎么解決的?下面是筆者根據自己15年互聯網研發經歷總結的多個線上故障真實案例。相信可以幫你從容應對面試官的提問!

本文圖不多,但內容很干!理解為主,面試為輔,學以致用!

故障一:JVM頻繁FULL GC快速排查


在分享此案例前,先聊聊哪些場景會導致頻繁Full GC:

  • 內存泄漏(代碼有問題,對象引用沒及時釋放,導致對象不能及時回收)

  • 死循環

  • 大對象

  • 尤其是大對象,80%以上的情況就是他。

    ? ??

    那么大對象從哪里來的呢?

  • 數據庫(包括Mysql和Mongodb等NOSql數據庫),結果集太大

  • 第三方接口傳輸的大對象

  • 消息隊列,消息太大

  • 根據多年一線互聯網經驗,絕大部分情況是數據庫大結果集導致。

    好,現在我們開始介紹這次線上故障:

    在沒有任何發布的情況下,POP服務(接入第三方商家的服務)突然開始瘋狂Full GC,觀察堆內存監控沒內存泄漏,回滾到前一版本,問題仍然存在,尷尬了!!!

    按照常規做法,一般先用jmap導出堆內存快照(jmap -dump:format=b,file=文件名 [pid]),然后用mat等工具分析出什么對象占用了大量空間,再查看相關引用找到問題代碼。這種方式定位問題周期會比較長,如果是關鍵服務,長時間不能定位解決問題,影響太大。

    下面來看看我們的做法。先按照常規做法分析堆內存快照,與此同時另外的同學去查看數據庫服務器網絡IO監控,如果數據庫服務器網絡IO有明顯上升,并且時間點吻合,基本可以確定是數據庫大結果集導致了Full GC,趕緊找DBA快速定位大SQL(對DBA來說很簡單,分分鐘搞定,如果DBA不知道怎么定位,那他要被開除了,哈哈),定位到SQL后再定位代碼就非常簡單了。按照這種辦法,我們很快定位了問題。原來是一個接口必傳的參數沒傳進來,也沒加校驗,導致SQL語句where后面少了兩個條件,一次查幾萬條記錄出來,真坑啊!這種方法是不是要快很多,哈哈,5分鐘搞定。

    當時的DAO層是基于Mybatis實現的,出問題的SQL語句如下:

    <select?id="selectOrders"?resultType="com.***.Order"?>select * from user where 1=1 <if?test="?orderID?!=?null?"> and?order_id?=?#{orderID} </if ><if?test="userID?!=null"> and?user_id=#{userID} </if ><if?test="startTime?!=null"> and?create_time >= #{createTime} </if ><if test="endTime !=null"> and create_time <= #{userID} </if ></select>

    上面SQL語句意思是根據orderID查一個訂單,或者根據userID查一個用戶所有的訂單,兩個參數至少要傳一個。但是兩個參數都沒傳,只傳了startTime和endTime。所以一次Select就查出了幾萬條記錄。

    所以我們在使用Mybatis的時候一定要慎用if test,一不小心就會帶來災難。后來我們將上面的SQL拆成了兩個:

    根據訂單ID查詢訂單:

    <select?id="selectOrderByID"?resultType="com.***.Order"?>select * from user whereorder_id?=?#{orderID}</select>

    根據userID查詢訂單:

    <select?id="selectOrdersByUserID"?resultType="com.***.Order"?>select * from user where user_id=#{userID}<if?test="startTime?!=null"> and?create_time >= #{createTime} </if ><if test="endTime !=null"> and create_time <= #{userID} </if ></select>

    故障二:內存泄漏


    介紹案例前,先了解一下內存泄漏和內存溢出的區別。

    內存溢出:程序沒有足夠的內存使用時,就會發生內存溢出。內存溢出后程序基本上就無法正常運行了。

    內存泄漏:當程序不能及時釋放內存,導致占用內存逐漸增加,就是內存泄漏。內存泄漏一般不會導致程序無法運行。不過持續的內存泄漏,累積到內存上限時,就會發生內存溢出。在Java中,如果發生內存泄漏,會導致GC回收不徹底,每次GC后,堆內存使用率逐漸增高。下圖是JVM發生內存泄漏的監控圖,我們可以看到每次GC后堆內存使用率都比以前提高了。

    圖片來源于網絡

    當時內存泄漏的場景是,用本地緩存(公司基礎架構組自己研發的框架)存放了商品數據,商品數量不算太多,幾十萬的樣子。如果只存熱點商品,內存占用不會太大,但是如果存放全量商品,內存就不夠了。初期我們給每個緩存記錄都加了7天的過期時間,這樣就可以保證緩存中絕大部分都是熱點商品。不過后來本地緩存框架經過一次重構,過期時間被去掉了。沒有了過期時間,日積月累本地緩存越來越大,很多冷數據也被加載到了緩存。直到有一天接到告警短信,提示堆內存過高。趕緊通過jmap(jmap -dump:format=b,file=文件名 [pid]?)下載了堆內存快照,然后用eclipse的mat工具分析快照,發現了本地緩存中有大量的商品記錄。定位問題后趕緊讓架構組加上了過期時間,然后逐個節點重啟了服務。

    虧了我們加了服務器內存和JVM堆內存監控,及時發現了內存泄漏的問題。否則隨著泄漏問題日積月累,如果哪天真的OOM就慘了。所以技術團隊除了做好CPU,內存等運維監控,JVM監控也非常重要。

    故障三:冪等問題


    很多年前,筆者在一家大型電商公司做Java程序員,當時開發了積分服務。當時的業務邏輯是,用戶訂單完結后,訂單系統發送消息到消息隊列,積分服務接到消息后給用戶積分,在用戶現有的積分上加上新產生的積分。

    由于網絡等原因會有消息重復發送的情況,這樣也就導致了消息的重復消費。當時筆者還是個初入職場的小菜鳥,并沒有考慮到這種情況。所以上線后偶爾會出現重復積分的情況,也就是一個訂單完結后會給用戶加兩次或多次積分。

    后來我們加了一個積分記錄表,每次消費消息給用戶增加積分前,先根據訂單號查一遍積分記錄表,如果沒有積分記錄才給用戶增加積分。這也就是所謂的“冪等性”,即多次重復操作不影響最終的結果。實際開發中很多需要重試或重復消費的場景都要實現冪等,以保證結果的正確性。例如,為了避免重復支付,支付接口也要實現冪等。

    故障四:緩存雪崩


    我們經常會遇到需要初始化緩存的情況。比如,我們曾經經歷過用戶系統重構,用戶系統表結構發生了變化,緩存信息也要變。重構完成后上線前,需要初始化緩存,將用戶信息批量存入Reids。每條用戶信息緩存記錄過期時間是1天,記錄過期后再從數據庫查詢最新的數據并拉取到Redis中。灰度上線時一切正常,所以很快就全量發布了。整個上線過程非常順利,碼農們也很開心。

    不過,第二天,災難發生了!到某一個時間點,各種報警紛至沓來。用戶系統響應突然變得非常慢,甚至一度沒有任何響應。查看監控,用戶服務CPU突然飆高(IO wait很高),Mysql訪問量激增,Mysql服務器壓力也隨之暴增,Reids緩存命中率也跌到了極點。依賴于我們強大的監控系統(運維監控,數據庫監控,APM全鏈路性能監控),很快定位了問題。原因就是Reids中大量用戶記錄集中失效,獲取用戶信息的請求在Redis中查不到用戶記錄,導致大量的請求穿透到數據庫,瞬間給數據庫帶來巨大壓力。同時用戶服務和相關聯的其他服務也都受到了影響。

    這種緩存集中失效,導致大量請求同時穿透到數據庫的情況,就是所謂的“緩存雪崩”。如果沒到緩存失效時間點,性能測試也測不出問題。所以一定要引起大家注意。

    所以,需要初始化緩存數據時,一定要保證每個緩存記錄過期時間的離散性。例如,我們給這些用戶信息設置過期時間,可以采用一個較大的固定值加上一個較小的隨機值。比如過期時間可以是:24小時 + 0到3600秒的隨機值。

    故障五:磁盤IO導致線程阻塞


    問題發生在2017年下半年,有一段時間地理網格服務時不常的會響應變慢,每次持續幾秒鐘到幾十秒鐘就自動恢復。

    如果響應變慢是持續的還好辦,直接用jstack抓線程堆棧,基本可以很快定位問題。關鍵持續時間只有最多幾十秒鐘,而且是偶發的,一天只發生一兩次,有時幾天才發生一次,發生時間點也不確定,人盯著然后用jstack手工抓線程堆棧顯然不現實。

    好吧,既然手工的辦法不現實,咱們就來自動的,寫一個shell腳本自動定時執行jstack,5秒執行一次jstack,每次執行結果放到不同日志文件中,只保存20000個日志文件。

    Shell腳本如下:

    #!/bin/bash num=0 log="/tmp/jstack_thread_log/thread_info"cd /tmp if [ ! -d "jstack_thread_log" ]; thenmkdir jstack_thread_log fiwhile?((num?<=?10000));doID=`ps -ef | grep java | grep gaea | grep -v "grep" | awk '{print $2}'`if [ -n "$ID" ]; thenjstack $ID >> ${log} finum=$(( $num + 1 ))mod=$(( $num%100 ))if [ $mod -eq 0 ]; thenback=$log$nummv $log $backfisleep?5done

    下一次響應變慢的時候,我們找到對應時間點的jstack日志文件,發現里面有很多線程阻塞在logback輸出日志的過程,后來我們精簡了log,并且把log輸出改成異步,問題解決了,這個腳本果真好用!建議大家保留,以后遇到類似問題時,可以拿來用!

    故障六:數據庫死鎖問題


    在分析案例之前,我們先了解一下MySQL INNODB。在MySQL?INNODB引擎中主鍵是采用聚簇索引的形式,即在B樹的葉子節點中既存儲了索引值也存儲了數據記錄,即數據記錄和主鍵索引是存在一起的。而普通索引的葉子節點存儲的只是主鍵索引的值,一次查詢找到普通索引的葉子節點后,還要根據葉子節點中的主鍵索引去找到聚簇索引葉子節點并拿到其中的具體數據記錄,這個過程也叫“回表”。

    故障發生的場景是關于我們商城的訂單系統。有一個定時任務,每一小時跑一次,每次把所有一小時前未支付訂單取消掉。而客服后臺也可以批量取消訂單。

    訂單表t_order結構大至如下:

    id
    訂單id,主鍵
    status
    訂單狀態
    created_time
    訂單創建時間

    id是表的主鍵,created_time字段上是普通索引。

    聚簇索引(主鍵id)

    id(索引)
    statuscreated_time
    1
    UNPAID
    2020-01-01?07:30:00
    2UNPAID2020-01-01?08:33:00
    3
    UNPAID2020-01-01?09:30:00
    4
    UNPAID2020-01-01?09:39:00
    5
    UNPAID2020-01-01?09:50:00

    普通索引(created_time字段)

    created_time(索引)
    id(主鍵)
    2020-01-01?09:50:00
    5
    2020-01-01?09:39:004
    2020-01-01?09:30:003
    2020-01-01?08:33:00
    2
    2020-01-01?07:30:00
    1

    定時任務每一小時跑一次,每次把所有一小時前兩小時內的未支付訂單取消掉,比如上午11點會取消8點到10點的未支付訂單。SQL語句如下:

    update t_order set status = 'CANCELLED' where created_time > '2020-01-01 08:00:00' and created_time < '2020-01-01 10:00:00' and status = 'UNPAID'

    客服批量取消訂單SQL如下:

    update t_order set status = 'CANCELLED' where id in (2, 3, 5) and status = 'UNPAID'

    上面的兩條語句同時執行就可能發生死鎖。我們來分析一下原因。

    第一條定時任務的SQL,會先找到created_time普通索引并加鎖,然后再在找到主鍵索引并加鎖。

    ????第一步,created_time普通索引加鎖

    ????第二步,主鍵索引加鎖

    第二條客服批量取消訂單SQL,直接走主鍵索引,直接在主鍵索引上加鎖。

    我們可以看到,定時任務SQL對主鍵加鎖順序是5,4,3,2。客服批量取消訂單SQL對主鍵加鎖順序是2,3,5。當第一個SQL對3加鎖后,正準備對2加鎖時,發現2已經被第二個SQL加鎖了,所以第一個SQL要等待2的鎖釋放。而此時第二個SQL準備對3加鎖,卻發現3已經被第一個SQL加鎖了,就要等待3的鎖釋放。兩個SQL互相等待對方的鎖,也就發生了“死鎖”。

    解決辦法就是從SQL語句上保證加鎖順序一致。或者把客服批量取消訂單SQL改成每次SQL操作只能取消一個訂單,然后在程序里多次循環執行SQL,如果批量操作的訂單數量不多,這種笨辦法也是可行的。

    故障七:域名劫持


    先看看DNS解析是怎么回事,當我們訪問www.baidu.com時,首先會根據www.baidu.com到DNS域名解析服務器去查詢百度服務器對應的IP地址,然后再通過http協議訪問該IP地址對應的網站。而DNS劫持是互聯網攻擊的一種方式,通過攻擊域名解析服務器(DNS)或者偽造域名解析服務器,把目標網站域名解析到其他的IP。從而導致請求無法訪問目標網站或者跳轉到其他網站。如下圖:

    下面這張圖是我們曾經經歷過的DNS劫持的案例。

    看圖中的紅框部分,本來上方的圖片應該是商品圖片,但是卻顯示成了廣告圖片。是不是圖片配錯了?不是,是域名(DNS)被劫持了。原本應該顯示存儲在CDN上的商品圖片,但是被劫持之后卻顯示了其他網站的廣告鏈接圖片。由于當時的CDN圖片鏈接采用了不安全的http協議,所以很容易被劫持。后來改成了https,問題就解決了。

    當然域名劫持有很多方式,https也不能規避所有問題。所以,除了一些安全防護措施,很多公司都有自己的備用域名,一旦發生域名劫持可以隨時切換到備用域名。

    故障八:帶寬資源耗盡


    帶寬資源耗盡導致系統無法訪問的情況,雖然不多見,但是也應該引起大家的注意。來看看,之前遇到的一起事故。

    場景是這樣的。社交電商每個分享出去的商品圖片都有一個唯一的二維碼,用來區分商品和分享者。所以二維碼要用程序生成,最初我們在服務端用Java生成二維碼。前期由于系統訪問量不大,系統一直沒什么問題。但是有一天運營突然搞了一次優惠力度空前的大促,系統瞬時訪問量翻了幾十倍。問題也就隨之而來了,網絡帶寬直接被打滿,由于帶寬資源被耗盡,導致很多頁面請求響應很慢甚至沒任何響應。原因就是二維碼生成數量瞬間也翻了幾十倍,每個二維碼都是一張圖片,對帶寬帶來了巨大壓力。

    怎么解決呢?如果服務端處理不了,就考慮一下客戶端。把生成二維碼放到客戶端APP處理,充分利用用戶終端手機,目前Andriod,IOS或者React都有相關生成二維碼的SDK。這樣不但解決了帶寬問題,而且也釋放了服務端生成二維碼時消耗的CPU資源(生成二維碼過程需要一定的計算量,CPU消耗比較明顯)。

    外網帶寬非常昂貴,我們還是要省著點用啊!

    本文分享的案例都是筆者的親身經歷,希望對各位讀者有所幫助。

    原創不易,如果感覺本文對您有幫助,有勞點一下“在看”!讓更多人收獲知識!

    作者簡介:曾任職于阿里巴巴,每日優鮮等互聯網公司,任技術總監,15年電商互聯網經歷。

    IT技術分享社區

    文章推薦程序員效率:畫流程圖常用的工具程序員效率:整理常用的在線筆記軟件遠程辦公:常用的遠程協助軟件,你都知道嗎?51單片機程序下載、ISP及串口基礎知識硬件:斷路器、接觸器、繼電器基礎知識

    總結

    以上是生活随笔為你收集整理的为你总结了N个真实线上故障,从容应对面试官!的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。