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