Redis-Guava 的本地緩存組合

前言

我們開發中經常用到 Redis 作爲緩存,將高頻數據放在 Redis 中能夠提高業務性能,降低 MySQL 等關係型數據庫壓力,甚至一些系統使用 Redis 進行數據持久化,Redis 鬆散的文檔結構非常適合業務系統開發,在精確查詢,數據統計業務有着很大的優勢。

但是高頻數據流處理系統中,Redis 的壓力也會很大,同時 I/O 開銷纔是耗時的主要原因,這時候爲了降低 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 壓力。

代碼示例:

/**
 * 此緩存演示如何結合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 提供了豐富的數據類型及 api,非常適合業務系統開發,統計計數(increment,decrement),標記位(bitmap),鬆散數據(hash),先進先出、隊列式讀取(list)。

guava 緩存作爲本地緩存,能夠高效的讀取的同時,提供了大量 api 方便我們控制本地緩存的數據量及冷數據淘汰。

我們充分的學習這些特性能夠幫助我們在業務開發中更加輕鬆靈活,在空間與時間上找到一個平衡點。

作者:熱黃油啤酒

來源:https://juejin.cn/post/7000263632151904293

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/T-Pis0USfjILo0i5pvM5sw