WEB 應用緩存解析以及使用 Redis 實現分佈式緩存


什麼是緩存?

緩存就是數據交換的緩衝區,用於臨時存儲數據(使用頻繁的數據)。當用戶請求數據時,首先在緩存中尋找,若是找到了則直接返回。若是找不到,則去數據庫中查找。緩存的本質就是用空間換時間,犧牲數據的實時性,從而減輕數據庫壓力,儘量提升吞吐量,有效提高響應速度。java


緩存的分類

緩存的應用範圍十分普遍,常見的有文件緩存、瀏覽器緩存、數據庫緩存等等。但今天咱們着重關注的是 WEB 應用服務領域,根據緩存與應用的耦合度,能夠分爲本地緩存和分佈式緩存:redis

  • 本地緩存算法

    指在應用中的緩存組件,最大的優勢是應用和緩存是在同一個進程內部,請求緩存速度快;同時,它的缺點也是由於緩存跟應用程序耦合,多個應用程序沒法直接共享緩存,各應用或集羣的各節點都須要維護本身的單獨緩存數據庫

  • 分佈式緩存瀏覽器

    指的是與應用分離的緩存組件或服務,最大的優勢是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接共享緩存緩存


緩存的特色

緩存也是一個數據模型對象,那麼必然有它的一些特徵:服務器

  • 命中率mybatis

    命中率 = 返回正確結果數 / 請求緩存次數,命中率問題是緩存中的一個很是重要的問題,它是衡量緩存有效性的重要指標。命中率越高,代表緩存的使用率越高。app

  • 最大元素分佈式

    緩存中能夠存放的最大元素的數量,一旦緩存中元素數量超過這個值,將會觸發緩存清空策略。根據不一樣的場景合理設置最大元素值,能夠在必定程度上提升緩存的命中率,從而更有效的利用緩存。


緩存清空策略

緩存的存儲空間有限制,當緩存空間被用滿時,就須要緩存清空策略來處理。常見的通常策略有:

  • 先進先出策略

    先進入緩存的數據,在緩存空間不足時會被優先被清理掉,以騰出新的空間接受新的數據。先進先出策略主要比較緩存元素的建立時間,在數據實效性要求較高的場景下可選擇該類策略,優先保障最新數據可用

  • 最少使用策略

    不管是否過時,根據元素被使用的次數判斷,清除使用次數較少的元素。最少使用策略主要比較元素的命中次數,在保證高頻數據有效性場景下,可選擇該類策略

  • 最近最少使用策略

    不管是否過時,根據元素最後一次被使用的時間戳,清除最遠使用時間戳的元素。策略算法主要比較元素最近一次被使用的時間。適用於熱點數據場景,優先保證熱點數據的有效性

此外,還有一些簡單策略,好比:

  • 根據過時時間判斷,清理過時時間最長的元素
  • 根據過時時間判斷,清理最近要過時的元素
  • 隨機清理
  • 根據關鍵字(或元素內容)清理等等

Redis 實現分佈式緩存

能夠利用 Mybatis 自帶的本地緩存,結合 Redis 實現分佈式緩存。主要思路是將 Mybatis 二級緩存的存放地點從本地改成配置了 Redis 的遠程服務器。

第一步,建立一個 SpringBoot 工程,整合 MyBatis 和 Redis,在 Mapper 文件中加入 <cache/> 標籤開啓二級緩存。

<cache/> 標籤默認採用 PrepetualCache,該類是 Cache 接口的實現類,維護一個 Map 來保存數據。咱們要做改造,就要自定義一個實現類並替換 <cache type="xxxx.RedisCache">

實現自定義 RedisCache

public class RedisCache implements Cache {

  	// 當前放入緩存的 mapper 的 namespace,也是緩存的惟一標識
    private final String id;

    public RedisCache(String id) {
        System.out.println("id:" + id);
        this.id = id;
    }


    /**
     * 返回 cache 的惟一標識
     */
    @Override
    public String getId() {
        return this.id;
    }

    /**
     * 緩存放入值
     */
    @Override
    public void putObject(Object key, Object value) {
        System.out.println("放入緩存");
        // 經過工具類獲取 redisTemplate
        RedisTemplate redisTemplate = getRedisTemplate();
        // 使用 redishash 類型做爲緩存存儲模型
        redisTemplate.opsForHash().put(id.toString(), key.toString(), value);
    }

    /**
     * 獲取緩存中的值
     */
    @Override
    public Object getObject(Object key) {
        System.out.println("得到緩存");
        // 經過工具類獲取 redisTemplate
        RedisTemplate redisTemplate = getRedisTemplate();
        // 根據 key 從 redis 的 hash 類型中獲取數據
        return redisTemplate.opsForHash().get(id.toString(), key.toString());

    }

    /**
     * 根據指定的 key 刪除緩存
     * 該方法爲 mybatis 保留方法,默認沒有實現
     */
    @Override
    public Object removeObject(Object key) {
        System.out.println("根據指定的 key 刪除緩存");
        return null;
    }

    @Override
    public void clear() {
        System.out.println("清空緩存");
        // 經過工具類獲取 redisTemplate
        RedisTemplate redisTemplate = getRedisTemplate();
        // 清空 namespace
        redisTemplate.delete(id.toString());
    }

    /**
     * 計算緩存數量
     */
    @Override
    public int getSize() {
        RedisTemplate redisTemplate = getRedisTemplate();
        return redisTemplate.opsForHash().size(id.toString()).intValue();
    }

    /**
     * 獲取 redisTemplate
     */
    private RedisTemplate getRedisTemplate() {
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

到此爲止,使用 Redis 實現分佈式緩存的目標就完成了。還有一點要注意的是,涉及到多表查詢時,結果會包含另外一個表的對象信息。因爲 Mybatis 二級緩存清理只會清理自身 namespace 的緩存,因此被包含的對象信息不會被清理。若是此時表信息發生改變,將致使數據不一致。解決辦法是每一個 namespace 都使用同一個緩存

<!-- 共享其餘 namespace 的緩存 -->
<cache-ref namespace="..."/>

緩存穿透(擊穿)

客戶端查詢了一個數據庫中沒有的數據,致使緩存在這種狀況下沒法利用(數據庫都沒有則緩存更不可能有了)。此狀況下可繞過緩存直接攻擊數據庫。

對於這種惡意訪問,一種思路是先作校驗,對惡意數據直接過濾掉,不要發送至數據庫層;第二種思路是緩存空結果,就是對查詢不存在的數據也記錄在緩存中,這樣就能夠有效的減小查詢數據庫的次數。MyBatis 正是使用了第二種方式。


緩存雪崩

在系統運行的某一時刻,緩存所有失效,剛好這一時刻涌來大量客戶端請求,致使數據庫阻塞或掛起。致使緩存失效的緣由有不少,常見的是緩存到了失效時間(全部的緩存設置了一樣的過時時間),而沒有做合適的處理。

要解決這個問題,一種方式是設置緩存永久存儲(不推薦),另外一種方式是針對不一樣業務數據設置不一樣的超時時間,防止集體失效。

相關文章
相關標籤/搜索