<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

本地緩存的技術實踐

科技綠洲 ? 來源:Java技術指北 ? 作者:Java技術指北 ? 2023-09-30 15:29 ? 次閱讀

一、摘要

說到緩存,面試官基本上會繞不開以下幾個話題!

項目中哪些地方用到了緩存?為什么要使用緩存?怎么使用它的?引入緩存后會帶來哪些問題?

這些問題,基本上是互聯網公司面試時必問的一些問題,如果面試的時候,連緩存都不清楚,那確實多少顯的有些尷尬!

項目里面為什么要引入緩存?這個問題還得結合項目中的業務來回答!

引入緩存,其實主要有兩個用途: 高性能 、 高并發 !

假設某個操作非常頻繁,比如網站的商城首頁,需要頻繁的從數據庫里面獲取商品數據,可能從數據庫一頓各種亂七八糟的操作下來,平均耗時 500 ms,隨著請求頻次越高,用戶等待數據的返回結果時間越來越長,體驗越來越差。

如果此時, 引入緩存 ,將數據庫里面查詢出來的商品數據信息,放入緩存服務里面,當用戶再此發起查詢操作的時候,直接從緩存服務里面獲取,速度從耗時 500 ms,可能直接優化成 5 ms,體驗上瞬間會上升好幾個層次!

這就是引入緩存帶來的高性能體驗結果 !

當然,除此之外, 引入緩存之前 ,以 mysql 數據庫為例,單臺機器一秒內的請求次數到達 2000 之后就會開始報警; 引入緩存之后 ,比如以 redis 緩存服務器為例,單臺機器一秒內的請求次數支持 110000 次,兩者支持的并發量完全不是一個數量級的。

這就是引入緩存帶來的高并發體驗結果 !

尤其是對于流量很大的業務,引入緩存,給系統帶來的提升是十分顯著的 。

可能有的同學又會發出疑問,緩存和數據庫為啥差距這么大,有啥區別?

我們都知道在計算機領域,數據的存儲主要有兩處: 一處是內存,另一處是磁盤 。

在計算機中,內存的數據讀寫性能遠超磁盤的讀寫性能,盡管如此,其實兩者也有不同,如果數據存儲到內存中,雖然讀寫性能非常高,但是當電腦重啟之后,數據會全部清除;而存入磁盤的數據,雖然讀寫性能很差,但是電腦重啟之后數據不會丟失。

因為兩者的數據存儲方案不同,造就了不同的實踐用途 !

我們上面講到的緩存服務,其實本質就是將數據存儲到內存中;而數據庫服務,是將數據寫入到磁盤,從磁盤中讀取數據。

無論是哪種方案,沒有絕對的好與壞,主要還是取決于實際的業務用途。

在項目中如何引入緩存呢?我們通常的做法如下:

圖片

操作步驟:

  • 1.當用戶發起訪問某數據的操作時,檢查緩存服務里面是否存在,如果存在,直接返回;如果不存在,走數據庫的查詢服務
  • 2.從數據庫里面獲取到有效數據之后,存入緩存服務,并返回給用戶
  • 3.當被訪問的數據發生更新的時候,需要同時刪除緩存服務,以便用戶再次查詢的時候,能獲取到最新的數據

當然以上的緩存處理辦法,對于簡單的需要緩存的業務場景,能輕松應對。

但是面對復雜的業務場景和服務架構,尤其是對緩存要求比較高的業務,引入緩存的方式,也會跟著一起變化!

從緩存面向的對象不同,緩存分為: 本地緩存 、分布式緩存多級緩存 。

所謂 本地緩存 ,相信大家都能理解,在單個計算機服務實例中,直接把數據緩存到內存中進行使用。

但是現在的服務,大多都是以集群的方式來部署,你也可以這樣理解,同一個網站服務,同時在兩臺計算機里面部署,比如你用到的session會話,就無法同時共享,因此需要引入一個獨立的緩存服務來連接兩臺服務器,這個獨立部署的緩存服務,我們把這種技術實踐方案稱為 分布式緩存 。

在實際的業務中,本地緩存分布式緩存會同時結合進行使用,當收到訪問某個數據的操作時,會優先從本地緩存服務(也叫一級緩存)查詢,如果沒有,再從分布式緩存服務(也叫二級緩存)里面獲取,如果也沒有,最后再從數據庫里面獲??;從數據庫查詢完成之后,在依次更新分布式緩存服務、本次緩存服務,我們把這個技術實踐方案叫 多級緩存 !

由于篇幅的原因,我們在后期給大家介紹 分布式緩存服務 、 多級緩存服務 。

今天主要圍繞本地緩存服務的技術實現,給大家進行分享和介紹!

二、方案介紹

如果使用過緩存的同學,可以很容易想到緩存需要哪些東西,通常我們在使用緩存的時候,比較關注兩個地方,第一是內存持久化,第二是支持緩存的數據自動過期清楚。

基于以上的要求,我們向介紹以下幾種技術實現方案。

2.1、手寫一個緩存服務

對于簡單的數據緩存,我們完全可以自行編寫一套緩存服務,實現過程如下!

首先創建一個緩存實體類

public class CacheEntity {

    /**
     * 緩存鍵
     */
    private String key;

    /**
     * 緩存值
     */
    private Object value;

    /**
     * 過期時間
     */
    private Long expireTime;

    //...set、get
}

接著,編寫一個緩存操作工具類CacheUtils

public class CacheUtils {

    /**
     * 緩存數據
     */
    private final static Map< String, CacheEntity > CACHE_MAP = new ConcurrentHashMap<  >();

    /**
     * 定時器線程池,用于清除過期緩存
     */
    private static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();


    static {
        // 注冊一個定時線程任務,服務啟動1秒之后,每隔500毫秒執行一次
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 清理過期緩存
                clearCache();
            }
        },1000,500,TimeUnit.MILLISECONDS);
    }

    /**
     * 添加緩存
     * @param key    緩存鍵
     * @param value  緩存值
     */
    public static void put(String key, Object value){
        put(key, value, 0);
    }


    /**
     * 添加緩存
     * @param key    緩存鍵
     * @param value  緩存值
     * @param expire 緩存時間,單位秒
     */
    public static void put(String key, Object value, long expire){
        CacheEntity cacheEntity = new CacheEntity()
                .setKey(key)
                .setValue(value);
        if(expire > 0){
            Long expireTime = System.currentTimeMillis() + Duration.ofSeconds(expire).toMillis();
            cacheEntity.setExpireTime(expireTime);
        }
        CACHE_MAP.put(key, cacheEntity);
    }


    /**
     * 獲取緩存
     * @param key
     * @return
     */
    public static Object get(String key){
        if(CACHE_MAP.containsKey(key)){
            return CACHE_MAP.get(key).getValue();
        }
        return null;
    }

    /**
     * 移除緩存
     * @param key
     */
    public static void remove(String key){
        if(CACHE_MAP.containsKey(key)){
            CACHE_MAP.remove(key);
        }
    }

    /**
     * 清理過期的緩存數據
     */
    private static void clearCache(){
        if(CACHE_MAP.size() > 0){
            return;
        }
        Iterator< Map.Entry< String, CacheEntity >> iterator = CACHE_MAP.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry< String, CacheEntity > entry = iterator.next();
            if(entry.getValue().getExpireTime() != null && entry.getValue().getExpireTime().longValue() > System.currentTimeMillis()){
                iterator.remove();
            }
        }
    }

}

最后,我們來測試一下緩存服務

// 寫入緩存數據
CacheUtils.put("userName", "張三", 3);

// 讀取緩存數據
Object value1 = CacheUtils.get("userName");
System.out.println("第一次查詢結果:" + value1);

// 停頓4秒
Thread.sleep(4000);

// 讀取緩存數據
Object value2 = CacheUtils.get("userName");
System.out.println("第二次查詢結果:" + value2);

輸出結果,與預期一致!

第一次查詢結果:張三
第二次查詢結果:null

實現思路其實很簡單,采用ConcurrentHashMap作為緩存數據存儲服務,然后開啟一個定時調度,每隔500毫秒檢查一下過期的緩存數據,然后清除掉!

2.2、基于 Guava Cache 實現本地緩存

Guava 是 Google 團隊開源的一款 Java 核心增強庫,包含集合、并發原語、緩存、IO、反射等工具箱,性能和穩定性上都有保障,應用十分廣泛。

相比自己編寫的緩存服務,Guava Cache 要強大的多,支持很多特性如下:

  • 支持最大容量限制
  • 支持兩種過期刪除策略(插入時間和讀取時間)
  • 支持簡單的統計功能
  • 基于 LRU 算法實現

使用方面也很簡單,首先引入guava庫包。

< !--guava-- >
< dependency >
    < groupId >com.google.guava< /groupId >
    < artifactId >guava< /artifactId >
    < version >31.1-jre< /version >
< /dependency >

案例代碼如下:

// 創建一個緩存實例
Cache< String, String > cache = CacheBuilder.newBuilder()
        // 初始容量
        .initialCapacity(5)
        // 最大緩存數,超出淘汰
        .maximumSize(10)
        // 過期時間
        .expireAfterWrite(3, TimeUnit.SECONDS)
        .build();

// 寫入緩存數據
cache.put("userName", "張三");

// 讀取緩存數據
String value1 = cache.get("userName", () - > {
    // 如果key不存在,會執行回調方法
    return "key已過期";
});
System.out.println("第一次查詢結果:" + value1);

// 停頓4秒
Thread.sleep(4000);

// 讀取緩存數據
String value2 = cache.get("userName", () - > {
    // 如果key不存在,會執行回調方法
    return "key已過期";
});
System.out.println("第二次查詢結果:" + value2);

輸出結果:

第一次查詢結果:張三
第二次查詢結果:key已過期

2.3、基于 Caffeine 實現本地緩存

Caffeine 是基于 java8 實現的新一代緩存工具,緩存性能接近理論最優,可以看作是 Guava Cache 的增強版,功能上兩者類似,不同的是 Caffeine 采用了一種結合 LRU、LFU 優點的算法:W-TinyLFU,在性能上有明顯的優越性。

使用方面也很簡單,首先引入caffeine庫包。

< !--caffeine-- >
< dependency >
    < groupId >com.github.ben-manes.caffeine< /groupId >
    < artifactId >caffeine< /artifactId >
    < version >2.9.3< /version >
< /dependency >

案例代碼如下:

// 創建一個緩存實例
Cache< String, String > cache = Caffeine.newBuilder()
        // 初始容量
        .initialCapacity(5)
        // 最大緩存數,超出淘汰
        .maximumSize(10)
        // 設置緩存寫入間隔多久過期
        .expireAfterWrite(3, TimeUnit.SECONDS)
        // 設置緩存最后訪問后間隔多久淘汰,實際很少用到
        //.expireAfterAccess(3, TimeUnit.SECONDS)
        .build();

// 寫入緩存數據
cache.put("userName", "張三");

// 讀取緩存數據
String value1 = cache.get("userName", (key) - > {
    // 如果key不存在,會執行回調方法
    return "key已過期";
});
System.out.println("第一次查詢結果:" + value1);

// 停頓4秒
Thread.sleep(4000);

// 讀取緩存數據
String value2 = cache.get("userName", (key) - > {
    // 如果key不存在,會執行回調方法
    return "key已過期";
});
System.out.println("第二次查詢結果:" + value2);

輸出結果:

第一次查詢結果:張三
第二次查詢結果:key已過期

2.4、基于 Encache 實現本地緩存

Encache 是一個純 Java 的進程內緩存框架,具有快速、精干等特點,是 Hibernate 中默認的 CacheProvider。

同 Caffeine 和 Guava Cache 相比,Encache 的功能更加豐富,擴展性更強,特性如下:

  • 支持多種緩存淘汰算法,包括 LRU、LFU 和 FIFO
  • 緩存支持堆內存儲、堆外存儲、磁盤存儲(支持持久化)三種
  • 支持多種集群方案,解決數據共享問題

使用方面也很簡單,首先引入ehcache庫包。

< !--ehcache-- >
< dependency >
    < groupId >org.ehcache< /groupId >
    < artifactId >ehcache< /artifactId >
    < version >3.9.7< /version >
< /dependency >

案例代碼如下:

/**
 * 自定義過期策略實現
 */
public  class CustomExpiryPolicy< K, V > implements ExpiryPolicy< K, V > {

    private final Map< K, Duration > keyExpireMap = new ConcurrentHashMap();


    public Duration setExpire(K key, Duration duration) {
        return keyExpireMap.put(key, duration);
    }

    public Duration getExpireByKey(K key) {
        return Optional.ofNullable(keyExpireMap.get(key))
                .orElse(null);
    }

    public Duration removeExpire(K key) {
        return keyExpireMap.remove(key);
    }

    @Override
    public Duration getExpiryForCreation(K key, V value) {
        return Optional.ofNullable(getExpireByKey(key))
                .orElse(Duration.ofNanos(Long.MAX_VALUE));
    }

    @Override
    public Duration getExpiryForAccess(K key, Supplier< ? extends V > value) {
        return getExpireByKey(key);
    }

    @Override
    public Duration getExpiryForUpdate(K key, Supplier< ? extends V > oldValue, V newValue) {
        return getExpireByKey(key);
    }
}
public static void main(String[] args) throws InterruptedException {
    String userCache = "userCache";

    // 自定義過期策略
    CustomExpiryPolicy< Object, Object > customExpiryPolicy = new CustomExpiryPolicy<  >();

    // 聲明一個容量為20的堆內緩存配置
    CacheConfigurationBuilder configurationBuilder = CacheConfigurationBuilder
            .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))
            .withExpiry(customExpiryPolicy);

    // 初始化一個緩存管理器
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            // 創建cache實例
            .withCache(userCache, configurationBuilder)
            .build(true);

    // 獲取cache實例
    Cache< String, String > cache = cacheManager.getCache(userCache, String.class, String.class);
    // 獲取過期策略
    CustomExpiryPolicy expiryPolicy = (CustomExpiryPolicy)cache.getRuntimeConfiguration().getExpiryPolicy();

    // 寫入緩存數據
    cache.put("userName", "張三");
    // 設置3秒過期
    expiryPolicy.setExpire("userName", Duration.ofSeconds(3));

    // 讀取緩存數據
    String value1 = cache.get("userName");
    System.out.println("第一次查詢結果:" + value1);

    // 停頓4秒
    Thread.sleep(4000);

    // 讀取緩存數據
    String value2 = cache.get("userName");
    System.out.println("第二次查詢結果:" + value2);
}

輸出結果:

第一次查詢結果:張三
第二次查詢結果:null

三、小結

從易用性角度看:Guava Cache、Caffeine 和 Encache 都有十分成熟的接入方案,使用簡單。

從功能性角度看:Guava Cache 和 Caffeine 功能類似,都是只支持堆內緩存,Encache 相比功能更為豐富,不僅支持堆內緩存,還支持磁盤寫入、集群實現。

從性能角度看:Caffeine 最優、GuavaCache 次之,Encache 最差。

以下是網絡上三者性能對比的結果。

圖片

對于本地緩存的技術選型, 推薦采用 Caffeine ,性能上毫無疑問,遙遙領先。

雖然 Encache 功能非常的豐富,甚至提供了持久化和集群的功能,但是相比更成熟的分布式緩存中間件 redis 來說,還是稍遜一些!

關于 redis 的使用,有興趣的同學可以查看歷史文章,之前有寫過 redis 系列相關的技術實踐介紹。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據
    +關注

    關注

    8

    文章

    6531

    瀏覽量

    87770
  • 服務器
    +關注

    關注

    12

    文章

    8249

    瀏覽量

    82890
  • 內存
    +關注

    關注

    8

    文章

    2785

    瀏覽量

    72893
  • 緩存
    +關注

    關注

    1

    文章

    221

    瀏覽量

    26464
  • 磁盤
    +關注

    關注

    1

    文章

    343

    瀏覽量

    24927
收藏 人收藏

    評論

    相關推薦

    高并發系統中的緩存 緩存系統存在的三大問題

    緩存在計算機系統是無處不在,在CPU層面有L1-L3的Cache,在Linux中有TLB加速虛擬地址和物理地址的轉換,在瀏覽器有本地緩存、手機有本地
    的頭像 發表于 07-15 11:03 ?4054次閱讀

    LRU緩存模塊最佳實踐

    LRU(Least Recently Used)是一種緩存替換算法,它的核心思想是當緩存滿時,替換最近最少使用的數據。在實際應用中,LRU算法被廣泛應用于緩存、頁面置換等領域。Rust語言提供
    的頭像 發表于 09-30 16:47 ?612次閱讀

    如何選擇合適的本地緩存?

    小編最近在使用系統的時候,發現盡管應用已經使用了 redis 緩存提高查詢效率,但是仍然有進一步優化的空間,于是想到了比分布式緩存性能更好的本地緩存,因此對領域內常用的
    的頭像 發表于 01-18 11:19 ?543次閱讀
    如何選擇合適的<b class='flag-5'>本地</b><b class='flag-5'>緩存</b>?

    157.157、緩存 緩存使用 本地鎖在分布式下的問題

    緩存
    充八萬
    發布于 :2023年07月18日 04:44:59

    ASP緩存技術

    使用ASP中的緩存技術可以很大程度上提高你的網站性能,其實這些實現方法是非常的簡單,它將說明如何在服務器上的緩存是如何工作以及你如何使用一種被稱為斷開連接的ADO連接技術。在介紹這些
    發表于 11-21 10:53

    怎樣去實現一種基于DSP和ADC技術高速緩存和海量緩存?

    構成高速緩存的方案有哪幾種?如何去實現一種海量緩存的設計?怎樣去實現一種基于DSP和ADC技術高速緩存和海量緩存?
    發表于 06-26 07:50

    內容中心網絡中基于用戶偏好的協作緩存策略

    用戶本地偏好度指標,實現緩存內容的選擇;然后,對需要緩存內容執行差異化緩存策略,全局活躍的內容則緩存在重要的中心節點,非活躍內容則按
    發表于 12-19 15:23 ?4次下載
    內容中心網絡中基于用戶偏好的協作<b class='flag-5'>緩存</b>策略

    緩存的基本原理 緩存的分類

    緩存的主要手段有:瀏覽器緩存、CDN、反向代理、本地緩存、分布式緩存、數據庫緩存。
    發表于 06-13 12:04 ?4389次閱讀

    緩存技術的工作原理

    緩存系統有時也稱為混合存儲網關設備,以強調其作為本地和云基礎架構之間橋梁的作用。正如該Gartner定義所指出,與其他嵌入式緩存一樣,這些設備基于設備和應用程序攔截文件、塊或對象存儲I / O。
    的頭像 發表于 08-10 11:51 ?2812次閱讀

    ThingJS平臺推出3D場景本地緩存技術

    為提升用戶訪問體驗,縮短項目加載時間,ThingJS平臺推出3D場景本地緩存技術:IndexedDB,也稱客戶端緩存持久化技術。通俗來說,I
    發表于 03-13 11:19 ?1624次閱讀

    關于瀏覽器緩存最詳細解析

    瀏覽器緩存即 http 緩存,將請求過的數據(html、css、js)存在瀏覽器(本地磁盤)中,當再次訪問這些資源時可以從本地直接加載,減少服務端請求。
    的頭像 發表于 04-16 16:01 ?2525次閱讀

    聊聊本地緩存和分布式緩存

    本地緩存 :應用中的緩存組件,緩存組件和應用在同一進程中,緩存的讀寫非???,沒有網絡開銷。但各應用或集群的各節點都需要維護自己的單獨
    發表于 06-11 15:12 ?600次閱讀
    聊聊<b class='flag-5'>本地</b><b class='flag-5'>緩存</b>和分布式<b class='flag-5'>緩存</b>

    如何在 Linux 上查看本地 DNS 緩存

    ? 刷新本地 DNS 緩存可以解決 HTTP 錯誤并保護您免受 DNS 欺騙。以下是在 Linux 上執行此操作的方法。 當您使用域名訪問網站時,您的系統會向 DNS 服務器發送請求以獲取
    的頭像 發表于 06-26 10:52 ?2612次閱讀
    如何在 Linux 上查看<b class='flag-5'>本地</b> DNS <b class='flag-5'>緩存</b>

    Ehcache!這才是Java本地緩存之王!

    就Java而言,其常用的緩存解決方案有很多,例如數據庫緩存框架EhCache,分布式緩存Memcached等,這些緩存方案實際上都是為了提升吞吐效率,避免持久層壓力過大。
    的頭像 發表于 07-29 11:21 ?1274次閱讀
    Ehcache!這才是Java<b class='flag-5'>本地</b><b class='flag-5'>緩存</b>之王!

    基于java開發的緩存框架jetcache簡介

    在實際應用中,并不是單一的使用本地緩存或者redis,更多是組合使用來滿足不同的業務場景,于是如何優雅的組合本地緩存和遠程緩存就成了我們要研
    的頭像 發表于 09-07 10:36 ?727次閱讀
    基于java開發的<b class='flag-5'>緩存</b>框架jetcache簡介
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>