プログラミングにおけるキャッシュの使用

伊谢尔伦
リリース: 2023-03-05 10:12:01
オリジナル
1184 人が閲覧しました

キャッシュは、システムのパフォーマンスを最適化する最も一般的な方法の 1 つで、時間のかかるコンポーネント (データベースなど) の前にキャッシュを追加することで、実際の呼び出しの数を減らし、応答時間を短縮できます。ただし、キャッシュを導入する前に必ずよく考えてください。

インターネット経由でリソースを取得するには時間がかかり、コストがかかります。このため、HTTP プロトコルにはキャッシュ制御部分が含まれており、HTTP クライアントが以前に取得したリソースをキャッシュして再利用できるため、パフォーマンスが最適化され、エクスペリエンスが向上します。ただし、プロトコルの進化に伴い、HTTP のキャッシュ制御部分にはいくつかの変更が加えられています。しかし、バックエンド プログラマとして Web サービスを開発する場合、リクエスト ヘッダーの If-None-Match、レスポンス ヘッダーの ETag、レスポンス ヘッダーの Cache-Control だけを意識すればよいと感じています。これら 3 つの HTTP ヘッダーはニーズを満たすことができ、今日のほとんどのブラウザーはこれら 3 つの HTTP ヘッダーをサポートしているためです。私たちがしなければならないことは、各サーバー応答が、応答をキャッシュできる時期と期間をブラウザーに指示するための正しい HTTP ヘッダー ディレクティブを提供していることを確認することだけです。

キャッシュはどこにありますか?

プログラミングにおけるキャッシュの使用

上の図にはブラウザ、Webプロキシ、サーバーの3つの役割があります。図に示すように、HTTPキャッシュはブラウザとWebプロキシに存在します。もちろん、サーバー内にもさまざまなキャッシュがありますが、これはこの記事で説明する HTTP キャッシュではありません。いわゆる Http キャッシュ制御は、異なる応答ヘッダー Cache-Control を設定することによってブラウザーと Web プロキシのキャッシュ使用ポリシーを制御し、リクエスト ヘッダー If-None-Match と応答ヘッダー ETag を設定することによってキャッシュを制御するための協定です。効果を検証します。

レスポンスヘッダETag

エンティティタグの完全名であるETagは、リソースを識別するために使用されます。特定の実装では、ETag はリソースのハッシュ値、または内部的に維持されるバージョン番号にすることができます。しかし、何があっても、ETag はリソース コンテンツの変更を反映できる必要があります。これは、HTTP キャッシュが適切に機能するための基礎です。

プログラミングにおけるキャッシュの使用

上記の例に示すように、サーバーが応答を返すとき、通常、HTTP ヘッダーには応答に関するいくつかのメタデータ情報が含まれています。この例では、ETag がその 1 つです。 x1323ddx が返されます。リソース/ファイルのコンテンツが変更されると、サーバーは別の ETag を返す必要があります。

リクエスト ヘッダー If-None-Match

同じリソース (前の例の /file など) について、リクエストを行った後、ブラウザーには既に /file のコンテンツのバージョンとこのバージョンの ETag が存在します。ユーザーが次回もこのリソースを必要とし、ブラウザーが再度サーバーを要求すると、要求ヘッダー If-None-Match を使用して、x1323ddx の ETag を持つ /file が既に存在することをサーバーに伝えることができます。サーバー上の /file は変更されません。つまり、サーバー上の /file の ETag も x1323ddx の場合、サーバーは /file のコンテンツを返さず、ブラウザーに次のことを伝える 304 応答を返します。リソースは変更されておらず、キャッシュは有効です。

プログラミングにおけるキャッシュの使用

上記の例に示すように、If-None-Match を使用した後、サーバーは同じ結果を達成するために小さな応答のみを必要とするため、パフォーマンスが最適化されます。

レスポンス ヘッダー Cache-Control

各リソースは、HTTP ヘッダー Cache-Control を通じて独自のキャッシュ戦略を定義でき、誰がどのような条件でレスポンスをキャッシュできるか、およびキャッシュできる期間を制御します。 最も速いリクエストは、サーバーと通信する必要のないリクエストです。レスポンスのローカル コピーを使用することで、すべてのネットワーク遅延とデータ転送のデータ コストを回避できます。この目的を達成するために、HTTP 仕様では、ブラウザまたは他のリレー キャッシュが応答をキャッシュする方法と期間を制御する一連の異なる Cache-Control ディレクティブをサーバーが返すことができます。

Cache-Control ヘッダーは HTTP/1.1 仕様で定義され、以前に応答キャッシュ ポリシー (Expires など) を定義するために使用されていたヘッダーを置き換えます。現在のすべてのブラウザは Cache-Control をサポートしているため、それを使用するだけで十分です。

ここではCache-Controlで設定できる一般的なコマンドを紹介します。

max-age

このディレクティブは、現在のリクエストから開始して取得されたレスポンスの再利用が許可される最大時間 (秒単位) を指定します。 例: Cache-Control:max-age=60 は、レスポンスが再利用できることを意味します。キャッシュされ、さらに 60 秒間再利用されます。max-age で指定された時間内では、ブラウザは、キャッシュが有効であるかどうかを確認するリクエストを含め、サーバーにリクエストを送信しないことに注意してください。この期間内にサーバー上のリソースに変更が発生しても、ブラウザーには通知されず、古いバージョンのリソースが使用されるため、キャッシュ時間の長さを設定する際には注意が必要です。 public と private

public が設定されている場合、応答はブラウザーで使用できるか、中継された Web プロキシにキャッシュできることを意味します。public がデフォルト値です。つまり、Cache-Control:max-age=60 と同等です。 Cache-Control:public, max-age=60 に設定します。

サーバー上で Cache-Control などのプライベートを設定すると、ユーザーのブラウザのみがプライベート応答をキャッシュでき、リレー Web はキャッシュされなくなります。プロキシはそれをキャッシュすることができます。たとえば、ユーザーのブラウザはユーザーの個人情報を含む HTML Web ページをキャッシュできますが、サーバーが応答で no-cache を設定した場合、CDN はキャッシュできません。 、キャッシュ制御: キャッシュなし、ブラウザは、キャッシュされたリソースを使用する前に、最初に応答が返されたかどうかをサーバーに確認する必要があります。リソースが変更されていない場合は、以前の応答が返されたかどうかのこの検証を回避できます。変更はリクエスト ヘッダー If-None-match とレスポンス ヘッダー ETag によって実現されます

no という名前は少し誤解を招きやすいので、no-cache を設定した後、ブラウザがキャッシュを無効にするという意味ではないことに注意してください。キャッシュ データは長くなりますが、キャッシュされたデータを使用する場合、ブラウザはまずデータがキャッシュなしと一致しているかどうかを確認する必要があり、ETag の実装にはリソースの変更が反映されないため、ブラウザのキャッシュ データが変更されます。

no-store

サーバーが応答に no-store を設定すると、ブラウザと中継された Web プロキシは今度は対応するデータを保存しません。次回リクエストされた場合、ブラウザは再度サーバーをリクエストし、サーバーからリソースを再度読み取るだけです。

次のフローチャートが役に立ちます。

起動時のキャッシュ

場合によっては、アプリケーションの起動が非常に遅く、最終的に依存サービスの 1 つが応答に時間がかかることが判明することがあります。

一般的に、この種の問題が発生した場合、それは依存サービスが需要に応えることができないことを意味します。これがサードパーティのサービスであり、当社が制御できない場合は、現時点でキャッシュを導入する可能性があります。

現時点でキャッシュを導入する場合の問題は、キャッシュの無効化戦略を有効にするのが難しいことです。キャッシュ設計の本来の目的は、依存するサービスをできるだけ少なくすることであるためです。

プログラミングにおけるキャッシュの使用時期尚早なキャッシュ

ここで言う「初期」とは、アプリケーションのライフサイクルではなく、開発サイクルのことです。開発者の中には、システムのボトルネックを推定し、開発の初期段階でキャッシュを導入している人もいます。 実際、このアプローチでは、潜在的なパフォーマンス最適化ポイントが隠蔽されます。いずれにせよ、このサービスの戻り値はそれまでにキャッシュされるでしょう。なぜコードのこの部分の最適化に時間を費やす必要があるのでしょうか。

統合キャッシュ

SOLID 原則の「S」は、単一責任原則を表します。アプリケーションがキャッシュ モジュールを統合すると、キャッシュ モジュールとサービス層は強く結合され、キャッシュ モジュールの参加なしでは独立して実行できなくなります。

すべてのコンテンツをキャッシュする

場合によっては、応答遅延を減らすために、外部呼び出しに盲目的にキャッシュを追加することがあります。実際、そのような動作により、開発者や保守者がキャッシュ モジュールの存在に気づくことが容易に妨げられ、最終的には基礎となる依存モジュールの信頼性について誤った評価が行われる可能性があります。

カスケード キャッシュ

すべてのコンテンツ、またはほとんどのコンテンツのみをキャッシュすると、キャッシュされたデータに他のキャッシュ データが含まれる可能性があります。

アプリケーションにこのカスケード キャッシュ構造が含まれている場合、キャッシュの有効期限が制御できない状況が発生する可能性があります。最上位キャッシュは、最終的に返されたデータが完全に更新される前に、キャッシュの各レベルが無効化されて更新されるまで待つ必要があります。

キャッシュを更新できません

通常、キャッシュミドルウェアはキャッシュを更新するツールを提供します。たとえば、Redis を使用すると、保守者は、提供されるツールを通じて一部のデータを削除したり、キャッシュ全体を更新したりすることもできます。

ただし、一部の一時キャッシュにはそのようなツールが含まれていない場合があります。たとえば、コンテンツ内にデータを保存するだけのキャッシュでは、通常、外部ツールがキャッシュされたコンテンツを変更または削除することはできません。このとき、異常なキャッシュデータが見つかった場合、保守員はサービスを再起動することしかできず、運用保守コストと応答時間が大幅に増加します。さらに、一部のキャッシュは、バックアップのためにキャッシュの内容をファイル システムに書き込む場合があります。このとき、サービスを再起動するだけでなく、アプリケーションを開始する前にファイル システム上のキャッシュ バックアップが削除されていることを確認する必要もあります。

キャッシュの影響

キャッシュの導入によって発生する可能性のある一般的なエラーは上記で説明されていますが、これらの問題はキャッシュなしのシステムでは考慮されません。

キャッシュに大きく依存するシステムを導入すると、キャッシュの有効期限が切れるのを待つのに多くの時間がかかる場合があります。たとえば、CDN 経由でコンテンツをキャッシュする場合、システムのリリース後に CDN 構成と CDN によってキャッシュされたコンテンツを更新するのに数時間かかることがあります。

さらに、パフォーマンスのボトルネックが発生した場合、キャッシュを優先すると、パフォーマンスの問題が隠蔽され、真の解決にはなりません。実際、多くの場合、コードのチューニングに費やす時間は、キャッシュ コンポーネントの導入とそれほど変わりません。

最後に、キャッシュ コンポーネントを含むシステムの場合、デバッグ コストが大幅に増加します。コードが半日にわたってトレースされ、結果として得られるデータはキャッシュから得られ、実際のロジックが依存するコンポーネントとは何の関係もないことがよくあります。関連するテスト ケースがすべて実行されたが、変更されたコードが実際にはテストされていない場合にも、同じ問題が発生する可能性があります。

キャッシュを有効に活用するにはどうすればよいですか?

キャッシュを削除!

まあ、多くの場合、キャッシュは避けられません。インターネットベースのシステムでは、http プロトコル ヘッダーにキャッシュ設定が含まれていても、キャッシュの使用を完全に回避することは困難です。Cache-Control: max-age=xxx。

データを理解する

キャッシュ内のデータにアクセスしたい場合は、まずデータ更新戦略を理解する必要があります。データをいつ更新する必要があるかを明確に理解することによってのみ、クライアントが要求したデータを更新する必要があるかどうかを If-Modified-Since ヘッダーを使用して判断できます。単純に 304 Not Modified 応答を返し、クライアントに 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();  
            }  
        }  
    }  
}
ログイン後にコピー

概要

キャッシュは非常に便利なツールですが、簡単に悪用される可能性があります。最後の瞬間までキャッシュを使用せず、アプリケーションのパフォーマンスを最適化する他の方法を優先してください。


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!