構建高可用web站點學習(二)

web站點的緩存學習php

緩存在web應用裏面十分常見,也有各類各樣的緩存,從請求開始一直到代碼處理的階段均可以採起緩存。下面就逐一介紹:html

1、客戶端緩存(瀏覽器和http方面)前端

   前端頁面緩存主要遵循http協議和客戶端java

      若是響應頭信息告訴緩存器不要保留緩存;請求信息須要認證或者安全加密;一個緩存的副本若是含有如下信息,內容將會被認爲足夠新:含有完整的過時時間和壽命控制頭信息,而且內容仍在保鮮期內。瀏覽器已經使用過緩存副本,而且在一個會話中已經檢查過內容的新鮮度。mysql

Expires(過時時間)屬性是http控制緩存的基本手段。靜態圖片linux

Cache-Control  : Last-Modified:當客戶端第二次請求此url時,瀏覽器會向服務器傳送If-Modified-Since報頭,若是服務器端的資源沒有變化,則自動返回304,內容爲空。 EtagLast-Modified都起到文檔惟一性標識的做用。nginx

打開新窗口,若是指定cache-control的值爲privateno-cachemust-revalidate,那麼打開新窗口訪問時都會從新訪問服務器。而若是指定了max-age,那麼在此值內的時間裏就不會從新訪問服務器,例如:Cache-control: max-age=5 表示當訪問此網頁後的5秒內再次訪問不會去服務器。在地址欄回車 若是值爲privatemust-revalidate,則只有第一次訪問時會訪問服務器,之後就再也不訪問。若是值爲no-cache,那麼每次都會訪問。若是值爲max-age,則在過時以前不會重複訪問。按後退按扭 若是值爲privatemust-revalidatemax-age,則不會重訪問,而若是爲no-cache,則每次都重複訪問。按刷新按扭 不管爲什麼值,都會重複訪問。web

2、Web服務器緩存redis

  在ApacheNginx中開啓緩存模塊,服務器緩存的原理就是一個URL在一段較長的時間內對應一個惟一的內容,好比靜態內容或者更新不太頻繁的動態內容,一旦將內容緩存後,下次web服務器即可以在收到請求後當即拿出事先緩存好的響應內容並返回給瀏覽器算法

還有像Apache可使用APC opcode cache緩存PHP的動態內容的方案。這裏就不細說了。

3、反向代理緩存

  首先說明何爲反向代理(Reverse Proxy):反向代理:讓Internet上的其餘用戶能夠訪問局域網內的資源,使用的代理服務器即爲反向代理器

 

  反向代理的做用除了緩存,還有就是負載均衡。負載均衡在後面還會說到。這裏主要講的是基於反向代理的緩存。在這方面VarnishNginx都作得很不錯。(Varnish是基於內存進行緩存,利用二叉堆管理緩存文件,可達到積極刪除目的,Nginx是基於硬盤cache)通常用法是將Nginx運行在Apache服務器以前。監聽全部請求,按需將請求轉發給Apache

  反向代理服務器正是經過修改流經它的數據的HTTP頭信息來達到緩存的目的的,如一些靜態頁面等。而Nginx通常作Cache有下面幾種方法:

  1:這個辦法是把nginx404錯誤定向到後端,而後用proxy_store把後端返回的頁面保存。

  2:也是利用proxy_store,這裏利用if判斷cache目錄是否有文件,沒有的話從後端取,取回來後發送給用戶而且自身用proxy_store保存起來                   

  #2種辦法其實原理都差很少,只是方法不一樣,都是使用proxy_sore把數據保存起來,這種辦法在準肯定義上不能算是cache,只能算是鏡像-mirror功能,由於其沒有內建機制定時刪除cache,等因而永遠的靜態保存,因此這方案適合基本不會變化的數據,更詳細的說明請看下面詳細介紹這2cache的作法和介紹

  3:基於memcached的緩存(第四點就會提到)

  4:新版本nginx支持真正的proxy_cache模塊。                                      

    #proxy_cachenginx-0.7.44版開始,nginx支持了相似squid較爲正規的cache功能,這個緩存是把連接用md5編碼hash後保存。

  5:第三方插件。                                                                                                                          

4、第三方緩存

  當你學習Web的時候,常常會說Web的性能瓶頸出如今數據庫上(這句話也不必定是正確的),解決的辦法比較好的一個就是搭建一個數據庫前端的cache來減小數據庫的訪問次數。

  Memcached篇:Memcached是一個免費開源的、高性能的、具備分佈式內存對象的緩存系統。準備以一下幾個方面來講一下它是如何緩存的,也是對一些操做系統知識的鞏固:

  (1)基於libevent的事件處理

      libevent 是一個事件觸發的網絡庫,適用於windowslinuxbsd等多種平臺,內部使用selectepollkqueue等系統調用管理事件機 制。著名的用於apachephp緩存庫memcached聽說也是libevent based,並且libevent在使用上能夠作到跨平臺,若是你將要開發的應用程序須要支持以上所列出的平臺中的兩個以上,那麼強烈建議你採用這個庫,即便你的應用程序只須要支持一個平臺,選擇libevent也是有好處的,由於它能夠根據編譯/運行環境切換底層的事件驅動機制,這既能充分發揮系統的性能,又增長了軟件的可移植性。它封裝而且隔離了事件驅動的底層機制,除了通常的文件描述符讀寫操做外,它還提供有讀寫超時、定時器和信號回調,另外,它還容許爲事件設定不一樣的優先級,當前版本的libevent還提供dnshttp協議的異步封裝,這一切都讓這個庫尤爲適合於事件驅動應用程序的開發。(摘抄於libevent介紹)有興趣的能夠去看看具體源碼實現。

  (2)內置的內存管理方式

    Memcached利用Slab Allocation的工做機制: slabSlab Allocation)的設計理念是基於對象緩衝的,基本想法是避免重複大量的初始化和清理操做。slab主要能夠用於頻繁非配釋放的內存對象。替代malloc/free

Memcached中內存分配機制主要理念

    1.先爲分配響應的大塊內存,再在上面進行無縫小對象填充

    2.懶惰檢測機制,Memcached不花過多的時間在檢測各個item對象是否超市,當get獲取數據時,才檢查item對象是否應該刪除,

    3.懶惰刪除機制,在memcached中刪除一個item對象的時候,並非從內存中釋放,而是單單的進行標記處理,再將其指針放入slot回放插槽,下次分配的時候直接使用。

    內存不足時,slab採用LRU算法淘汰不經常使用item

    在 memcached 運行過程當中,要把一個 item 調入內存,但內存已無空閒空間時,爲了保證程序能正常運行,系統必須從內存中調出一部分數據,送磁盤的對換區中。但應將哪些數據調出,須根據必定的算法來肯定。一般,把選擇換出數據(頁面)的算法稱爲頁面置換算法(Page Replacement Algorithms)。 Memcached 採用最近最久未使用(LRU)置換算法,是根據數據(頁面)調入內存後的使用狀況進行決策的。因爲沒法預測各頁面未來的使用狀況,只能利用「最近的過去」做爲「最近的未來」的近似,所以,LRU置換算法是選擇最近最久未使用的頁面予以淘汰。當內存不足時,memcached 會從 slab各個 class 中的雙向鏈表的尾部開始檢測,即最近最久未使用的頁面,往前一直尋找合適的 item 予以淘汰。因此該 LRU 算法爲 slab 局部 class 淘汰的機制。可是在一些特定情形也會可能引發一些沒必要要的麻煩,能夠在運行時加入」-M」參數禁止該算法。

  (3)分佈式

    增長多臺Memcached服務器,能夠把訪問的壓力分配到各個不一樣的服務器上,因爲各個Memcached服務器之間互相不通訊,都是獨立的存取數據,不共享任何信息。因此經過對客戶端的設計,讓Memcached具備分佈式,支持    海量緩存和大規模應用。。memcached分佈式算法由客戶端API程序決定和實現的,(api根據存儲用的key以及已知的服務器列表,根據keyhash計算將指定的key存儲到對應的服務器列表上),這種分佈式是memcached的最    大特色。

    起初算法:根據餘數計算分散。

    機制:鍵值HashCode / 服務器節點數目 =(求餘服務器位置。【哈希原理: 兩個集合間的映射關係函數,在咱們一般的應用中基本上能夠理解爲 在集合A(任意字母數字等組合,此處爲存儲用的key)裏的一條記錄去查找集合B(如0-2^32)中的對應記錄。(題外話:md5的碰撞或者說衝突其實 就是發生在這裏,也就是說多個A的記錄映射到了同一個B的記錄)】當選擇的服務器沒法鏈接時,則將鏈接次數添加到鍵值後,再計算哈希值並再次嘗試 (Rehash)鏈接選擇的服務器。

    這種方法很好,可是當增刪服務器時,緩存的命中率的確很失敗,大概下降到23%web應用中,增刪緩存服務器瞬間,緩存效率會急劇降低,負載會忽然集中到數據庫服務器。因而,新的事物誕生了。

    優化後的算法:一致性哈希(Consistent Hashing

    首先求出每一個服務節點的hash,並將其配置到一個0~2^32的圓環(continuum)區間上。其次使用一樣的方法求出你所須要存儲的keyhash,也將其配置到這個圓環(continuum)上。

    而後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務節點上。若是超過2^32仍然找不到服務節點,就會保存到第一個memcached服務節點上。

    首先求出memcached服務器節點的哈希值,並將其配置到0232次方的環上,而後用一樣的方法求出數據鍵值的哈希值並映射到環上,而後從數據被映射到環的位置開始順時針查找服務器位置,將數據保存在第一個找到的服務器上。若是沒有找到則保持在第一個服務器上。

    

  Redis篇:

    Redis不只僅能夠做爲緩存中間件,還有其餘不少的用途,固然,它的許多特性也特別吸引我,Redis 是徹底開源免費的,遵照BSD協議,先進的key - value持久化產品。它一般被稱爲數據結構服務器,由於值(value)能夠是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型。主要和Memcached不同的地方就是提供更多的數據類型和看成爲分佈式的時候,Redis服務器之間能夠相互通訊。這裏對於Redis的數據類型不做詳細描述,畢竟關於Redis的資料也不少。我比較感興趣的Redis是做爲nosql服務器的一個優勢:內存數據的持久化

     內存快照是默認的持久化方式:將內存中的數據以快照方式寫入二進制文件中,默認文件名爲dump.rdb、客戶端用save或者bgsave命令告訴Redis作一次內存快照。Redis由單線程處理全部請求,執行save命令可能阻塞其餘客戶端請求,從而致使不能快速響應請求

    下面介紹詳細的快照保存過程

      1.redis調用fork,如今有了子進程和父進程。

      2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。因爲os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面建立副本,而不是寫共享的頁面。因此子進程的地址空間內的數 據是fork時刻整個數據庫的一個快照。

      3.當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,而後子進程退出。

client 也可使用save或者bgsave命令通知redis作一次快照持久化。save操做是在主線程中保存快照的,因爲redis是用一個主線程來處理全部 client的請求,這種方式會阻塞全部client請求。因此不推薦使用。另外一點須要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並不 是增量的只同步髒數據。若是數據量大的話,並且寫操做比較多,必然會引發大量的磁盤io操做,可能會嚴重影響性能。

另外因爲快照方式是在必定間隔時間作一次的,因此若是redis意外down掉的話,就會丟失最後一次快照後的全部修改。若是應用要求不能丟失任何修改的話,能夠採用aof持久化方式。

      下面介紹Append-only file

      aof 比快照方式有更好的持久化性,是因爲在使用aof持久化方式時,redis會將每個收到的寫命令都經過write函數追加到文件中(默認是 appendonly.aof)。當redis重啓時會經過從新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。固然因爲os會在內核中緩存 write作的修改,因此可能不是當即寫到磁盤上。這樣aof方式的持久化也仍是有可能會丟失部分修改。不過咱們能夠經過配置文件告訴redis咱們想要 經過fsync函數強制os寫入到磁盤的時機。有三種方式以下(默認是:每秒fsync一次)

      appendonly yes              //啓用aof持久化方式

      # appendfsync always      //每次收到寫命令就當即強制寫入磁盤,最慢的,可是保證徹底的持久化,不推薦使用

      appendfsync everysec     //每秒鐘強制寫入磁盤一次,在性能和持久化方面作了很好的折中,推薦

      # appendfsync no    //徹底依賴os,性能最好,持久化沒保證

      aof 的方式也同時帶來了另外一個問題。持久化文件會變的愈來愈大。例如咱們調用incr test命令100次,文件中必須保存所有的100條命令,其實有99條都是多餘的。由於要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照相似的方式將內存中的數據 以命令的方式保存到臨時文件中,最後替換原來的文件。具體過程以下

      1. redis調用fork ,如今有父子兩個進程

      2. 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令

      3.父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證若是子進程重寫失敗的話並不會出問題。

      4.當子進程把快照內容寫入已命令方式寫到臨時文件中後,子進程發信號通知父進程。而後父進程把緩存的寫命令也寫入到臨時文件。

      5.如今父進程可使用臨時文件替換老的aof文件,並重命名,後面收到的寫命令也開始往新的aof文件中追加。

      須要注意到是重寫aof文件的操做,並無讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點相似。

      fromhttp://www.cnblogs.com/xhan/archive/2011/02/04/1949151.html

  MongoDB篇:

    稍後總結。 

5、數據庫(Mysql)緩存

  數據庫像Mysql有不少性能優化的方案,這裏主要研究一下數據庫的緩存,好比說查詢緩存:對於相同類型的SQL就能夠跳過SQL解析和執行計劃生成階段。MySQL在某些場景下也能夠實現,可是「MySQL還有另外一種不一樣的緩存類型:緩存完整的SELECT 查詢結果。幾個問題:

  1.MySQL如何判斷緩存命中

     緩存存放在一個引用表中,經過一個哈希值引用。這個哈希值包括了以下因素,即查詢自己、當前要查詢的數據庫、客戶端協議的版本等。若是查詢中包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表,或者任何包含列級別權限的表,都不會被緩存。

     有些問題:打開查詢緩存對讀和寫操做都會帶來額外的消耗:讀查詢開始以前必須先檢查是否命中緩存,寫緩存。對查詢緩存操做是一個加鎖排他操做,若是查詢緩存使用了很大量的查詢結果,那麼緩存失效操做時整個系統均可能會僵死一下子。

   2.查詢緩存如何使用內存

      除了查詢結果以外,須要緩存的還有不少別的維護相關的數據。這和文件系統有些相似:須要一些內存專門用來肯定哪些內存目前是可用的、哪些已經用掉、哪些用來存儲數據表和查詢結果以前的映射、哪些用來存儲查詢字符串和查詢結果。當有查詢結果須要緩存的時候,MySQL先從大的空間塊中申請一個數據塊用於存儲結果。這個數據塊須要大於參數query_cache_min_res_unit的配置。由於須要先鎖住空間塊,而後找到合適大小數據塊,相對來講,分配內存塊是一個很是慢的操做。當須要緩存一個查詢結果的時候,它先選擇一個儘量小的內存快。MySQL本身管理一大塊內存。不依賴操做系統的內存管理.

   3.什麼狀況下查詢緩存能發揮做用

      跟服務器壓力模型有關:一個判斷緩存的直接數據是命中率:要麼增長Qcache_hits的值,要麼增長Com_select的值。

   4.通用查詢緩存優化

      (1)用多個小表代替一個大表對查詢緩存有好處。更合適的粒度。

      (2)批量寫入時只須要作一次緩存失效

      (3)控制緩存空間的大小

      (4)SQL_CACHE控制某個語句是否緩存

 

  小結:除了上面說的緩存方法,其實還有不少緩存手段,好比說模版緩存,文件緩存等等,還有像java語言中的對象緩存,Session緩存等等都是比較期待的能提高訪問速度的手段。有人說那所有用上不就行了嗎?其實什麼東西都是有利弊的,要根據實際狀況來判斷是否採起哪一種手段來進行緩存。上面還有不少不足之處,我也會繼續學習和完善的。

相關文章
相關標籤/搜索