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