首頁 後端開發 php教程 程式設計中快取的使用

程式設計中快取的使用

Jan 24, 2017 am 10:49 AM

快取是優化系統效能最常用的方式之一,透過在耗時元件(如資料庫)之前添加緩存,可以減少實際呼叫次數,降低迴應時間。但是在引入快取之前,務必三思而後行。

透過Internet取得資源既緩慢,成本又高。為此,Http協定包含了控制快取的部分,以使Http客戶端可以快取和重複使用先前取得的資源,從而優化效能,提升體驗。雖然Http中關於快取控制的部分,隨著協定演進,有些變化。但我覺著,身為後端程式設計師,在開發Web服務時,只需要關注請求頭If-None-Match、回應頭ETag、回應頭Cache-Control就夠了。因為這三個Http頭就可以滿足你的需求,而且,當今絕大多數的瀏覽器,都支援這三個Http頭。我們要做的就是,確保每個伺服器回應都提供正確的 HTTP 頭指令,以指導瀏覽器何時可以快取回應以及可以快取多久。

緩存在哪裡?

程式設計中快取的使用

上圖中有三個角色,瀏覽器、Web代理和伺服器,如圖所示HTTP快取存在於瀏覽器和Web代理程式中。當然在伺服器內部,也存在著各種緩存,但這已經不是本文要討論的Http快取了。所謂的Http快取控制,就是一種約定,透過設定不同的回應頭Cache-Control來控制瀏覽器和Web代理程式對快取的使用策略,透過設定請求頭If-None-Match和回應頭ETag,來對緩存的有效性進行驗證。

響應頭ETag

ETag全稱Entity Tag,用來識別一個資源。在具體的實作中,ETag可以是資源的hash值,也可以是一個內部維護的版本號碼。但不管怎樣,ETag應該能反映出資源內容的變化,而這正是Http快取可以正常運作的基礎。

程式設計中快取的使用

如上例中所展示的,伺服器在返回回應時,通常會在Http頭中包含一些關於回應的元資料訊息,其中,ETag就是其中一個,本例中傳回了值為x1323ddx的ETag 。當資源/file的內容發生變化時,伺服器應傳回不同的ETag。

請求頭If-None-Match

對於同一個資源,例如上一例中的/file,在進行了一次請求之後,瀏覽器就已經有了/file的一個版本的內容,和這個版本的ETag ,當下次使用者再需要這個資源,瀏覽器再次向伺服器請求的時候,可以利用請求頭If-None-Match來告訴伺服器自己已經有個ETag為x1323ddx的/file,這樣,如果伺服器上的/file沒有變化,也就是說伺服器上的/file的ETag也是x1323ddx的話,伺服器就不會再回傳/file的內容,而是回傳一個304的回應,告訴瀏覽器該資源沒有變化,快取有效。

程式設計中快取的使用

如上例所示,在使用了If-None-Match之後,伺服器只需要很小的回應就可以達到相同的結果,從而優化了效能。

回應頭Cache-Control

每個資源都可以透過Http頭Cache-Control來定義自己的快取策略,Cache-Control控制誰在什麼條件下可以快取回應以及可以快取多久。 最快的請求是不必與伺服器進行通訊的請求:透過回應的本地副本,我們可以避免所有的網路延遲以及資料傳輸的資料成本。為此,HTTP 規格允許伺服器傳回一系列不同的 Cache-Control 指令,控制瀏覽器或其他中繼快取如何快取某個回應以及快取多久。

Cache-Control 頭在 HTTP/1.1 規格中定義,取代了先前用來定義回應快取策略的頭(例如 Expires)。目前的所有瀏覽器都支援 Cache-Control,因此,使用它就足夠了。

以下我來介紹可以再Cache-Control中設定的常用指令。

max-age

該指令指定從目前請求開始,允許取得的回應被重複使用的最長時間(單位為秒。例如:Cache-Control:max-age=60表示回應可以再快取和重複使用60 秒。發生了變化,那麼瀏覽器將不能得到通知,而使用舊版的資源。 所以在設定快取時間的長度時,需要慎重。或任何中繼的Web代理中緩存,public是預設值,即Cache-Control:max-age=60等同於Cache-Control:public, max-age=60。 :private, max-age=60的情況下,表示只有使用者的瀏覽器可以快取private回應,不允許任何中繼Web代理對其進行快取– 例如,使用者瀏覽器可以快取包含使用者私人資訊的HTML 網頁,但是CDN 不能快取。被更改,如果資源未被更改,可以避免下載。 -cache這個名字有一點誤導。 no-cache,而ETag的實作沒有反應出資源的變化,那就會導致瀏覽器的快取資料一直得不到更新的情況。 Cache-Control:no-store,那麼瀏覽器和任何中繼的Web代理,都不會儲存這次對應的資料。 。啟動很慢,最後發現是其中一個依賴的服務回應時間很長,這時該怎麼辦?

通常來說,遇到這類問題,表示這個依賴服務無法滿足需求。如果這是一個第三方服務,控制權不在自己手上,這時我們可能會引入快取。

此時引入快取的問題,是快取失效策略難以生效,因為快取設計的本意就是盡可能少的請求依賴的服務。

過早緩存

這裡提到“早”,不是應用程式的生命週期,而是開發的週期。有的時候我們會看見,有些開發者在開發初期就已經估算出系統瓶頸,並引進快取。

事實上,這樣的做法掩蓋了可能進行效能最佳化的點。反正到時候這個服務的回傳值會被快取住,我幹嘛還要花時間去優化這部分程式碼呢?

整合快取

SOLID原則中的「S」代表-單一功能原則(Single responsibility principle)。當應用程式整合快取模組之後,快取模組和服務層就有了強耦合,無法在沒有快取模組的參與下單獨運作。

快取所有內容

有的時候為了降低迴應延遲,可能會盲目的對外部呼叫都加上快取。事實上,這樣的行為很容易讓開發者和維護者無法意識到快取模組的存在,最終對底層依賴模組的可靠性做出了錯誤的評估。

級聯快取

快取所有內容,或只是快取了大部分內容,可能會導致快取資料中包含其他快取資料。 程式設計中快取的使用

如果應用程式中包含這種級聯的快取結構,可能導致的情況是快取失效時間不可控。最上層的快取需要等每一層快取都失效更新之後,最終回傳的資料才會徹底更新。

不可刷新快取

通常情況下,快取中間件會提供一個刷新快取的工具。例如Redis,維護人員可以透過其提供的工具,刪除部分數據,甚至刷新整個快取。

但是,一些臨時緩存,可能不會包含這樣的工具。例如簡單的將資料保存在內容中的緩存,通常不會允許外部工具來修改或刪除快取內容。這時,如果發現快取資料異常,維護人員只能採取重新啟動服務的方式,這將大大增加維運成本和回應時間。更有甚者,有些快取可能會將快取內容寫在檔案系統中備份。此時除了重新啟動服務,還需要確保應用程式啟動先前刪除檔案系統上的快取備份。

快取帶來的影響

上面提到了引入快取可能導致的常見錯誤,這些問題在無快取系統中透過不會考慮。

部署一個重度依賴快取的系統,可能會因為等待快取失效而花費大量時間。例如透過CDN快取內容,系統發布之後去刷新CDN配置、CDN快取的內容,可能需要幾個小時。

另外,出現效能瓶頸優先考慮緩存,會導致效能問題被掩蓋,無法得到真正的解決。事實上,很多時候調優程式碼花費的時間,和引入快取元件不會相差太多。

最後,對於包含快取組件的系統,調試成本會大大增加。經常會發生追蹤半天代碼,結果資料來自緩存,和實際邏輯上應該依賴的元件沒有任何關係。同樣的問題也可能出現在執行了所有相關測試案例之後,修改到的程式碼實際上沒有被測試到。

如何用好快取?

放棄快取!

好吧,很多時候快取是無法避免的。基於互聯網的系統,很難完全避免使用快取,甚至連http協定頭,都包含快取配置:Cache-Control: max-age=xxx。

了解資料

如果要將資料存取緩存,首先需要了解資料更新策略。只有明確了解數據何時需要更新,才能透過If-Modified-Since頭來判斷客戶端請求的數據是否需要更新,是簡單返回304 Not Modified響應讓客戶端復用之前的本地緩存數據,還是返回最新數據。另外,為了更好利用http協定中的緩存,建議給資料區分版本,或是利用eTag來標記快取資料的版本。

優化效能而不是使用快取

前文提到過,使用快取往往會將潛在效能問題掩蓋。盡可能利用效能分析工具,找到應用程式響應緩慢的真實原因並且修復它。例如減少無效程式碼調用,根據SQL執行計畫優化SQL等。

下面是清除應用程式所有快取的程式碼

/* 
 * 文 件 名:  DataCleanManager.java 
 * 描   述:  主要功能有清除内/外缓存,清除数据库,清除sharedPreference,清除files和清除自定义目录 
 */  
package com.test.DataClean;  
  
import java.io.File;  
  
import android.content.Context;  
import android.os.Environment;  
  
/** 
 * 本应用数据清除管理器 
 */  
public class DataCleanManager {  
    /** 
     * 清除本应用内部缓存(/data/data/com.xxx.xxx/cache) 
     *  
     * @param context 
     */  
    public static void cleanInternalCache(Context context) {  
        deleteFilesByDirectory(context.getCacheDir());  
    }  
  
    /** 
     * 清除本应用所有数据库(/data/data/com.xxx.xxx/databases) 
     *  
     * @param context 
     */  
    public static void cleanDatabases(Context context) {  
        deleteFilesByDirectory(new File("/data/data/"  
                + context.getPackageName() + "/databases"));  
    }  
  
    /** 
     * 清除本应用SharedPreference(/data/data/com.xxx.xxx/shared_prefs) 
     *  
     * @param context 
     */  
    public static void cleanSharedPreference(Context context) {  
        deleteFilesByDirectory(new File("/data/data/"  
                + context.getPackageName() + "/shared_prefs"));  
    }  
  
    /** 
     * 按名字清除本应用数据库 
     *  
     * @param context 
     * @param dbName 
     */  
    public static void cleanDatabaseByName(Context context, String dbName) {  
        context.deleteDatabase(dbName);  
    }  
  
    /** 
     * 清除/data/data/com.xxx.xxx/files下的内容 
     *  
     * @param context 
     */  
    public static void cleanFiles(Context context) {  
        deleteFilesByDirectory(context.getFilesDir());  
    }  
  
    /** 
     * 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache) 
     *  
     * @param context 
     */  
    public static void cleanExternalCache(Context context) {  
        if (Environment.getExternalStorageState().equals(  
                Environment.MEDIA_MOUNTED)) {  
            deleteFilesByDirectory(context.getExternalCacheDir());  
        }  
    }  
  
    /** 
     * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 
     *  
     * @param filePath 
     */  
    public static void cleanCustomCache(String filePath) {  
        deleteFilesByDirectory(new File(filePath));  
    }  
  
    /** 
     * 清除本应用所有的数据 
     *  
     * @param context 
     * @param filepath 
     */  
    public static void cleanApplicationData(Context context, String... filepath) {  
        cleanInternalCache(context);  
        cleanExternalCache(context);  
        cleanDatabases(context);  
        cleanSharedPreference(context);  
        cleanFiles(context);  
        for (String filePath : filepath) {  
            cleanCustomCache(filePath);  
        }  
    }  
  
    /** 
     * 删除方法 这里只会删除某个文件夹下的文件,如果传入的directory是个文件,将不做处理 
     *  
     * @param directory 
     */  
    private static void deleteFilesByDirectory(File directory) {  
        if (directory != null && directory.exists() && directory.isDirectory()) {  
            for (File item : directory.listFiles()) {  
                item.delete();  
            }  
        }  
    }  
}
登入後複製

總結

快取是非常有用的工具,但極易被濫用。不到最後一刻不要使用緩存,優先考慮使用其他方式優化應用程式效能。


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

解釋PHP中晚期靜態結合的概念。 解釋PHP中晚期靜態結合的概念。 Mar 21, 2025 pm 01:33 PM

文章討論了PHP 5.3中介紹的PHP中的晚期靜態結合(LSB),允許靜態方法的運行時間分辨率調用以更靈活的繼承。 LSB的實用應用和潛在的觸摸

框架安全功能:防止漏洞。 框架安全功能:防止漏洞。 Mar 28, 2025 pm 05:11 PM

文章討論了框架中的基本安全功能,以防止漏洞,包括輸入驗證,身份驗證和常規更新。

描述紮實的原則及其如何應用於PHP的開發。 描述紮實的原則及其如何應用於PHP的開發。 Apr 03, 2025 am 12:04 AM

SOLID原則在PHP開發中的應用包括:1.單一職責原則(SRP):每個類只負責一個功能。 2.開閉原則(OCP):通過擴展而非修改實現變化。 3.里氏替換原則(LSP):子類可替換基類而不影響程序正確性。 4.接口隔離原則(ISP):使用細粒度接口避免依賴不使用的方法。 5.依賴倒置原則(DIP):高低層次模塊都依賴於抽象,通過依賴注入實現。

自定義/擴展框架:如何添加自定義功能。 自定義/擴展框架:如何添加自定義功能。 Mar 28, 2025 pm 05:12 PM

本文討論了將自定義功能添加到框架上,專注於理解體系結構,識別擴展點以及集成和調試的最佳實踐。

如何用PHP的cURL庫發送包含JSON數據的POST請求? 如何用PHP的cURL庫發送包含JSON數據的POST請求? Apr 01, 2025 pm 03:12 PM

使用PHP的cURL庫發送JSON數據在PHP開發中,經常需要與外部API進行交互,其中一種常見的方式是使用cURL庫發送POST�...

如何在系統重啟後自動設置unixsocket的權限? 如何在系統重啟後自動設置unixsocket的權限? Mar 31, 2025 pm 11:54 PM

如何在系統重啟後自動設置unixsocket的權限每次系統重啟後,我們都需要執行以下命令來修改unixsocket的權限:sudo...

See all articles