1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        如何設(shè)計(jì)一個(gè)牛逼的本地緩存!

        共 5024字,需瀏覽 11分鐘

         ·

        2020-11-19 11:03

        轉(zhuǎn)自:juejin.im/post/6844904003394863118


        最近在看Mybatis的源碼,剛好看到緩存這一塊,Mybatis提供了一級緩存和二級緩存;一級緩存相對來說比較簡單,功能比較齊全的是二級緩存,基本上滿足了一個(gè)緩存該有的功能;當(dāng)然如果拿來和專門的緩存框架如ehcache來對比可能稍有差距;本文我們將來整理一下實(shí)現(xiàn)一個(gè)本地緩存都應(yīng)該需要考慮哪些東西。

        考慮點(diǎn)

        考慮點(diǎn)主要在數(shù)據(jù)用何種方式存儲,能存儲多少數(shù)據(jù),多余的數(shù)據(jù)如何處理等幾個(gè)點(diǎn),下面我們來詳細(xì)的介紹每個(gè)考慮點(diǎn),以及該如何去實(shí)現(xiàn);

        1.數(shù)據(jù)結(jié)構(gòu)

        首要考慮的就是數(shù)據(jù)該如何存儲,用什么數(shù)據(jù)結(jié)構(gòu)存儲,最簡單的就直接用Map來存儲數(shù)據(jù);或者復(fù)雜的如redis一樣提供了多種數(shù)據(jù)類型哈希,列表,集合,有序集合等,底層使用了雙端鏈表,壓縮列表,集合,跳躍表等數(shù)據(jù)結(jié)構(gòu);

        2.對象上限

        因?yàn)槭潜镜鼐彺?,?nèi)存有上限,所以一般都會指定緩存對象的數(shù)量比如1024,當(dāng)達(dá)到某個(gè)上限后需要有某種策略去刪除多余的數(shù)據(jù);

        3.清除策略

        上面說到當(dāng)達(dá)到對象上限之后需要有清除策略,常見的比如有LRU(最近最少使用)、FIFO(先進(jìn)先出)、LFU(最近最不常用)、SOFT(軟引用)、WEAK(弱引用)等策略;

        4.過期時(shí)間

        除了使用清除策略,一般本地緩存也會有一個(gè)過期時(shí)間設(shè)置,比如redis可以給每個(gè)key設(shè)置一個(gè)過期時(shí)間,這樣當(dāng)達(dá)到過期時(shí)間之后直接刪除,采用清除策略+過期時(shí)間雙重保證;

        5.線程安全

        像redis是直接使用單線程處理,所以就不存在線程安全問題;而我們現(xiàn)在提供的本地緩存往往是可以多個(gè)線程同時(shí)訪問的,所以線程安全是不容忽視的問題;并且線程安全問題是不應(yīng)該拋給使用者去保證;

        6.簡明的接口

        提供一個(gè)傻瓜式的對外接口是很有必要的,對使用者來說使用此緩存不是一種負(fù)擔(dān)而是一種享受;提供常用的get,put,remove,clear,getSize方法即可;

        7.是否持久化

        這個(gè)其實(shí)不是必須的,是否需要將緩存數(shù)據(jù)持久化看需求;本地緩存如ehcache是支持持久化的,而guava是沒有持久化功能的;分布式緩存如redis是有持久化功能的,memcached是沒有持久化功能的;

        8.阻塞機(jī)制

        在看Mybatis源碼的時(shí)候,二級緩存提供了一個(gè)blocking標(biāo)識,表示當(dāng)在緩存中找不到元素時(shí),它設(shè)置對緩存鍵的鎖定;這樣其他線程將等待此元素被填充,而不是命中數(shù)據(jù)庫;其實(shí)我們使用緩存的目的就是因?yàn)楸痪彺娴臄?shù)據(jù)生成比較費(fèi)時(shí),比如調(diào)用對外的接口,查詢數(shù)據(jù)庫,計(jì)算量很大的結(jié)果等等;這時(shí)候如果多個(gè)線程同時(shí)調(diào)用get方法獲取的結(jié)果都為null,每個(gè)線程都去執(zhí)行一遍費(fèi)時(shí)的計(jì)算,其實(shí)也是對資源的浪費(fèi);最好的辦法是只有一個(gè)線程去執(zhí)行,其他線程等待,計(jì)算一次就夠了;但是此功能基本上都交給使用者來處理,很少有本地緩存有這種功能;

        如何實(shí)現(xiàn)

        以上大致介紹了實(shí)現(xiàn)一個(gè)本地緩存我們都有哪些需要考慮的地方,當(dāng)然可能還有其他沒有考慮到的點(diǎn);下面繼續(xù)看看關(guān)于每個(gè)點(diǎn)都應(yīng)該如何去實(shí)現(xiàn),重點(diǎn)介紹一下思路;

        1.數(shù)據(jù)結(jié)構(gòu)

        本地緩存最常見的是直接使用Map來存儲,比如guava使用ConcurrentHashMap,ehcache也是用了ConcurrentHashMap,Mybatis二級緩存使用HashMap來存儲:
        ????Map<Object,?Object>?cache?=?new?ConcurrentHashMap<Object,?Object>()Mybatis使用HashMap本身是非線程安全的,所以可以看到起內(nèi)部使用了一個(gè)SynchronizedCache用來包裝,保證線程的安全性;?
        當(dāng)然除了使用Map來存儲,可能還使用其他數(shù)據(jù)結(jié)構(gòu)來存儲,比如redis使用了雙端鏈表,壓縮列表,整數(shù)集合,跳躍表和字典;當(dāng)然這主要是因?yàn)閞edis對外提供的接口很豐富除了哈希還有列表,集合,有序集合等功能;

        2.對象上限

        本地緩存常見的一個(gè)屬性,一般緩存都會有一個(gè)默認(rèn)值比如1024,在用戶沒有指定的情況下默認(rèn)指定;當(dāng)緩存的數(shù)據(jù)達(dá)到指定最大值時(shí),需要有相關(guān)策略從緩存中清除多余的數(shù)據(jù)這就涉及到下面要介紹的清除策略;

        3.清除策略

        配合對象上限之后使用,場景的清除策略如:LRU(最近最少使用)、FIFO(先進(jìn)先出)、LFU(最近最不常用)、SOFT(軟引用)、WEAK(弱引用);?
        LRU?:Least Recently
        Used的縮寫最近最少使用,移除最長時(shí)間不被使用的對象;常見的使用LinkedHashMap來實(shí)現(xiàn),也是很多本地緩存默認(rèn)使用的策略;?
        FIFO?:先進(jìn)先出,按對象進(jìn)入緩存的順序來移除它們;常見使用隊(duì)列Queue來實(shí)現(xiàn);?
        LFU?:Least Frequently
        Used的縮寫大概也是最近最少使用的意思,和LRU有點(diǎn)像;區(qū)別點(diǎn)在LRU的淘汰規(guī)則是基于訪問時(shí)間,而LFU是基于訪問次數(shù)的;可以通過HashMap并且記錄訪問次數(shù)來實(shí)現(xiàn);?
        SOFT?:軟引用基于垃圾回收器狀態(tài)和軟引用規(guī)則移除對象;常見使用SoftReference來實(shí)現(xiàn);?
        WEAK?:弱引用更積極地基于垃圾收集器狀態(tài)和弱引用規(guī)則移除對象;常見使用WeakReference來實(shí)現(xiàn);

        4.過期時(shí)間

        設(shè)置過期時(shí)間,讓緩存數(shù)據(jù)在指定時(shí)間過后自動刪除;常見的過期數(shù)據(jù)刪除策略有兩種方式:被動刪除和主動刪除;?
        被動刪除?:每次進(jìn)行g(shù)et/put操作的時(shí)候都會檢查一下當(dāng)前key是否已經(jīng)過期,如果過期則刪除,類似如下代碼:
        ????if?(System.currentTimeMillis()?-?lastClear?>?clearInterval)?{
        ??????????clear();
        ????}
        ????
        主動刪除?:專門有一個(gè)job在后臺定期去檢查數(shù)據(jù)是否過期,如果過期則刪除,這其實(shí)可以有效的處理冷數(shù)據(jù);

        5.線程安全

        盡量用線程安全的類去存儲數(shù)據(jù),比如使用ConcurrentHashMap代替HashMap;或者提供相應(yīng)的同步處理類,比如Mybatis提供了SynchronizedCache:
        ?????public?synchronized?void?putObject(Object?key,?Object?object)?{
        ????????...省略...
        ??????}

        ??????@Override
        ??????public?synchronized?Object?getObject(Object?key)?{
        ????????...省略...
        ??????}
        ????

        6.簡明的接口

        提供常用的get,put,remove,clear,getSize方法即可,比如Mybatis的Cache接口:
        ????public?interface?Cache?{
        ??????String?getId();
        ??????void?putObject(Object?key,?Object?value);
        ??????Object?getObject(Object?key);
        ??????Object?removeObject(Object?key);
        ??????void?clear();
        ??????int?getSize();
        ??????ReadWriteLock?getReadWriteLock();
        ????}
        ????
        再來看看guava提供的Cache接口,相對來說也是比較簡潔的:
        ????public?interface?Cache<K,?V>?{
        ??????V?getIfPresent(@CompatibleWith("K")?Object?key);
        ??????V?get(K?key,?Callable?loader)?throws?ExecutionException;
        ??????ImmutableMap?getAllPresent(Iterable?keys);
        ??????void?put(K?key,?V?value);
        ??????void?putAll(Map?m);
        ??????void?invalidate(@CompatibleWith("K")?Object?key);
        ??????void?invalidateAll(Iterable?keys);
        ??????void?invalidateAll();
        ??????long?size();
        ??????CacheStats?stats();
        ??????ConcurrentMap?asMap();
        ??????void?cleanUp();
        ????}

        7.是否持久化

        持久化的好處是重啟之后可以再次加載文件中的數(shù)據(jù),這樣就起到類似熱加載的功效;比如ehcache提供了是否持久化磁盤緩存的功能,將緩存數(shù)據(jù)存放在一個(gè).data文件中;
        ????diskPersistent="false"?//是否持久化磁盤緩存
        redis更是將持久化功能發(fā)揮到極致,慢慢的有點(diǎn)像數(shù)據(jù)庫了;提供了AOF和RDB兩種持久化方式;當(dāng)然很多情況下可以配合使用兩種方式;

        8.阻塞機(jī)制

        除了在Mybatis中看到了BlockingCache來實(shí)現(xiàn)此功能,之前在看<>的時(shí)候其中有實(shí)現(xiàn)一個(gè)很完美的緩存,大致代碼如下:
        ????public?class?Memoizerl<A,?V>?implements?Computable<A,?V>?{
        ????????private?final?Map>?cache?=?new?ConcurrentHashMap>();
        ????????private?final?Computable?c;

        ????????public?Memoizerl(Computable?c)?{
        ????????????this.c?=?c;
        ????????}

        ????????@Override
        ????????public?V?compute(A?arg)?throws?InterruptedException,?ExecutionException?{
        ????????????while?(true)?{
        ????????????????Future?f?=?cache.get(arg);
        ????????????????if?(f?==?null)?{
        ????????????????????Callable?eval?=?new?Callable()?{
        ????????????????????????@Override
        ????????????????????????public?V?call()?throws?Exception?{
        ????????????????????????????return?c.compute(arg);
        ????????????????????????}
        ????????????????????};
        ????????????????????FutureTask?ft?=?new?FutureTask(eval);
        ????????????????????f?=?cache.putIfAbsent(arg,?ft);
        ????????????????????if?(f?==?null)?{
        ????????????????????????f?=?ft;
        ????????????????????????ft.run();
        ????????????????????}
        ????????????????????try?{
        ????????????????????????return?f.get();
        ????????????????????}?catch?(CancellationException?e)?{
        ????????????????????????cache.remove(arg,?f);
        ????????????????????}
        ????????????????}
        ????????????}
        ????????}
        ????}
        compute是一個(gè)計(jì)算很費(fèi)時(shí)的方法,所以這里把計(jì)算的結(jié)果緩存起來,但是有個(gè)問題就是如果兩個(gè)線程同時(shí)進(jìn)入此方法中怎么保證只計(jì)算一次,這里最核心的地方在于使用了ConcurrentHashMap的putIfAbsent方法,同時(shí)只會寫入一個(gè)FutureTask;

        總結(jié)

        本文大致介紹了要設(shè)計(jì)一個(gè)本地緩存都需要考慮哪些點(diǎn):數(shù)據(jù)結(jié)構(gòu),對象上限,清除策略,過期時(shí)間,線程安全,阻塞機(jī)制,實(shí)用的接口,是否持久化;當(dāng)然肯定有其他考慮點(diǎn),歡迎補(bǔ)充。

        后臺回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


        如有收獲,點(diǎn)個(gè)在看,誠摯感謝

        瀏覽 26
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本无码流出 | 国产一区二区三区免费看 | 尤物com| 国产3级片在线观看 | 911亚洲精品 | 能看黄色视频的网站 | 边打电话边啪啪 | 男人操女逼 | 黄片播放黄片播放黄片播放 | 迪丽热巴自慰 |