《構建高性能的web站點》讀書筆記--緩存


      其實在說緩存之前,還有其它關於網絡和服務器硬件、系統的基礎知識,其中在網絡一節中:着重介紹了網絡模型和帶寬的概念,提供了一個我們去計算一次網絡傳輸時間的方法,以及在當前聯通、電信網絡的情況下,如何部署服務器,做好互聯互通。在服務器硬件、系統能力方面,突出了一個服務器能力的指標:吞吐率,介紹了各個主要部件和系統的基礎知識。了解並熟悉這方面的知識,對我們構建一個優秀的系統是不可缺少的,筆者所有的這些這方面的知識也是各種資料,沒有太多的實踐的經歷,這里就不摘抄了,推薦大家去看此書或其它相關資料了解這方面的知識。

      下面進入本篇的主題,緩存我們再熟悉不過了,不僅體現空間換時間的體現,也能節省不少的資源開銷,提高服務器吞吐率,但是使用所有緩存之前,我們要做壽好更新或過期策略,並且任何時候都要做好去重新計算的准備。

 

首先,從網站方面說一下緩存的應用場景:

1,動態內容緩存

      無論你的頁面是php、aspx或jsp,最后輸出內容都必須是html,而這些動態頁面都要通過一系列的計算最終輸出html的結果,這樣我們就可以把這個結果緩存,當有下次相同的請求時,直接返回,這種緩存叫頁面緩存,在asp.net里我們可以輕松的實現全部或局部的頁面緩存(控件緩存)。

      而有些情況下,我們將緩存持久化到硬盤上,這樣我們可以很廉價的緩存大量的文件,像我們可以根據url參數不同的緩存不同的結果。但是如果相同緩存太多,就可能造成硬盤I/O開銷巨大,我們就要分組目錄或其它算法來分目錄:我們用時間來創建分級目錄,或根據物品ID對一個基數求余的結果來把物品信息緩存放到不同的目錄。

      除此之外,我們可以把結果(計算的結果或提取的數據)放到內存中,如.net的cache,或memcache,這些以key/value形式存在的緩存,這種恐怕應該是我們最常用的了。

      還有一種更徹底的方式:靜態化頁面,讓用戶直接訪問生成的html頁面,這種是所有緩存里效率最高的,因為用戶的請求不經過我們的程序直接返回,我們的程序只是在后台管理着這些頁面。

2,腳本加速

       書中稱之為opcode緩存,解釋型語言在執行時,由解釋器分析代碼之后,直接生成操作碼operate code,然后直接執行。這樣的語言主要有如php、python、ruby,所謂opcode緩存,就是像動態內容那樣,把operate code緩存起來,從而節省代碼解釋過程的時間,這些可以通過php的opcode緩存擴展如APC、xCache等來實現。

       而如C++的編譯就沒有這一過程,它編譯和運行是兩個過程,編譯成機器可以直接運行的目標程序之后,運行則是由目標程序來控制。

       我們知道C#也是一種編譯型語言,不過它的過程卻是比較不一樣的,它首先是一個C#編譯器生成IL,在真正執行時,IL被JIT編譯器再次生成機器碼來執行。而在IL編譯成可運行的代碼的過程中,它是以函數為單位編譯的,並且每個函數只會被編譯一次,以后的運行也是直接拿成生成后的機器碼來運行的,這與上面所說的opcode緩存是同一個思想,不過這不用我們去費力氣再去關心它,我們關心的如何利用這個機制?在effictive C#中有一條:盡量實現短小簡潔的函數,其實這也是我們程序時應該注意的一點,盡量提取相同代碼到方法,而不是分散的內聯代碼,同名函數只會被JIT編譯一次。

3,瀏覽器緩存

       這個可能大家都比較熟悉了,在園子里也看到有很多文章討論過。瀏覽器的緩存主要是對單個請求url來說的,如頁面和頁面的組件(圖片、CSS、腳本等)。相同的URL再次請求時,才有可能用到瀏覽器緩存。

       首先瀏覽器會把頁面的內容緩存到一個特定的位置,但是使用不使用確不是瀏覽器自作主張的,這里有一個協商的過程,而這些協調信息是包含在http的頭信息中的。

LAST-MODIFIED協商

一個url第一次請求,動態程序我們可以輕松在http頭信息中加入last-modified信息,而靜態文件,系統會自動把最后修改時間在http頭信息中,如Last-Modified: Wed, 13 Jun 2012 09:15:45 GMT;

如果再次請求這個url時,瀏覽器發現有此緩存時,就會再請求中加入:If-Modified-Since: Wed, 13 Jun 2012 09:15:45 GMT這樣格式,時間就是上次請求返回的最后修改時間;

這時如果是動態程序我們就可以檢查是否有改變,然后決定是輸出新內容或304,如果是靜態文件,系統會比較這個時間,返回200還是304;

瀏覽器接到響應,如果是304就使用瀏覽器的緩存,200則使用新接收的數據。

ETAG協商

這個請求的過程和LAST-MODIFIED是一樣的,只不過標識是使用etag,而不再是last-modified.

第一次請求時,你可以生成一個etag值,做為http頭信息:ETag: "b09ce81c4549cd1:155db" 發出;再次發出這個請求時,瀏覽器會在請求頭信息中加入:If-None-Match: "b09ce81c4549cd1:155d7";這樣你可以,再次比較這個值,來決定返回200或304.

 

     當然這兩種方式,結果是一樣的,要根據不同的場景來決定如何使用:如我們如果采用定期更新靜態文件策略來實現的靜態化,一些頁面經常會被重新生成也就是最后修改時間經常變化,但是內容可能沒有改變,這樣我們就可以內容生成的標識使用etag來實現緩存協商;如果我們有多台服務器,每一次請求可能是不同服務器來處理,用根據內容生成的etag值比最后修改時間更合適。

 

      此外,還有一個不得不說的那就是:我們還可以設置http輸出頭信息中的expires和Cache-Control,如Cache-Control: max-age=31536000,單位是s,來設置過期時間。就是下次的同一個url,瀏覽器會先檢查Cache-Control: max-age,這個是相對過期時間;如果不存在,檢查expires設置的時間,這是一個絕對的過期時間。

 

      實際上更多時候,我們不大可能准確判斷出這個過期時間,有一個良好的方法:設置一個較大的過期時間,如果文件有改變時,我們就替換一個新的文件名或者加一個新的參數,還讓瀏覽器去請求新的資源。

 

      服務器端擁有絕對的能力來決定瀏覽器是否使用緩存的文件,同樣瀏覽器不同的刷新方式也決定瀏覽器是否使用緩存:輸入url轉向,這個會使用緩存的全部功能,會使用過期時間的設置;f5/刷新,這不會考慮expires但會去詢問服務器是否可以使用緩存;ctrl+f5/強制刷新,不使用緩存,不詢問,直接請求文件。

4,服務器緩存

      說起這個緩存,我們一定要和之前的動態內容緩存區分開,上述的動態內容緩存,服務器接到一個url的請求,交到我們的動態程序,程序去判斷是否有請求內容的緩存,如果有,加入到輸出的html;而服務器緩存是,服務器接到一個url的請求,服務器發現有這個url可用的緩存,直接返回,這樣就不會再走我們精心構建的程序代碼。

5,反向代理緩存

     這個其實應該歸到服務器緩存里,但是這里有個反向代理的概念,需要說明一下。代理服務器,這個大家肯定都很清楚,平常所謂的“翻牆”:例如我們無法訪問網站A,而B可以訪問A,我們可以訪問B,我們就可以把B做為代理服務器,把對A的請求發給B,B去請求A,然后把A返回內容給B,B再把內容返回給我們。

     而反向代理服務器,就是服務器的代理,就是我們請求一個url,這個反向代理服務器,接到這個請求后,會根據請求的信息分發到相應的實際的請求處理服務器,實際服務器處理完畢,返給代理,再返給瀏覽器。

     這樣的代理服務器相當於一個中樞的位置,當然就可以緩存一些響應的內容,當有相同的請求時,不會到后端實際處理的服務器而直接把內容返回給瀏覽器。

 

寫操作的緩存(緩沖)

      上面所說的都是為了減少服務器讀資源使用,減少數據庫的讀操作或服務器的計算時間。其實我們可以用緩存來實現一些寫操作的緩沖:書中提到一個例子,例如我們對於頁面流量次數統計的這樣不太重要且不需要及時的數據,可以在緩存中緩存訪問次數,書中使用memcache實現,因為memcache支持原子操作,當次數到達1000或你設定的值時,才更新一次數據庫,這樣可以大減少即時更新的資源消耗,可以讓數據庫干更多的其它重要的事務。

 

分布式緩存和擴展

      隨着網站規模變大,可能會有單獨的緩存服務器,就以memcache為例,這也是我們最常用的選擇,越來越,當一台緩存器不能支持我們的日常運行,你可能會再加一台緩存服務器,我們可以按業務來划分:一台負責用戶相關緩存,一台用於物品相關緩存。越來越,當一台無法負責到用戶相關緩存,還可能再按業務拆分嗎,總之,我們將不可避免的會出現多台服務器用於緩存同類的數據項,但是我們應該把緩存放到哪個服務器,我們要取一個緩存項時,去哪個服務器去取呢?

     書中提到一個解決方案,運用基本的散列算法:我們把key值進行MD5運算,得到一個32位的散列碼,同時它也是一個16進制的長整數,這樣我們可以取前幾位轉換為長整數,然后對你緩存服務器的台數取余,來決定去哪台服務器進行該Key緩存的操作。

     按這樣的方法,如果我們再增加緩存服務器時呢,這樣就不得不做出犧牲,把之前的緩存數據清理掉,重新部署緩存規則:當然清理之前你可能要發出公告,並且暫停網站服務一會,進行升級,然后重新生成緩存或者等到用戶訪問時自動生成緩存。

 

最后

     我們不能只是為了使用緩存而去使用緩存,如何使用好緩存,使用緩存的時機、如何提高緩存的命中率、緩存的更新策略、過期策略以及緩存內容的監控等等一系列的問題要我們去考慮。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM