張宴VS.岑文初
張宴:在項目的架構設計中,對於將來可能發生的需求變動,你是如何考慮的?如何應對?
岑文初:需求變動能夠分爲業務性和非業務性兩類。
對於業務性需求變動,思惟方式應當按以下順序進行:
第一,是否已經有相似功能,須要作些改進就能夠知足需求;
第二,沒有相似功能,是否能夠抽取部分已有功能,再作部分封裝便可實現;
第三,徹底沒有能夠複用的內容,考慮一下後續可能的業務需求。
也許上面的內容比較虛,但業務必定是根據場景來作出實際判斷,而這三點其實就是一個理念——不斷優化業務代碼,複用的思考會促進不斷地合理化結構(由於大部分狀況下,複用性越小的代碼其結構自己存在耦合性過強的問題)。
非業務性需求變動主要是指因爲系統自身應用場景發生變化(包括處理的數據量、業務規則複雜度等),而使得須要對現有系統作結構性調整。下面我舉個例子。
開放平臺的日誌分析系統,在數據量不斷增大和計算實時性要求加強的狀況下,由單機多線程計算演變爲多機分佈式協做計算,從全量文件分析轉變爲增量基於數據流分析。但總體結構卻沒有發生太大變化。
最初,系統日訪問量爲六千萬,數據分析報表每日出一次,分析器僅僅用於業務行爲分析統計、單機每日拖取全量日誌文件、多線程切割分析後合併。系統設計中有統計模型抽象層和簡單的任務管理層:統計模型抽象層就是將傳統的統計分析抽象成爲對無結構化的數據作MapReduce計算,模型規則引擎能夠根據配置直接分析無結構化定義的日誌數據;任務管理層在第一階段只是負責任務多線程內分配和管理。
當日志量每日達到兩億,分析器每日分析數據涵蓋了系統、ISV、服務提供方,業務多角度分析和數據挖掘、單機I/O及CPU就成爲瓶頸。所以,首先修改任務管理層,將原來多線程的任務管理和分配,擴展爲支持多機任務管理和分配(基於TCP層的數據交互協議)。數據通訊層理所固然地成爲承載多機協做的基礎層,由於自己沒有太多的複雜流程在裏面,就直接實現NIO的通訊層。此次需求變動的代價就是擴展了任務分配協議以支持多機協做工做,本地的數據合併轉變爲通訊傳輸後的本地數據合併,但對上層業務分析引擎沒有任何影響。
當日志量每日達到八億,分析器已經應用於整個系統的監控和告警及業務趨勢分析等各方面,全量每日分析知足不了業務需求,因而由每日全量分析改變爲實時增量分析。此次須要修改任務管理層:首先,擴展了Slave的數據源獲取方式,除了支持文件獲取,還支持HTTP數據流的獲取;其次,擴展了Master對於任務週期的管理,任務列表能夠被週期性重置,同時能夠週期性導出增量數據,所以實時分析能夠經過使用較短週期(分鐘級別)的任務列表管理、任務分配、結果合併和增量導出,實現實時的大數據量分析。此次需求變動僅僅是對任務管理的週期作了更靈活的擴展,同時在Slave上作了多數據源數據的獲取,對於自己的任務管理、合併、計算都沒有任何影響。總之,對於非業務性的需求改變,須要可以將業務性設計和系統設計隔離開來,同時明晰系統設計邊界,支持外部接口的可擴展,這樣就可以支撐因爲系統應用場景的變化帶來的系統架構的變化。
許式偉:做爲系統架構師,您通常會從哪些方面來保證網站的高可用性(下降故障時間)?
張宴:不少因素都會致使網站發生故障,從而影響網站的高可用性,好比服務器硬件故障、軟件系統故障、IDC機房故障、程序上線前測試未發現的Bug、遭受分佈式攻擊、突發訪問人數劇增等。
一套良好的網站系統架構,應該儘量地避免只有一臺服務器、一個數據庫、一套軟件節點等單點故障的存在。單點故障一旦發生,將直接致使網站服務不可用,恢復正常服務所需的時間也比較長,甚至還可能沒法恢復。負載均衡集羣、雙節點熱備、分佈式處理等均可以用來解決單點故障,好比提供相同業務的Web服務器、MySQL數據庫從庫,均可以構建負載均衡集羣。一旦集羣中的一臺服務器、一個服務出現故障,自動實時摘除,對用戶來講是不可感知的,不會影響到整個網站的訪問,能夠爲運維工程師留下足夠的時間去排查和解決故障。
對於重要的MySQL數據庫主庫,咱們習慣於從硬件層和軟件層來實現熱備,避免單點。越是複雜的設備,發生故障的機率越大。在磁盤沒有損壞的狀況下,應用程序致使服務器宕機的機率,遠高於簡單的磁盤陣列宕機的機率。因此,從硬件層解決的話,能夠在兩臺服務器上安裝相同的數據庫版本、進行相同的配置,用SAS或SCSI線鏈接一臺磁盤陣列,將數據庫數據文件存放到盤陣上。正常狀況下用服務器A掛載盤陣分區,啓動MySQL,綁定虛擬IP;若是服務器A宕機,則用服務器B掛載盤陣分區,啓動MySQL,接管虛擬IP。從軟件層解決的話,則能夠藉助DRBD等軟件作鏡像。
IDC機房發生故障的機率較小,但若是發生的話,影響面也是最大的。若是全部服務器都託管在一個IDC機房,一旦該機房遭遇長時間流量攻擊、斷電、斷網、地方政策性封網等,一般只能聯繫IDC去處理,除此以外一籌莫展,解決時間也比較長。若是成本容許,將網站服務器分佈在兩個以上的IDC機房,當某個IDC發生故障時,能夠臨時切換DNS域名解析來優先恢復服務。
雖然程序代碼上線前,通過了測試人員的嚴格測試,但測試環境和生產環境畢竟有差別,因此一些會急劇影響性能、正常服務的Bug每每在程序上線以後,纔會被發現,這就要求咱們在發現Bug後,可以迅速回滾到上一正常版本。咱們在SVN的基礎上,開發了Web代碼發佈系統,會將每一個發佈版本之間的文件變動記錄下來,一鍵實現程序代碼在多臺Web服務器上的發佈和回滾。
遭遇DDOS分佈式拒絕服務攻擊,使用防火牆來對付半鏈接、假IP,還算比較容易。而那種專挑複雜動態應用程序URL進行的分佈式CC攻擊,來源爲真實IP、真實HTTP請求,具備模擬正規瀏覽器User-Agent、單個IP的每秒請求數不高、有成千上萬個攻擊源等特徵,很難與正常訪問區分開,比較難對付。可是,正常經過瀏覽器訪問一個URL,會加載該URL中引入的JavaScript腳本、CSS樣式、圖片等文件。遇到CC攻擊,須要及時分析日誌,找出訪問量異常上漲的URL,而後用事先寫好的shell腳本找出哪些IP的請求只訪問了該URL,而不加載該URL引入的文件,對這些IP進行自動封鎖。
對於網遊站點來講,訪問量受廣告集中時間段投放、線上活動的影響較大,帶寬峯值時間不固定,對於靜態內容,可使用商業CDN,按實際使用量計費。對於動態內容,若是遇到突發訪問人數劇增,超過現有服務器處理能力,最簡單的臨時處理辦法就是增長服務器。上架新服務器須要時間,可是,同一個IDC機房內,能夠藉助其餘業務的服務器,在不一樣端口開啓一組新進程,加入到原有負載均衡池中。另外,能夠臨時關閉一些Web中的次要功能,來減小服務器消耗。
許式偉:您在任務切分上,有什麼經驗分享?您經過哪些手段保證任務的獨立性?
張宴:相信不少人都遇到過這種狀況:在一個老項目上修改、增長一些新功能所花費的時間,不比從新來作一個包含全部功能的新項目時間用得少。一個須要長期維護的項目,不可避免地會面臨老員工的離職、新員工的接手,不少時候,項目代碼的可維護性將決定一個項目的生存週期。讓一個新員工在規定開發時間的壓力下,去面對一個文檔不夠詳細、陌生的、功能複雜的龐大項目,短期弄明白全部功能邏輯不是一件容易的事。因此,任務須要切分,將一個大的任務切分紅一個個小模塊以後,各模塊之間能夠作到代碼獨立,互不影響,可維護性也大大加強。
關於任務切分,在第一個項目:金山遊戲官網的《用戶行爲分析系統》中,因爲數據挖掘計算須要消耗較高的內存、CPU資源,一臺服務器的處理能力不夠,而商業的分佈式數據倉庫價格又太貴,因此,只有從程序應用中下手,進行任務切分。咱們先按須要挖掘的數據指標,將整個數據挖掘任務切分紅多個數據挖掘插件,每一個插件能夠在不一樣的服務器上運行,多個插件能夠同時在多臺服務器上。多個數據挖掘插件之間,若是用到相同的某項數據,那麼,就將該項數據以冗餘方式,複製幾份提供給須要的插件,從而實現插件之間無交互、無關聯,保證了超大數據量下插件的運算速度。
在第二個項目:金山遊戲新版運營管理系統中,則將整個任務切分紅了PHP Web管理界面、PHP Web API功能接口、C/C++中間件引擎三部分。這是一種分層結構切分,最上層的「PHP Web管理界面」調用「PHP Web API功能接口」,「PHP Web API功能接口」調用運行在遊戲服務器端的「C/C++中間件引擎」,「C/C++中間件引擎」與「遊戲服務器端進程」經過TCP、UDP二進制協議、信號、命令行等多種方式通訊。四者之間相對獨立,代碼無關聯,經過一層層API接口實現交互。「PHP Web管理界面」負責通用界面實現。「PHP Web API功能接口」內部,又按接入的遊戲模塊、子功能模塊進行了更細的切分,各功能模塊之間經過內部API交互。「C/C++中間件引擎」大而全,不處理具體指令,但兼容TCP、UDP、HTTP、HTTPS/SSL、信號、命令行等大多數通訊方式,負責和各類類型的遊戲服務端交互。這是一套徹底由API接口驅動的系統架構,一款新遊戲接入運營管理系統時,只需在「PHP Web API功能接口」中增長一個模塊;一個遊戲新管理功能的增長,只須要在「PHP Web API功能接口」中增長一個子模塊。經過任務切分,將複雜功能簡單化,也將原來接入一款新遊戲所須要的幾個月時間,縮短爲1~2周。shell