程序員如何優雅地解決線上問題?
身為一個程序員,遇到線上問題那都是家常便飯的事兒。
如果你在深夜看到一群同事圍在一起,他們是在共同探討什么哲學問題么?非也,他們一定是遇到了線上BUG。
線上問題只要影響到了核心業務流程那便是事故,所以一旦事故發生,無論你在約會,還是周末打游戲,甚至是在睡覺,只要接到了來自公司的電話,那只能趕緊連上公司網絡加班了。
線上問題是復雜多變的,我們一般將bug分為系統級別和業務級別bug。
一、系統級別bug
業務部署在整套系統上運行,一旦出現系統級別bug則業務會被嚴重拖垮。如CPU爆滿、服務不可用、甚至服務器宕機等都屬于系統級別的bug。
如果是CPU100%,那是由哪個線程,哪個類,甚至是哪個方法導致的?
若是業務流程正常但是部分服務性能拉跨,那么如何快速定位到問題在哪兒?
因為是線上發生的事兒,所以重點在于如何迅速解決。
以下分享我最常用的一些問題排查工具。
linux定位工具
linux定位工具
perf是linux的性能分析工具,核心作用之一就是用來查看熱點函數的分布情況。
用它可以生成火焰圖查看到函數的資源占用情況,函數的調用棧越深火焰就越高。所以對于異常的函數一眼就能看出。
如上圖通過調用棧你可以看出Monitor管程在反復調用enter和wait,這種情況下就可以判斷出該程序已經發生死鎖且存在性能問題。假設有大量線程請求這段代碼,那么CPU資源將被迅速打滿!
在著名的“713B站事故”里技術團隊在事故發生時就用到了當前工具生成了火焰圖,快速地分析出了事故的根因也就是導致CPU100%的lua熱點函數。
某一進程存在異常嫌疑,想快速知道它的狀態?
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
我想知道內存&磁盤的使用情況?
vmstat命令:
vmstat是Virtual Meomory Statistics(虛擬內存統計)的縮寫。
它是一個用于監控內存和磁盤使用情況的工具,但是也可以用來查看CPU的一些指標,如中斷次數等。使用它可以查看內存使用的詳細信息和磁盤的讀/寫情況。
以上表頭字段的說明如下:
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等待時間
從以上命令就可以很清晰地看出服務器的各方面性能情況。除此之外還有以下命令也可以在排查或者調優中使用:
二、業務級別bug
如何定位到業務bug?
出現了業務bug那就純純的是開發或測試的鍋了。
bug確定后第一步一定是先看日志,只要你寫需求的時候日志打的全,一般出現了問題日志或者告警都會第一時間推送。
通過日志我們可以定位到bug對應代碼的位置,但這僅僅是第一步,因為你只知道哪里出了問題,并不知道代碼出了什么問題(除非一眼就能看出)。
所以下一步,看數據,數據是業務應用的核心。若通過日志和頁面表現查看到你的主流程是沒有問題的,那么下一步就是要確定表的數據是否有問題,數據存在bug的表現會是各方面的,可能是用戶反饋,也可能是流程錯誤,這要取決于你表的設計。
切記!!線上數據是重中之重,當你決定要修復數據,在處理之前一定要做好備份,這樣起碼可以保證事情不會變的更糟。一般情況下修改線上數據這種活都需要你寫好SQL,然后經過leader審批再交給DBA來操作,一定不要干出刪庫跑路這種事喲。
假設驗證了你數據是OK的,那么問題就極大可能出現在了代碼層面。
當代程序員最難過的瞬間無非就是有一個非常緊急的線上bug需要你來解決,但是擺在你面前的卻是一堆屎山代碼!!
修改業務bug最重要的是要將bug點修改掉并且保證其它業務還能正常運行,這是牽一發而動全身的事情,否則bug只會越改越多。
所以平時應該預知到這些風險,做好代碼設計。總結一下定位業務bug的正確步驟:
三、代碼設計
一般公司都有自己的代碼設計規范。比如由外到里包裝代碼,每一個方法都要有對應的職責,并且一個方法不要超過100行,一個類不要超過1000行代碼等。清晰的結構可以讓你和他人更好地review代碼,避免看起來一頭霧水。
寫業務邏輯有兩種方式,一種就是簡潔明了的線性邏輯,另一種就是通過封裝代碼來減少代碼耦合提高內聚性,也就是我們說的設計模式的使用。兩種方式各有優缺點,但是工作多年了咱寫的代碼也不能直里直氣的,多少得帶點”藝術“對吧?推薦一下我經常使用但是也不會特別復雜的設計模式。
設計模式
工廠模式
這是最常使用的設計模式之一。
工廠模式分為簡單工廠模式、工廠方法模式和抽象工廠模式。我們這里講解簡單工廠模式,因為后兩個都是以其為基礎做改進的。
其結構如下:
通過定義一個用以創建對象的接口, 讓子類決定實例化哪個類。
所以其實質就是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類(這些產品類繼承自一個父類或接口)的實例。
其包含以下角色:
- 工廠(Creator)角色:工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。
- 抽象產品(Product)角色:它負責描述所有實例所共有的公共接口。
- 具體產品(Concrete Product)角色:創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。
當遇到需要根據某個前提條件創建不同的類實現時, 可以使用工廠模式。
裝飾者模式
它是在不必改變原類結構和繼承體系的情況下,動態地擴展一個對象的功能。通過創建一個包裝對象來實現對功能的擴展,動態的給一個對象添加一些額外的職責。
所以裝飾者模式分為主體和裝飾者。
其包含角色如下:
- 主體(Main):業務主體邏輯、字段等。
- 主體具體實現類(MainComponent):主體具體的實現類。
- 裝飾者(Decorator):要做的裝飾擴展邏輯接口。
- 裝飾者具體實現類(DecoratorComponent):擴展邏輯的具體實現類。
以上兩種設計模式都有著”高擴展性“的特點,我們應該根據業務靈活設計接口,避免需求迭代導致的一坨坨又臭又長的代碼。但是設計模式切勿用來炫技,一些較為冷門或者復雜的設計模式不推薦使用,否則當一套代碼只有你能維護時,那將會是非常痛苦的。。
當然了這也能夠體現出你在公司的不可替代性!
四、架構設計
系統高性能 & 高可用
- 使用緩存:緩存的作用是為了系統的讀能力。將用戶經常訪問的數據扔到緩存里面可以有效地提高訪問速度并且減少數據庫的壓力。
- 服務降級 & 限流:若短時間內流量激增影響到服務器性能,可考慮降級邊緣業務以保證核心業務的可用性和性能。
- ?分布式系統 & 服務拆分:將整個系統拆分成不同的業務模塊再部署到對應的服務器中,服務之間通過中間件通信,可以有效地避免
和減少單一服務故障對整體系統的影響。
- 高可用架構:重要性不言而喻。同城多活、異地多活的架構部署可以保證單機房掛掉的情況下流量可以迅速切換到其他機房讓核心業務不受影響。可謂是防止系統宕機必備良藥啊!
做好事故復盤
都說小事故傷身,大事故提桶。。一般發生事故后寫一張事故單是不可避免的。除了詳細描述好事故發生的經過,背鍋人,解決方案,后續的事故跟進也是一系列流程的事,多則需要數周去跟進。事故的發生對于團隊的技術發展和成型往往起著積極推進作用,所以對于每一個團隊來說事故一定是不可避免的。每次事故發生我們都要思考如何完善系統,打破技術壁壘。并且遇到事兒也不要慌,如果是大問題,那么首先背鍋的一定是leader!
其實呢一般公司最喜歡的是能快速解決問題的員工,即便這些問題可能是由你創造的。