LRUポリシーによるAxiosリクエストキャッシュの実装


ビジネスシーン
前の时间はちょうど1つのプロジェクトを终えて、まず业务のシーンを言って、他の先端のプロジェクトとは异なって、今回のプロジェクトは直接第三者のサービスのインターフェースを呼び出して、私达のサービス端は鑑権と透伝だけをして、第三者は柔软のために、インターフェースをばらばらに分解して、このプロジェクトはあなたに楽高の粒子を投げてあなたをロボットに组み立てるようです.このプロジェクトのインタフェースを要求する際の特徴を大体分析し、適切に最適化することができます.
  • 要求インタフェースは多くて、あなたの1つのn個のエントリのリストはもともと1つのインタフェースが完成して今n*10個のインタフェースが必要で完全なデータを得ることができて、一部の機能モジュールは何千何万回もインタフェースを要求する必要があるかもしれません;
  • は基本的にgetリクエストで、読み取り専用で書かない.
  • インタフェース呼び出しの繰返し率が高く、インタフェースが細いため、よく使われるインタフェースの中には繰り返し呼び出しが必要なものもあるかもしれません.
  • インタフェースが返すデータのリアルタイム性の要求は高くなく、サードパーティのデータはリアルタイムで更新されず、1日または1週間に1回更新される可能性がありますが、サードパーティの要求はいかなる方法でもライブラリに落ちることはできません.

  • したがって、分析をまとめると、フロントエンドキャッシュは実行可能性の高い最適化スキームとなっている.
    ソリューション
    フロントエンドのHTTPリクエストはAxiosを使用しているので、Axiosのブロッキングを利用してキャッシュの管理を行うことができます.論理を整理します.
  • キャッシュオブジェクトを作成します.
  • リクエストが開始される前に、リクエストがキャッシュにヒットしたかどうかを判断します.
  • は、キャッシュ内容を直接返す.
  • いいえ、リクエストが開始され、リクエストが成功した後、リクエスト結果がキャッシュに格納されます.


  • タイトルのように、ここでのキャッシュポリシーはLRU(Least Recently Used)ポリシーを使用しています.キャッシュが無限に大きくならないため、キャッシュが大きすぎるとブラウザのページのパフォーマンスが低下したり、メモリが漏洩したりする可能性があります.LRUは、キャッシュが最大ベアラに達した後、最近最も使用されていないキャッシュ内容を削除するので、キャッシュが無限に増大する心配はありません.では、LRUキャッシュポリシーはどのように実現されますか?Githubには既製の車輪がありますが、もっと深く勉強するためには、自分で手動で実現しましょう.
    LRUの実装
    LRUには主に2つの機能があり、保存、取得します.論理を整理します.
  • 保存:
  • キャッシュがいっぱいになったら、最近最も少ないキャッシュ内容を削除し、現在のキャッシュを保存し、最もよく使われている位置に置く.
  • でなければ、キャッシュは最も一般的な場所に直接格納されます.

  • 読み込み:
  • このキャッシュが存在する場合、キャッシュ内容を返し、同時にそのキャッシュを最も一般的な位置に置く.
  • がない場合は、-1を返します.


  • ここでは、キャッシュに優先度がありますが、優先度を示すには何を使用しますか?配列で格納すると、あまり使わないものを配列の頭部に置くことができ、よく使われるものを末尾に置くことができます.しかし,データの挿入効率が低いことを考慮して,ここではコンテナストレージキャッシュとしてMapオブジェクトを用いる.
    コードは次のとおりです.
    class LRUCache {
        constructor(capacity) {
            if (typeof capacity !== 'number' || capacity < 0) {
                throw new TypeError('capacity        ');
            }
            this.capacity = capacity;
            this.cache = new Map();
        }
    
        get(key) {
            if (!this.cache.has(key)) {
                return -1;
            }
            let tmp = this.cache.get(key);
            //                
            this.cache.delete(key);
            this.cache.set(key, tmp);
            return tmp;
        }
    
        set(key, value) {
            if (this.cache.has(key)) {
                //             
                this.cache.delete(key);
            } else if (this.cache.size >= this.capacity) {
                //         ,           
                this.cache.delete(this.cache.keys().next.val);
            }
            this.cache.set(key, value);
        }
    }

    Axiosによるリクエストキャッシュの実装
    大体の論理を整理します:毎回要求して要求の方法、url、パラメータに基づいて一連のhashを生成して、キャッシュの内容はhash->responseで、後続の要求は要求の方法、url、パラメータが一致するならば、つまりキャッシュに命中すると思っています.
    コードは次のとおりです.
    import axios from 'axios';
    import md5 from 'md5';
    import LRUCache from './LRU.js';
    
    const cache = new LRUCache(100);
    
    const _axios = axios.create();
    
    //        ,         hash  
    function sortObject(obj = {}) {
        let result = {};
        Object.keys(obj)
            .sort()
            .forEach((key) => {
                result[key] = obj[key];
            });
    }
    
    //   request method,url,data/params  cache   
    function genHashByConfig(config) {
        const target = {
            method: config.method,
            url: config.url,
            params: config.method === 'get' ? sortObject(config.params) : null,
            data: config.method === 'post' ? sortObject(config.data) : null,
        };
        return md5(JSON.stringify(target));
    }
    
    _axios.interceptors.response.use(
        function(response) {
            //     
            const hashKey = genHashByConfig(response.config);
            cache.set(hashKey, response.data);
            return response.data;
        },
        function(error) {
            return Promise.reject(error);
        }
    );
    
    //  axios    ,            http  ,        
    export default function request({
        method,
        url,
        params = null,
        data = null,
        ...res
    }) {
        const hashKey = genHashByConfig({ method, url, params, data });
        const result = cache.get(hashKey);
        if (~result) {
            console.log('cache hit');
            return Promise.resolve(result);
        }
        return _axios({ method, url, params, data, ...res });
    }

    要求のカプセル化:
    import request from './axios.js';
    
    export function getApi(params) {
        return request({
            method: 'get',
            url: '/list',
            params,
        });
    }
    
    export function postApi(data) {
        return request({
            method: 'post',
            url: '/list',
            data,
        });
    }

    ここで注意すべき点は,要求方法,url,パラメータをhash操作し,パラメータの順序変更によるhash結果の不一致を防止するために,hash操作の前にパラメータのソート処理を行ったが,実際の開発ではパラメータのタイプもobjectとは限らず,自分のニーズに合わせて改造できる.
    以上のように改造して、初めてのリクエスト後、同じリクエストが再びトリガーされるとhttpリクエストは送信されず、キャッシュから直接取得されるので、どれだけ速くて節約できますか?
    参照先:
    JSはLRUアルゴリズムを実現する