版權聲明:本文由韓偉原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/163數據庫
來源:騰雲閣 https://www.qcloud.com/community編程
在服務器端程序開發領域,性能問題一直是備受關注的重點。業界有大量的框架、組件、類庫都是以性能爲賣點而廣爲人知。然而,服務器端程序在性能問題上應該有何種基本思路,這個卻不多被這些項目的文檔說起。本文正式但願介紹服務器端解決性能問題的基本策略和經典實踐,並分爲幾個部分來講明:瀏覽器
緩存策略的概念和實例緩存
緩存策略的難點:不一樣特色的緩存數據的清理機制安全
分佈策略的概念和實例服務器
分佈策略的難點:共享數據安全性與代碼複雜度的平衡網絡
咱們提到服務器端性能問題的時候,每每會混淆不清。由於當咱們訪問一個服務器時,出現服務卡住不能獲得數據,就會認爲是「性能問題」。可是實際上這個性能問題多是有不一樣的緣由,表現出來都是針對客戶請求的延遲很長甚至中斷。咱們來看看這些緣由有哪些:第一個是所謂併發數不足,也就是同時請求的客戶過多,致使超過容納能力的客戶被拒絕服務,這種狀況每每會由於服務器內存耗盡而致使的;第二個是處理延遲過長,也就是有一些客戶的請求處理時間已經超過用戶能夠忍受的長度,這種狀況經常表現爲CPU佔用滿額100%。併發
咱們在服務器開發的時候,最經常使用到的有下面這幾種硬件:CPU、內存、磁盤、網卡。其中CPU是表明計算機處理時間的,硬盤的空間通常很大,主要是讀寫磁盤會帶來比較大的處理延遲,而內存、網卡則是受存儲、帶寬的容量限制的。因此當咱們的服務器出現性能問題的時候,就是這幾個硬件某一個甚至幾個都出現負荷佔滿的狀況。這四個硬件的資源通常能夠抽象成兩類:一類是時間資源,好比CPU和磁盤讀寫;一類是空間資源,好比內存和網卡帶寬。因此當咱們的服務器出現性能問題,有一個最基本的思路,就是——時間空間轉換。咱們能夠舉幾個例子來講明這個問題。
框架
水壩就是用水庫空間來換流量時間的例子分佈式
當咱們訪問一個WEB的網站的時候,輸入的URL地址會被服務器變成對磁盤上某個文件的讀取。若是有大量的用戶訪問這個網站,每次的請求都會形成對磁盤的讀操做,可能會讓磁盤不堪重負,致使沒法即時讀取到文件內容。可是若是咱們寫的程序,會把讀取過一次的文件內容,長時間的保存在內存中,當有另一個對一樣文件的讀取時,就直接從內存中把數據返回給客戶端,就無需去讓磁盤讀取了。因爲用戶訪問的文件每每很集中,因此大量的請求可能都能從內存中找到保存的副本,這樣就能大大提升服務器能承載的訪問量了。這種作法,就是用內存的空間,換取了磁盤的讀寫時間,屬於用空間換時間的策略。
方便麪預先緩存了大量的烹飪操做
舉另一個例子:咱們寫一個網絡遊戲的服務器端程序,經過讀寫數據庫來提供玩家資料存檔。若是有大量玩家進入這個服務器,一定有不少玩家的數據資料變化,好比升級、得到武器等等,這些經過讀寫數據庫來實現的操做,可能會讓數據庫進程負荷太重,致使玩家沒法即時完成遊戲操做。咱們會發現遊戲中的讀操做,大部分都是針是對一些靜態數據的,好比遊戲中的關卡數據、武器道具的具體信息;而不少寫操做,其實是會覆蓋的,好比個人經驗值,可能每打一個怪都會增長几十點,可是最後記錄的只是最終的一個經驗值,而不會記錄下打怪的每一個過程。因此咱們也可使用時空轉換的策略來提供性能:咱們能夠用內存,把那些遊戲中的靜態數據,都一次性讀取並保存起來,這樣每次讀這些數據,都和數據庫無關了;而玩家的資料數據,則不是每次變化都去寫數據庫,而是先在內存中保持一個玩家數據的副本,全部的寫操做都先去寫內存中的結構,而後按期再由服務器主動寫回到數據庫中,這樣能夠把屢次的寫數據庫操做變成一次寫操做,也能節省不少寫數據庫的消耗。這種作法也是用空間換時間的策略。
拼裝傢俱很省運輸空間,可是安裝很費時
最後說說用時間換空間的例子:假設咱們要開發一個企業通信錄的數據存儲系統,客戶要求咱們能保存下通信錄的每次新增、修改、刪除操做,也就是這個數據的全部變動歷史,以即可以讓數據回退到任何一個過去的時間點。那麼咱們最簡單的作法,就是這個數據在任何變化的時候,都拷貝一份副本。可是這樣會很是的浪費磁盤空間,由於這個數據自己變化的部分可能只有很小一部分,可是要拷貝的副本可能很大。這種狀況下,咱們就能夠在每次數據變化的時候,都記下一條記錄,內容就是數據變化的狀況:插入了一條內容是某某的聯繫方法、刪除了一條某某的聯繫方法……,這樣咱們記錄的數據,僅僅就是變化的部分,而不須要拷貝不少份副本。當咱們須要恢復到任何一個時間點的時候,只須要按這些記錄依次對數據修改一遍,直到指定的時間點的記錄便可。這個恢復的時間可能會有點長,可是卻能夠大大節省存儲空間。這就是用CPU的時間來換磁盤的存儲空間的策略。咱們如今常見的MySQL InnoDB日誌型數據表,以及SVN源代碼存儲,都是使用這種策略的。
另外,咱們的Web服務器,在發送HTML文件內容的時候,每每也會先用ZIP壓縮,而後發送給瀏覽器,瀏覽器收到後要先解壓,而後才能顯示,這個也是用服務器和客戶端的CPU時間,來換取網絡帶寬的空間。
在咱們的計算機體系中,緩存的思路幾乎無處不在,好比咱們的CPU裏面就有1級緩存、2級緩存,他們就是爲了用這些快速的存儲空間,換取對內存這種相對比較慢的存儲空間的等待時間。咱們的顯示卡里面也帶有大容量的緩存,他們是用來存儲顯示圖形的運算結果的。
通往大空間的郊區路上容易交通堵塞
緩存的本質,除了讓「已經處理過的數據,不須要重複處理」之外,還有「以快速的數據存儲讀寫,代替較慢速的存儲讀寫」的策略。咱們在選擇緩存策略進行時空轉換的時候,必須明確咱們要轉換的時間和空間是否合理,是否能達到效果。好比早期有一些人會把WEB文件緩存在分佈式磁盤上(例如NFS),可是因爲經過網絡訪問磁盤自己就是一個比較慢的操做,並且還會佔用可能就不充裕的網絡帶寬空間,致使性能可能變得更慢。
在設計緩存機制的時候,咱們還容易碰到另一個風險,就是對緩存數據的編程處理問題。若是咱們要緩存的數據,並非徹底無需處理直接讀寫的,而是須要讀入內存後,以某種語言的結構體或者對象來處理的,這就須要涉及到「序列化」和「反序列化」的問題。若是咱們採用直接拷貝內存的方式來緩存數據,當咱們的這些數據須要跨進程、甚至跨語言訪問的時候,會出現那些指針、ID、句柄數據的失效。由於在另一個進程空間裏,這些「標記型」的數據都是不存在的。所以咱們須要更深刻的對數據緩存的方法,咱們可能會使用所謂深拷貝的方案,也就是跟着那些指針去找出目標內存的數據,一併拷貝。一些更現代的作法,則是使用所謂序列化方案來解決這個問題,也就是用一些明肯定義了的「拷貝方法」來定義一個結構體,而後用戶就能明確的知道這個數據會被拷貝,直接取消了指針之類的內存地址數據的存在。好比著名的Protocol Buffer就能很方便的進行內存、磁盤、網絡位置的緩存;如今咱們常見的JSON,也被一些系統用來做爲緩存的數據格式。
可是咱們須要注意的是,緩存的數據和咱們程序真正要操做的數據,每每是須要進行一些拷貝和運算的,這就是序列化和反序列化的過程,這個過程很快,也有可能很慢。因此咱們在選擇數據緩存結構的時候,必需要注意其轉換時間,不然你緩存的效果可能被這些數據拷貝、轉換消耗去不少,嚴重的甚至比不緩存更差。通常來講,緩存的數據越解決使用時的內存結構,其轉換速度就越快,在這點上,Protocol Buffer採用TLV編碼,就比不上直接memcpy的一個C結構體,可是比編碼成純文本的XML或者JSON要來的更快。由於編解碼的過程每每要進行復雜的查表映射,列表結構等操做。