在整個網站應用中,緩存幾乎無處不在,既存在於瀏覽器也存在於應用服務器和數據庫服務器;既能夠對數據緩存,也能夠對文件緩存,還能夠對頁面片斷緩存。合理使用緩存,對網站性能優化意義重大。
網站性能優化第必定律:優先考慮使用緩存。算法
緩存是指將數據存儲在相對較高訪問速度的存儲介質中。數據庫
(1)訪問速度快,減小數據訪問時間;
(2)若是緩存的數據進過計算處理獲得的,那麼被緩存的數據無需重複計算便可直接使用,所以緩存還起到減小計算時間的做用。編程
緩存的本質是一個內存Hash表,以一對Key、Value的形式存儲在內存Hash表中,讀寫時間複雜度爲O(1)。設計模式
緩存主要用來存放那些讀寫比很高、不多變化的數據,如商品的類目信息,熱門詞的搜索列表信息,熱門商品信息等。應用程序讀取數據時,先到緩存中讀取,若是讀取不到或數據已失效,再訪問數據庫,並將數據寫入緩存。瀏覽器
網站數據訪問一般遵循二八定律,即80%的訪問落在20%的數據上,所以利用Hash表和內存的高速訪問特性,將這20%的數據緩存起來,可很好地改善系統性能,提升數據存取速度,下降存儲訪問壓力。緩存
不合理使用緩存非但不能提升系統的性能,還會成爲系統的累贅,甚至風險。安全
若是緩存中保存的是頻繁修改的數據,就會出現數據寫入緩存後,應用還來不及讀取緩存,數據就已經失效,徒增系統負擔。通常來講,數據的讀寫比在2:1(寫入一次緩存,在數據更新前至少讀取兩次)以上,緩存纔有意義。性能優化
若是應用系統訪問數據沒有熱點,不遵循二八定律,那麼緩存就沒有意義。服務器
通常會對緩存的數據設置失效時間,一旦超過失效時間,就要從數據庫中從新加載。所以要容忍必定時間的數據不一致,如賣家已經編輯了商品屬性,可是須要過一段時間才能被買家看到。還有一種策略是數據更新當即更新緩存,不過這也會帶來更多系統開銷和事務一致性問題。網絡
緩存會承擔大部分數據庫訪問壓力,數據庫已經習慣了有緩存的日子,因此當緩存服務崩潰時,數據庫會由於徹底不能承受如此大壓力而宕機,致使網站不可用。這種狀況被稱做緩存雪崩,發生這種故障,甚至不能簡單地重啓緩存服務器和數據庫服務器來恢復。
實踐中,有的網站經過緩存熱備份等手段提升緩存可用性:當某臺緩存服務器宕機時,將緩存訪問切換到熱備服務器上。但這種設計有違緩存的初衷,緩存根本就不該該當作一個可靠的數據源來使用。
經過分佈式緩存服務器集羣,將緩存數據分佈到集羣多臺服務器上可在必定程度上改善緩存的可用性。當一臺緩存服務器宕機時,只有部分緩存數據丟失,從新從數據庫加載這部分數據不會產生很大的影響。
緩存中存放的是熱點數據,熱點數據又是緩存系統利用LRU(最近最久未用算法)對不斷訪問的數據篩選淘汰出來,這個過程須要花費較長的時間。新系統的緩存系統若是沒有任何數據,在重建緩存數據的過程當中,系統的性能和數據庫負載都不太好,那麼最好在緩存系統啓動時就把熱點數據加載好,這個緩存預加載手段叫緩存預熱。對於一些元數據如城市地名列表、類目信息,能夠在啓動時加載數據庫中所有數據到緩存進行預熱。
若是由於不恰當的業務、或者惡意攻擊持續高併發地請求某個不存在的數據,因爲緩存沒有保存該數據,全部的請求都會落到數據庫上,會對數據庫形成壓力,甚至崩潰。一個簡單的對策是將不存在的數據也緩存起來(其value爲null)。
分佈式緩存指緩存部署在多個服務器組成的集羣中,以集羣方式提供緩存服務,其架構方式有兩種,一種是以JBoss Cache爲表明的須要更新同步的分佈式緩存,一種是以Memcached爲表明的不互相通訊的分佈式緩存。
JBoss Cache在集羣中全部服務器中保存相同的緩存數據,當某臺服務器有緩存數據更新,就會通知其餘機器更新或清除緩存數據。 它一般將應用程序和緩存部署在同一臺服務器上,但受限於單一服務器的內存空間;當集羣規模較大的時候,緩存更新須要同步到全部機器,代價驚人。所以這種方案多見於企業應用系統中。
Memcached採用一種集中式的緩存集羣管理(互不通訊的分佈式架構方式)。緩存與應用分離部署,緩存系統部署在一組專門的服務器上,應用程序經過一致性Hash等路由算法選擇緩存服務器遠程訪問數據,緩存服務器之間不通訊,集羣規模能夠很容易地實現擴容,具備良好的伸縮性。詳細請看LZ其餘文章。
Memcached有如下幾個特性:
(1)簡單的通訊協議。Memcached使用TCP協議(UDP也支持)通訊;
(2)豐富的客戶端程序。
(3)高性能的網絡通訊。Memcached服務端通訊模塊基於Libevent,一個支持事件觸發的網絡通訊程序庫,具備穩定的長鏈接。
(4)高效的內存管理。
(5)互不通訊的服務器集羣架構。
使用消息隊列將調用異步化(生產者–消費者模式),可改善網站的擴展性,還能夠改善系統的性能。
在不使用消息隊列的狀況下,用戶的請求數據直接寫入數據庫,在高併發的狀況下,會對數據庫形成巨大壓力,使得響應延遲加重。在使用消息隊列後,用戶請求的數據發送給消息隊列後當即返回,再由消息隊列的消費者進程(一般狀況下,該進程獨立部署在專門的服務器集羣上)從消息隊列中獲取數據,異步寫入數據庫。因爲消息隊列服務器處理速度遠快於服務器(消息隊列服務器也比數據庫具備更好的伸縮性)。
消息隊列具備很好的削峯做用–經過異步處理,將短期高併發產生的事務消息存儲在消息隊列中,從而削平高峯期的併發事務。
須要注意的是,因爲數據寫入消息隊列後當即返回給用戶,數據在後續的業務校驗、寫數據庫等操做可能失敗,所以在使用消息隊列進行業務異步處理後,須要適當修改業務流程進行配合,如訂單提交後,訂單數據寫入消息隊列,不能當即返回用戶訂單提交成功,須要在消息隊列的訂單消費者進程真正處理完後,甚至商品出庫後,再經過電子郵件或SMS消息通知用戶訂單成功,以避免交易糾紛。有關消息隊列的詳細信息請參看LZ的其餘博客。
任何能夠晚點作的事情都應該晚點再作。
在網站高併發訪問的場景下,使用負載均衡技術爲一個應用構建一個由多臺服務器組成的服務器集羣,將併發訪問請求分發到多臺服務器上處理,避免單一服務器因負載壓力過大而響應緩慢,使用戶請求具備更好的響應延遲特性。
從資源利用的角度看,使用多線程的緣由主要有兩個:IO阻塞與多CPU。當前線程進行IO處理的時候,會被阻塞釋放CPU以等待IO操做完成,因爲IO操做(不論是磁盤IO仍是網絡IO)一般都須要較長的時間,這時CPU能夠調度其餘的線程進行處理。 理想的系統Load是既沒有進程(線程)等待也沒有CPU空閒,利用多線程IO阻塞與執行交替進行,可最大限度利用CPU資源。 使用多線程的另外一個緣由是服務器有多個CPU。
簡化啓動線程估算公式:
啓動線程數 = [任務執行時間 / (任務執行時間 - IO等待時間)]*CPU內核數
多線程編程一個須要注意的問題是線程安全問題,即多線程併發對某個資源進行修改,致使數據混亂。全部的資源—對象、內存、文件、數據庫,乃至另外一個線程均可能被多線程併發訪問。
編程上,解決線程安全的主要手段有:
(1)將對象設計爲無狀態對象。所謂無狀態對象是指對象自己不存儲狀態信息(對象無成員變量,或者成員變量也是無狀態對象),不過從面向對象設計的角度看,無狀態對象是一種不良設計。
(2)使用局部對象。即在方法內部建立對象,這些對象會被每一個進入該方法的線程建立,除非程序有意識地將這些對象傳遞給其餘線程,不然不會出現對象被多線程併發訪問的情形。
(3)併發訪問資源時使用鎖。即多線程訪問資源的時候,經過鎖的方式使多線程併發操做轉化爲順序操做,從而避免資源被併發修改。
系統運行時,要儘可能減小那些開銷很大的系統資源的建立和銷燬,好比數據庫鏈接、網絡通訊鏈接、線程、複雜對象等。從編程角度,資源複用主要有兩種模式:單例(Singleton)和對象池(Object Pool)。
單例雖然是GoF經典設計模式中較多被詬病的一個模式,但因爲目前Web開發中主要使用貧血模式,從Service到Dao都是些無狀態對象,無需重複建立,使用單例模式也就天然而然了。
對象池模式經過複用對象實例,減小對象建立和資源消耗。對於數據庫鏈接對象,每次建立鏈接,數據庫服務端都須要建立專門的資源以應對,所以頻繁建立關閉數據庫鏈接,對數據庫服務器是災難性的,同時頻繁建立關閉鏈接也須要花費較長的時間。所以實踐中,應用程序的數據庫鏈接基本都使用鏈接池(Connection Pool)的方式,數據庫鏈接對象建立好之後,將鏈接對象放入對象池容器中,應用程序要鏈接的時候,就從對象池中獲取一個空閒的鏈接使用,使用完畢再將該對象歸還到對象池中便可,不須要建立新的鏈接。
早期關於程序的一個定義是,程序就是數據結構+算法,數據結構對於編程的重要性不言而喻。在不一樣場景中合理使用數據結構,靈活組合各類數據結構改善數據讀寫和計算特性可極大優化程序的性能。
理解垃圾回收機制有助於程序優化和參數調優,以及編寫內存安全的代碼。