程式設計師如何優雅地解決線上問題?

身為一個程式設計師,遇到線上問題那都是家常便飯的事兒。

如果你在深夜看到一群同事圍在一起,他們是在共同探討什麼哲學問題么?非也,他們一定是遇到了線上BUG。

線上問題只要影響到了核心業務流程那便是事故,所以一旦事故發生,無論你在約會,還是周末打遊戲,甚至是在睡覺,只要接到了來自公司的電話,那隻能趕緊連上公司網路加班了。

image-20220731171413404

 

BUG分類

線上問題是複雜多變的,我們一般將bug分為系統級別和業務級別bug。

系統級別bug

業務部署在整套系統上運行,一旦出現系統級別bug則業務會被嚴重拖垮。如CPU爆滿、服務不可用、甚至伺服器宕機等都屬於系統級別的bug。

如果是CPU100%,那是由哪個執行緒,哪個類,甚至是哪個方法導致的?

若是業務流程正常但是部分服務性能拉跨,那麼如何快速定位到問題在哪兒?

因為是線上發生的事兒,所以重點在於如何迅速解決

以下分享我最常用的一些問題排查工具。

 

linux定位工具

1.CPU高負載,甚至100%?

perf工具

perf是linux的性能分析工具,核心作用之一就是用來查看熱點函數的分布情況。

用它可以生成火焰圖查看到函數的資源佔用情況,函數的調用棧越深火焰就越高。所以對於異常的函數一眼就能看出。

image-20220729174819116

如上圖通過調用棧你可以看出Monitor管程在反覆調用enter和wait,這種情況下就可以判斷出該程式已經發生死鎖且存在性能問題。假設有大量執行緒請求這段程式碼,那麼CPU資源將被迅速打滿!

在著名的「713B站事故」里技術團隊在事故發生時就用到了當前工具生成了火焰圖,快速地分析出了事故的根因也就是導致CPU100%的lua熱點函數。

 

2.某一進程存在異常嫌疑,想快速知道它的狀態?

ps命令

我們項目部署的伺服器里在跑的進程老多了,java進程、nginx進程、redis、消息隊列進程等等。

舉個例子,假設在某一流量高峰期系統監控到整個服務性能下降5倍,業務被嚴重拖垮,在確定沒有業務層面bug的情況下大概率就是因為服務性能達到瓶頸了。如何確定瓶頸在哪兒?

大部分情況下通過系統告警就可以知道大概問題所在。如發生消息堆積我們就該懷疑消息生產者和消費者的狀態,這個時候就要具體去查看消息隊列這一進程。

可以使用一些輕量級的linux命令,如ps

[root@linuxfancy ~]# ps -ef | grep queuejob
root       1303      1  0 Apr17 ?        00:00:00 /usr/sbin/queuejob
root       3260   3087  0 Apr17 ?        00:00:00 /usr/bin/queuejob /bin/sh -c exec -l /bin/bash -c "env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic"
root      24174  19508  0 11:39 pts/0    00:00:00 grep --color=auto ssh
[root@linux265 ~]# ps -aux | grep queueA
root       1303  0.0  0.0  82468  1204 ?        Ss   Apr17   0:00 /usr/sbin/queueA
root       3260  0.0  0.0  52864   572 ?        Ss   Apr17   0:00 /usr/bin/queueA /bin/sh -c exec -l
root      24188  0.0  0.0 112652   956 pts/0    S+   11:39   0:00 grep --color=auto ssh

該命令還可以用於對進程的資源使用情況進行排序:

[root@linuxfancy ~]# ps aux | sort -nk 3
[root@linuxfancy ~]# ps aux | sort -rnk 4 

 

3.我想知道記憶體&磁碟的使用情況?

vmstat命令

vmstat是Virtual Meomory Statistics(虛擬記憶體統計)的縮寫。

它是一個用於監控記憶體和磁碟使用情況的工具,但是也可以用來查看CPU的一些指標,如中斷次數等。使用它可以查看記憶體使用的詳細資訊和磁碟的讀/寫情況。

image-20220731155906985

以上表頭欄位的說明如下:

Procs(進程):

r: 運行隊列中進程數量

b: 等待IO的進程數量

Memory(記憶體):

swpd: 使用虛擬記憶體大小

free: 可用記憶體大小

buff: 用作緩衝的記憶體大小

cache: 用作快取的記憶體大小

Swap(交換):

si: 每秒從交換區寫到記憶體的大小

so: 每秒寫入交換區的記憶體大小IO:(現在的Linux版本塊的大小為1024bytes)bi: 每秒讀取的塊數bo: 每秒寫入的塊數

System(系統):

in: 每秒中斷數,包括時鐘中斷

cs: 每秒上下文切換數

CPU(以百分比表示)

us: 用戶進程執行時間(user time)

sy: 系統進程執行時間(system time)

id: 空閑時間(包括IO等待時間),中央處理器的空閑時間

wa: IO等待時間

從以上命令就可以很清晰地看出伺服器的各方面性能情況。除此之外還有以下命令也可以在排查或者調優中使用:

image-20220731155528178

 

業務級別bug

如何定位到業務bug?

出現了業務bug那就純純的是開發或測試的鍋了。

bug確定後第一步一定是先看日誌,只要你寫需求的時候日誌打的全,一般出現了問題日誌或者告警都會第一時間推送。

通過日誌我們可以定位到bug對應程式碼的位置,但這僅僅是第一步,因為你只知道哪裡出了問題,並不知道程式碼出了什麼問題(除非一眼就能看出)。

所以下一步,看數據,數據是業務應用的核心。若通過日誌和頁面表現查看到你的主流程是沒有問題的,那麼下一步就是要確定表的數據是否有問題,數據存在bug的表現會是各方面的,可能是用戶回饋,也可能是流程錯誤,這要取決於你表的設計。

切記!!線上數據是重中之重,當你決定要修複數據,在處理之前一定要做好備份,這樣起碼可以保證事情不會變的更糟。一般情況下修改線上數據這種活都需要你寫好SQL,然後經過leader審批再交給DBA來操作,一定不要干出刪庫跑路這種事喲。

假設驗證了你數據是OK的,那麼問題就極大可能出現在了程式碼層面

當代程式設計師最難過的瞬間無非就是有一個非常緊急的線上bug需要你來解決,但是擺在你面前的卻是一堆屎山程式碼!!

修改業務bug最重要的是要將bug點修改掉並且保證其它業務還能正常運行,這是牽一髮而動全身的事情,否則bug只會越改越多。

所以平時應該預知到這些風險,做好程式碼設計。總結一下定位業務bug的正確步驟:

image-20220731163701459

 

方案設計

程式碼設計

一般公司都有自己的程式碼設計規範。比如由外到里包裝程式碼,每一個方法都要有對應的職責,並且一個方法不要超過100行,一個類不要超過1000行程式碼等。清晰的結構可以讓你和他人更好地review程式碼,避免看起來一頭霧水。

一般寫業務邏輯有兩種方式,一種就是簡潔明了的線性邏輯,另一種就是通過封裝程式碼來減少程式碼耦合提高內聚性,也就是我們說的設計模式的使用。兩種方式各有優缺點,但是工作多年了咱寫的程式碼也不能直里直氣的,多少得帶點」藝術「對吧?推薦一下我經常使用但是也不會特別複雜的設計模式。

設計模式

工廠模式

這是最常使用的設計模式之一。

工廠模式分為簡單工廠模式、工廠方法模式和抽象工廠模式。我們這裡講解簡單工廠模式,因為後兩個都是以其為基礎做改進的。

其結構如下:

通過定義一個用以創建對象的介面, 讓子類決定實例化哪個類。

所以其實質就是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類(這些產品類繼承自一個父類或介面)的實例。

其包含以下角色:

  • 工廠(Creator)角色: 工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。

  • 抽象產品(Product)角色:它負責描述所有實例所共有的公共介面。

  • 具體產品(Concrete Product)角色:創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。

image-20220731152210768

 

當遇到需要根據某個前提條件創建不同的類實現時, 可以使用工廠模式。

 

裝飾者模式

它是在不必改變原類結構和繼承體系的情況下,動態地擴展一個對象的功能。通過創建一個包裝對象來實現對功能的擴展,動態的給一個對象添加一些額外的職責。

所以裝飾者模式分為主體和裝飾者。

其包含角色如下:

  • 主體(Main):業務主體邏輯、欄位等。

  • 主體具體實現類(MainComponent):主體具體的實現類。

  • 裝飾者(Decorator):要做的裝飾擴展邏輯介面。

  • 裝飾者具體實現類(DecoratorComponent):擴展邏輯的具體實現類。

image-20220731154251894

以上兩種設計模式都有著」高擴展性「的特點,我們應該根據業務靈活設計介面,避免需求迭代導致的一坨坨又臭又長的程式碼。但是設計模式切勿用來炫技,一些較為冷門或者複雜的設計模式不推薦使用,否則當一套程式碼只有你能維護時,那將會是非常痛苦的。。

image-20220731171435913

當然了這也能夠體現出你在公司的不可替代性!

 

架構設計

系統高性能 & 高可用

  • 使用快取:快取的作用是為了系統的讀能力。將用戶經常訪問的數據扔到快取裡面可以有效地提高訪問速度並且減少資料庫的壓力。

image-20220731135840288

  • 服務降級 & 限流:若短時間內流量激增影響到伺服器性能,可考慮降級邊緣業務以保證核心業務的可用性和性能。

image-20220731135954949

  • 分散式系統 & 服務拆分:將整個系統拆分成不同的業務模組再部署到對應的伺服器中,服務之間通過中間件通訊,可以有效地避免

和減少單一服務故障對整體系統的影響。

image-20220731140137660

 

  • 高可用架構:重要性不言而喻。同城多活、異地多活的架構部署可以保證單機房掛掉的情況下流量可以迅速切換到其他機房讓核心業務不受影響。可謂是防止系統宕機必備良藥啊!

image-20220731142619051

做好事故復盤

都說小事故傷身,大事故提桶。。一般發生事故後寫一張事故單是不可避免的。除了詳細描述好事故發生的經過,背鍋人,解決方案,後續的事故跟進也是一系列流程的事,多則需要數周去跟進。其實事故的發生對於團隊的技術發展和成型往往起著積極推進作用,所以對於每一個團隊來說事故一定是不可避免的。每次事故發生我們都要思考如何完善系統,打破技術壁壘。並且遇到事兒也不要慌,如果是大問題,那麼首先背鍋的一定是leader!

其實呢一般公司最喜歡的是能快速解決問題的員工,即便這些問題可能是由你創造的。