首頁 > 軟體

Redis與本地快取的結合實現

2022-07-14 14:05:49

前言

  • 我們開發中經常用到Redis作為快取,將高頻資料放在Redis中能夠提高業務效能,降低MySQL等關係型資料庫壓力,甚至一些系統使用Redis進行資料持久化,Redis鬆散的檔案結構非常適合業務系統開發,在精確查詢,資料統計業務有著很大的優勢。但是高頻資料流處理系統中,Redis的壓力也會很大,同時I/0開銷才是耗時的主要原因,這時候為了降低Redis讀寫壓力我們可以用到本地快取,Guava為我們提供了優秀的本地快取API,包含了過期策略等等,編碼難度低,個人非常推薦。

設計範例

Redis懶載入快取

資料在新增到MySQL不進行快取,在精確查詢進行快取,做到查詢即快取,不查詢不快取

流程圖

程式碼範例

// 虛擬碼範例 Xx代表你的的業務物件 如User Goods等等
public class XxLazyCache {

    @Autowired
    private RedisTemplate<String, Xx> redisTemplate;
    
    @Autowired
    private XxService xxService;// 你的業務service
    
    /**
     * 查詢 通過查詢快取是否存在驅動快取載入 建議在前置業務保證id對應資料是絕對存在於資料庫中的
     */
    public Xx getXx(int id) {
        // 1.查詢快取裡面有沒有資料
        Xx xxCache = getXxFromCache(id);
        if(xxCache != null) {
            return xxCache;// 衛語句使程式碼更有利於閱讀
        }
        // 2.查詢資料庫獲取資料 我們假定到業務這一步,傳過來的id都在資料庫中有對應資料
        Xx xx = xxService.getXxById(id);
        // 3.設定快取、這一步相當於Redis快取懶載入,下次再查詢此id,則會走快取
        setXxFromCache(xx);
        return xx;
        }
    }
    
    /**
     * 對xx資料進行修改或者刪除操作 運算元據庫成功後 刪除快取
     * 刪除請求 - 刪除資料庫資料 刪除快取
     * 修改請求 - 更新資料庫資料 刪除快取 下次在查詢時候就會從資料庫拉取新的資料到快取中
     */
    public void deleteXxFromCache(long id) {
        String key = "Xx:" + xx.getId();
        redisTemplate.delete(key);
    }
    
    private void setXxFromCache(Xx xx) {
        String key = "Xx:" + xx.getId();
        redisTemplate.opsForValue().set(key, xx);
    }
    
    private Xx getXxFromCache(int id) {
        // 通過快取字首拼裝唯一主鍵作為快取Key 如Xxx資訊 就是Xxx:id
        String key = "Xx:" + id;
        return redisTemplate.opsForValue().get(key);
    }
    
}
// 業務類
public class XxServie {
    @Autowired
    private XxLazyCache xxLazyCache;
    // 查詢資料庫
    public Xx getXxById(long id) {
        // 省略實現
        return xx;
    }
    
    public void updateXx(Xx xx) {
        // 更新MySQL資料 省略
        // 刪除快取
        xxLazyCache.deleteXxFromCache(xx.getId());
    }
    
    public void deleteXx(long id) {
        // 刪除MySQL資料 省略
        // 刪除快取
        xxLazyCache.deleteXxFromCache(xx.getId());
    }
}
// 實體類
@Data
public class Xx {
    // 業務主鍵
    private Long id;
    // ...省略
}
複製程式碼

優點

  • 保證最小的快取量滿足精確查詢業務,避免冷資料佔用寶貴的記憶體空間
  • 對增刪改查業務入侵小、刪除即同步
  • 可插拔,對於老系統升級,歷史資料無需在啟動時初始化快取

缺點

  • 資料量需可控,在無限增長業務場景不適用
  • 在微服務場景不利於全域性快取應用

總結

  • 空間最小化
  • 滿足精確查詢場景
  • 總資料量可控推薦使用
  • 微服務場景不適用

Redis結合本地快取

微服務場景下,多個微服務使用一個大快取,流資料業務下,高頻讀取快取對Redis壓力很大,我們使用本地快取結合Redis快取使用,降低Redis壓力,同時本地快取沒有連線開銷,效能更優

流程圖

業務場景

在流處數處理過程中,微服務對多個裝置上傳的資料進行處理,每個裝置有一個code,流資料的頻率高,在訊息佇列傳送過程中使用分割區傳送,我們需要為裝置code生成對應的自增號,用自增號對kafka中topic分割區數進行取模,這樣如果有10000臺裝置,自增號就是0~9999,在取模後就進行分割區傳送就可以做到每個分割區均勻分佈,這個自增號我們使用redis的自增數生成,生成後放到redis的hash結構進行快取,每次來一個裝置,我們就去這個hash快取中取,沒有取到就使用自增數生成一個,然後放到redis的hash快取中,這時候每個裝置的自增數一經生成是不會再發生改變的,我們就想到使用本地快取進行優化,避免高頻的呼叫redis去獲取,降低redis壓力,下面連結為我寫的關於kafka分割區消費的文章,大家可以去看看 Kafka分割區傳送及消費實戰

程式碼範例

/**
 * 此快取演示如何結合redis自增數 hash 本地快取使用進行裝置自增數的生成、快取、本地快取
 * 本地快取使用Guava Cache
 */
public class DeviceIncCache {

    /**
     * 本地快取
     */
    private Cache<String, Integer> localCache = CacheBuilder.newBuilder()
        .concurrencyLevel(16) // 並行級別
        .initialCapacity(1000) // 初始容量
        .maximumSize(10000) // 快取最大長度
        .expireAfterAccess(1, TimeUnit.HOURS) // 快取1小時沒被使用就過期
        .build();

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;
    
    /**
     * redis自增數快取的key
     */
    private static final String DEVICE_INC_COUNT = "device_inc_count";
    
    /**
     * redis裝置編碼對應自增數的hash快取key
     */
    private static final String DEVICE_INC_VALUE = "device_inc_value";
    
    /**
     * 獲取裝置自增數
     */
    public int getInc(String deviceCode){
        // 1.從本地快取獲取
        Integer inc = localCache.get(deviceCode);
        if(inc != null) {
            return inc;
        }
        // 2.本地快取未命中,從redis的hash快取獲取
        inc = (Integer)redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode);
        // 3. redis的hash快取中沒有,說明是新裝置,先為裝置生成一個自增號
        if(inc == null) {
            inc = redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue;
            // 新增到redis hash快取
            redisTemplate.opsForHash().put(DEVICE_INC_VALUE, deviceCode, inc);
        }
        // 4.新增到本地快取
        localCache.put(deviceCode, inc);
        // 4.返回自增數
        return inc;
    }
    
}

優點

  • redis保證資料可持久,本地快取保證超高的讀取效能,微服務共用redis大快取的場景能有效降低redis壓力
  • guava作為本地快取,提供了豐富的api,過期策略,最大容量,保證服務記憶體可控,冷資料不會長期佔據記憶體空間
  • 服務重啟導致的本地快取清空不會影響業務進行
  • 微服務及分散式場景使用,分散式情況下每個服務範例只會快取自己接入的那一部分裝置的自增號,本地記憶體空間最優
  • 在範例業務中,自增數滿足了分佈區傳送的均勻分佈需求,也可以滿足統計裝置接入數目的業務場景,一舉兩得

缺點

  • 增加編碼複雜度,不直接
  • 只適用於快取內容只增不改的場景

總結

  • 本地快取空間可控,過期策略優
  • 適用於微服務及分散式場景
  • 快取內容不能發生改變
  • 效能優

後記

redis提供了豐富的資料型別及api,非常適合業務系統開發,統計計數(increment,decrement),標記位(bitmap),鬆散資料(hash),先進先出、佇列式讀取(list);guava快取作為本地快取,能夠高效的讀取的同時,提供了大量api方便我們控制本地快取的資料量及冷資料淘汰;我們充分的學習這些特效能夠幫助我們在業務開發中更加輕鬆靈活,在空間與時間上找到一個平衡點。

到此這篇關於Redis與本地快取的結合實現的文章就介紹到這了,更多相關Redis本地快取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com