應(yīng)用架構(gòu)設(shè)計“防火”經(jīng)驗分享
Author : 岑文初(淘寶花名:放翁)
Email: fangweng@taobao.com
Blog: http://blog.csdn.net/cenwenchu79
Date: 2009-08-26
剛從阿軟到淘寶不久,現(xiàn)在主要負責TOP平臺的技術(shù)框架設(shè)計,同時要肩負“救火”和“防火”的工作,也需要培養(yǎng)團隊的同學能夠有“防火”意識,減少“救火”次數(shù),因此今天下午花了一點時間,也沒于寫任何的PPT,就直接將自己想的起來的一些自己認為應(yīng)用架構(gòu)設(shè)計“防火”知識做了一下事例分享,這里也想記錄下來給更多的同學分享一下,當然很多都是老生常談的常識,但是有時候不經(jīng)意就會忘記一些血的教訓。
資源是有限的
著火點:
系統(tǒng)設(shè)計的時候總是估摸不到會有大數(shù)據(jù)量從遠端傳輸過來(例如處理Http請求時,對于大附件內(nèi)容的處理,全部裝載到內(nèi)存,結(jié)果資源耗盡。從搜索引擎或者DB或者緩存里面拉數(shù)據(jù),沒有分頁,結(jié)果內(nèi)存被吃盡。Socket無限建立連接,結(jié)果linux的文件句柄被耗盡。)
防火點:
對業(yè)務(wù)場景中資源的分配與申請需要做到上限控制,以及達到上限以后的邏輯處理(排隊,丟棄,告警)。可以采取一些滑動窗口設(shè)計來將不需要過多處理的內(nèi)容分段直接送入下一個處理管道中。
依賴是未知的
著火點:
事務(wù)中嵌入對于第三方系統(tǒng)的請求(例如在數(shù)據(jù)庫操作時去發(fā)送郵件或者緩存獲取內(nèi)容,結(jié)果連接池資源被Hold,導(dǎo)致系統(tǒng)不可用)。默認依賴系統(tǒng)會給出結(jié)果,如果出現(xiàn)異常就反復(fù)重試,結(jié)果對方被壓垮,自己也犧牲。
防火點:
對于第三方系統(tǒng)的依賴能夠異步的就采用異步方式,能夠從主流程中剝離的就剝離。同時設(shè)計好容錯的機制,采用本地時效性緩存減少對對方的壓力和依賴。最重要的就是注意系統(tǒng)間的死鎖,申請了一套資源處理業(yè)務(wù)邏輯,結(jié)果由于遠端系統(tǒng)的不可用,導(dǎo)致本地資源的無法釋放,最后擊垮自己系統(tǒng)。
線程安全與性能
著火點:
對于線程不安全的對象處理一定要小心,否則業(yè)務(wù)出現(xiàn)異常的地方其實已經(jīng)離設(shè)計出現(xiàn)問題的地方十萬八千里,問題時常成為靈異問題,解決只有靠經(jīng)驗。
防火點:
首先對于自己設(shè)計的類和方法需要注釋是否是線程安全的。同時明確類的使用場景,對線程安全及高性能作判斷,因為采用線程安全的對象一定會有性能損耗。最近給同學寫的一個Http消息的Lazy獲取參數(shù),就是線程不安全的類,但是這個類只會保存在ThreadLocal中,因此不存在問題。很直觀的一點判斷是否線程安全,就看看你設(shè)計的類里面的成員變量在多線程操作時候是否會有并發(fā)問題,例如一個普通的Map,多個線程操作就會導(dǎo)致結(jié)果的不可估量性。
資源釋放
著火點:
正常邏輯都會將IO流關(guān)閉,Socket關(guān)閉,但是異常拋出時候,沒有走到資源釋放的流程中,產(chǎn)生了資源泄露問題。另外,資源中可能會有內(nèi)嵌資源,當內(nèi)部資源被外部的對象引用,則釋放將不成功,內(nèi)部資源依然會泄露。一些需要顯式回收的資源(例如ThreadLocal),如果不回收,那么下次線程被操作系統(tǒng)重用,則會出現(xiàn)莫名其妙的問題(Java的線程創(chuàng)建和使用依賴于操作系統(tǒng)的實現(xiàn))。
防火點:
Finally的處理。需要釋放的資源要做深度檢查。需要顯式回收的資源要確保使用完畢以后被回收(異常情況也需要考慮)。
創(chuàng)建與復(fù)用
著火點:
在以前設(shè)計Cache客戶端的時候,有同學給我建議說我對于字節(jié)數(shù)組利用可以采取復(fù)用的方式,這樣可以減少對象的申請。但是做了一下測試,這樣的重復(fù)利用其實效果不像想象的那么好,甚至還不如直接創(chuàng)建。
防火點:
Java的垃圾收集器已經(jīng)在性能上有了很大的提高,同時對于對象的復(fù)用需要考慮對象復(fù)用前的初始化或者是內(nèi)容重置,這些得成本及復(fù)雜度可能遠遠要高于復(fù)用帶來的優(yōu)勢,因此需要根據(jù)具體的業(yè)務(wù)場景選擇復(fù)用和創(chuàng)建。當然對于稀缺資源采用池的方式是最好的。
字符串處理,日志級別的選擇
著火點:
這兩個是小問題,但是會帶來大麻煩。首先字符串的累加是老生常談的問題,但是很多新手不以為然,當你是一個高速運轉(zhuǎn)的系統(tǒng)時,你就會發(fā)現(xiàn)1ms的延時在上千萬次調(diào)用下回被無限放大,10byte的申請,在上千萬次的請求下會帶來GC多次的操作(帶來的短暫處理停滯直接影響系統(tǒng)的可用性)。日志級別的隨意性會導(dǎo)致線上環(huán)境日志迅速膨脹,出錯難以查找,影響系統(tǒng)的效率。(log4j優(yōu)化的再好也是要寫文件的,雖然是異步刷頁)
防火點:
謹慎處理字符串拼接,選擇線程安全或者不安全的兩個StringBuilder和StringBuffer。日志盡量區(qū)分清楚,debug和Info,前者純粹調(diào)試,可以有海量信息,Info一般用于系統(tǒng)或者模塊的狀況報告。Warn通常不建議使用了。Error就把你需要的關(guān)鍵信息都打出來。附帶這里說一下對于日期對象的處理,在傳輸和保存的過程中,建議都還是采用long型,可以很好的提高性能及滿足國際化的需求。
原子操作與并發(fā)控制
著火點:
對于本地的對象操作通常情況下通過鎖機制保證并發(fā)的一致性,當在設(shè)計一個對于資源訪問控制的策略時,例如集群應(yīng)用處理某人每天發(fā)送短信1000條,這時候計數(shù)器保存在遠端的集中式緩存中,采用get和put方式就會有并發(fā)問題,因為在應(yīng)用獲得到999這個計數(shù)器值的時候,也許正有10000個請求也獲得了這個值,這樣原有的控制就失效了。
防火點:
其實就是一個原子操作的支持,本地數(shù)據(jù)可以通過鎖來達到原子操作,遠程依賴就需要對方系統(tǒng)提供原子操作接口來實現(xiàn)高并發(fā)下的業(yè)務(wù)處理,例如Memcached Cache提供的incr 和decr。結(jié)合黑名單策略,計數(shù)器可以發(fā)揮很多用途,包括及時監(jiān)控告警等。
接口實現(xiàn)與松耦合
著火點:
沒有接口提供,團隊間合作困難,無法Mock,相互之間進度影響很大。同時業(yè)務(wù)實現(xiàn)的修改直接影響業(yè)務(wù)調(diào)用方,使得雙方耦合性很強,系統(tǒng)不穩(wěn)定性被放大。
防火點:
對外提供的服務(wù),或者模塊間交互的服務(wù)都需要接口化。框架性代碼需要在模塊載入時考慮是否需要接口化定義,以便在不同環(huán)境可以切換不同實現(xiàn)提供對特殊場景的支持,同時也可以將具體實現(xiàn)延后交給使用者實現(xiàn),使得框架更加靈活。Jdk對于xml的解析就是最好的范例。
靈活性和性能和可維護性的折中
著火點:
最近看了一些同學的代碼,看到大量的使用了反射,攔截器等。但是在線上環(huán)境運行過程中就發(fā)現(xiàn)對于一些攔截器的配置疏漏導(dǎo)致系統(tǒng)性能大幅度降低。對于幾十個spring文件,有誰能夠很清楚和直觀的了解到這些看似靈活和無侵入性的設(shè)計。
防火點:
對于業(yè)務(wù)邏輯不復(fù)雜,同時場景不多變的流程采用簡單的實現(xiàn),不要追求花哨的靈活性,帶來的只會是可讀性,可維護性,可用性的降低。
要有分布式和并發(fā)的觀念,但是不要本末倒置
著火點:
有些同學在做設(shè)計的時候考慮的很清晰,但是就是沒有考慮集群部署的情況,結(jié)果系統(tǒng)上線以后出現(xiàn)了無法集群部署的問題。并發(fā)情況的設(shè)計也一樣,僅僅在滿足業(yè)務(wù)需求以后,對于多用戶并發(fā)操作的考慮缺失,導(dǎo)致系統(tǒng)流程錯誤。
防火點:
設(shè)計的時候需要適度考慮這些問題,但是是在滿足現(xiàn)有業(yè)務(wù)邏輯的前提下,不要為了追求分布式而分布式。
便利性的函數(shù)與性能的沖突
著火點:
首先申明的是這點適用范圍有限(高速運轉(zhuǎn)的模塊)。對于String,Date等對象的便利性函數(shù),例如正則匹配,分割,Format等等其實都會有不少的性能損耗。例如你只是需要判斷文件名最后的后綴是否滿足需求,采用了正則匹配,結(jié)果發(fā)現(xiàn)性能在高速運轉(zhuǎn)的情況下大大下降。
防火點:
高速運轉(zhuǎn)的模塊盡量采用原始方式或者半原始方式。例如上面說到的文件后綴,就用string的endwith來判斷。對于一些字符串的替換,能夠用字符串拼接就拼接。對于一些字節(jié)流的處理也可以自己根據(jù)需求來訂制的寫。總的一句話,能夠滿足的就用最低成本的方法。
防止系統(tǒng)設(shè)計的完備性成為攻擊或者壓力的瓶頸
著火點:
在很多設(shè)計的時候,對于一些系統(tǒng)設(shè)計講究比較完美。例如對于對象的查詢會分本地緩存,集中緩存,DB三個階段。當對方攻擊采用不存在的資源名稱時候,這種分階段的設(shè)計反而會增加系統(tǒng)負荷。
防火點:
簡化流程的分支和層次,對于消耗性資源的訪問盡量減少或者沒有(采用黑名單本地緩存或者集中緩存的方式),同時改Pull為Push方式,通過控制數(shù)據(jù)變更點來通知相關(guān)系統(tǒng),而非輪詢獲取更新狀態(tài)。
多級緩存和異步緩解異構(gòu)系統(tǒng)的瓶頸
著火點:
有時候設(shè)計系統(tǒng)時,服務(wù)提供方向我們許諾說對方系統(tǒng)如何高效和健壯,但是當頻繁訪問產(chǎn)生網(wǎng)絡(luò)風暴的時候,我們發(fā)現(xiàn)原來帶寬和網(wǎng)絡(luò)IO本身都會成為瓶頸。
防火點:
對于第三方系統(tǒng)的依賴,要做到松耦合就要從流程的異步化來實現(xiàn)。同時通過緩存的使用來達到,系統(tǒng)的高效性和降低對于第三方系統(tǒng)的依賴程度。這樣可以大大降低系統(tǒng)的瓶頸。
運行期白盒化,模塊可重置
著火點:
系統(tǒng)運行起來以后就無法在知道內(nèi)部的狀態(tài),也無法對問題組件進行單獨處理,造成線上環(huán)境的不可知性和無法部分修復(fù)。不得不停機重起和看日志。
防火點:
模塊設(shè)計過程中考慮運行期可觀測和可重置,提高系統(tǒng)的模塊化程度及健壯性。
站在用戶角度設(shè)計接口,提升系統(tǒng)可用性
著火點:
總是從自身業(yè)務(wù)體系和架構(gòu)去考慮如何設(shè)計對外接口,但是發(fā)現(xiàn)最后用戶使用的很別扭,同時由于需求不能直接被滿足,會多次反復(fù)調(diào)用接口,導(dǎo)致自身系統(tǒng)的壓力增大。例如對于一個狀態(tài)的檢查接口,是否提供一個狀態(tài)變更通知接口就會極大降低輪詢的壓力
防火點:
需要從客戶角度考慮問題,設(shè)計接口,防止需求和實現(xiàn)脫節(jié),導(dǎo)致系統(tǒng)壓力增加。
懶惰有時候是件好事
著火點:
業(yè)務(wù)流程中很多耗時的操作在流程編排方面沒有考慮清楚,當耗時工作做完以后,發(fā)現(xiàn)不符合最基本的交驗,這樣就會導(dǎo)致系統(tǒng)無謂的增加了開銷。對于需要申請的資源,考慮處理流程的階段,階段性申請要優(yōu)于一次申請(不過需要注意死鎖)。
防火點:
流程編排需要合理性,盡量將耗時的工作放到合理的位置,同時做好基礎(chǔ)的防攻擊輕量前端屏障邏輯,提高系統(tǒng)的健壯性。
主流程和副流程隔離
著火點:
SIP早先的日志分析模塊中有分析日志,備份,發(fā)郵件,更新系統(tǒng)緩存,操作數(shù)據(jù)庫等多種操作,但是一股腦兒都被放到一個流程中,結(jié)果當郵件沒有發(fā)成功導(dǎo)致整個流程的失敗。
防火點:
把真正的主流程梳理出來,同時對于一些副流程可以考慮采用后臺異步方式完成,提高系統(tǒng)穩(wěn)定性。
模塊間接口交互,控制資源直接操作入口
著火點:
對于數(shù)據(jù)庫中的資源任何模塊不區(qū)分范圍都可以訪問,最后導(dǎo)致數(shù)據(jù)結(jié)構(gòu)變更困難,業(yè)務(wù)對象管理混亂,模塊無法剝離獨立。
防火點:
模塊化設(shè)計的基本思想,模塊間通過接口交互獲得其他模塊管轄的數(shù)據(jù),接口方式屏蔽了對于后端實現(xiàn)及業(yè)務(wù)對象的依賴。
學習份外的事情,配置決定成敗
著火點:
沒有SA就高不定環(huán)境,也無法了解操作系統(tǒng)的配置與Web容器的配置對于應(yīng)用的影響。沒有DBA就無法確定如何寫SQL避免一些簡單的耗時查詢。沒有測試同學就無法作壓力測試,無法了解當前系統(tǒng)性能。
防火點:
多學多問,多了解一些其他崗位的內(nèi)容,才能夠更加全面的掌握好架構(gòu)設(shè)計。
不要迷信
著火點:
總是看到新技術(shù)如何有優(yōu)點,但是看不到它的成熟度。總是聽到很多經(jīng)驗之談,但是從來沒有真的比較過。結(jié)果就是別人說什么就是什么,系統(tǒng)地穩(wěn)定性和可用性基于Google出來的結(jié)果。
防火點:
需要聽取各種意見和經(jīng)驗,同時用測試結(jié)果說明問題的結(jié)果,看代碼說明結(jié)果背后的問題。這樣才會走得更加踏實,學的更加實際。其實技術(shù)發(fā)展來說,真正的基礎(chǔ)性內(nèi)容還是有限的,而且各種技術(shù)都是觸類旁通。分布式,不論是文件系統(tǒng),DB,緩存都會遇到分布式的共性問題(負載均攤,容錯,數(shù)據(jù)復(fù)制,動態(tài)擴容等等),在結(jié)合一些文件系統(tǒng),DB,緩存的自身特質(zhì)。因此扎扎實實學好基礎(chǔ),了解Http協(xié)議,了解七層通信協(xié)議,了解文件系統(tǒng)設(shè)計,了解MapReduce思路,了解結(jié)構(gòu)化,半結(jié)構(gòu)化(bigmap),非結(jié)構(gòu)化存儲的要點,就會不會讓自己迷失在技術(shù)宣傳中。
明天晚上去北京參加系統(tǒng)架構(gòu)師會議,到時候會和大家分享一下TOP的一些商業(yè)和技術(shù)上的心得,準備得很倉促,但是個人覺得分享就在于自己將自己知道的說出來,時間不長,45分鐘,能講的也不多,但是如果對于淘寶開放平臺有興趣的同學可以來聽一下。這里也算是做個廣告,不過不要期望過高,免得失望也大^_^。五年沒有來北京了,首都應(yīng)該也變化不小了。