當裝飾者模式遇上Read Through緩存,一場技術的浪漫邂逅

《經驗之談:我爲什麼選擇了這樣一個激進的緩存大Key治理方案》一文中,我提到在系統中使用的緩存是旁路緩存模式,有讀者朋友問,有沒有用到過其他的緩存模式,本文將結合一個我曾經工作中的案例,使用裝飾者模式實現Read Through緩存模式,助你輕鬆掌握設計模式和緩存。

一、緩存模式

不說廢話,直入主題。緩存模式是指用於管理緩存數據的策略和方式。常見的有這幾種:Cache Aside、Read Through、Write Through、Write Back等

Cache Aside

在平時的開發工作中,旁路緩存是最常用的一種緩存模式,“旁路”二字意味着讀取緩存、讀取數據、更新緩存和操作都是在應用程序中完成的,緩存和數據庫之間是沒有連接的

Read Through

在這種模式下,緩存與數據庫是連接起來的。當緩存未命中的時候,緩存中數據庫中加載數據,填充緩存並返回給應用程序。

雖然Read Through和Cache Aside非常相似,但至少有兩個關鍵區別:

  1. 在Cache Aside中,應用程序負責從數據庫獲取數據並填充緩存。在Read Through中,此邏輯通常由庫或獨立緩存提供程序來實現。
  2. 與Cache Aside不同,Read Through緩存中的數據模型不能與數據庫的數據模型不同。

Write Through

在穿透寫入模式下,當應用程序需要更新數據時,它會先更新到緩存,同時更新到數據源,這兩個操作在共一個事務中完成。因此只有2個都寫成功了纔會最終寫成功,有助於保持緩存和數據源之間的數據一致性。

當應用程序想要寫入數據或更新值時,會發生以下情況:

  1. 應用程序將數據直接寫入緩存。
  2. 緩存更新主數據庫中的數據。當寫入完成後,緩存和數據庫都具有相同的值,並且緩存始終保持一致。

Write Back

在這個模式下,當寫入數據的時候,只是寫到了緩存。當緩存過期的時候,纔會被刷新到數據庫。這個模式最大的一個問題就是如果緩存突然宕機,那麼還沒有刷新到數據庫的數據就徹底丟失了。

說明

我認爲嚴格來說,現在的緩存工作模式都歸屬於旁路緩存。如果在代碼中的緩存層(類似於Mapper層)進行了封裝的話;那麼在業務代碼中調用緩存層方法進行操作,這裏屏蔽了緩存的實現細節,站在業務代碼層面來看,可以暫且認爲是實現了不同的緩存模式吧。

二、代理模式和裝飾者模式

那如何優雅實現上述緩存層的封裝,在開發中可以絲滑接入呢?接着看,這裏需要使用一些設計模式。

這是代理模式、裝飾者模式的代碼結構示例:二者結構完全一致。

// 裝飾者模式代碼結構
public interface AInterface {
    void run();
}

public class A implements AInterface {
    @Override
    public void run() {
         // run
        System.out.println("A run...");
    }
}

public class ADercorator implements AInterface {

    private AInterface a;

    public ADercorator(AInterface a) {
        this.a = a;
    }

    @Override
    public void run() {
        // doSomething
        System.out.println("ADerocator do something...");
        a.run();
        // doSomething
    }
}

當然代理模式和裝飾者模式還是有區別的,主要是看應用的場景。

代理模式主要是爲其他對象提供一種代理,以控制對這個對象的訪問

例如有一個案例這樣的,一個RPC接口的提供方和調用方都是雙機房部署的,原本的遠程調用也是機房垂直調用(機房A只調用機房A,機房B只調用機房B)的;由於接口提供方接口性能原因,希望調用方改爲跨機房調用(機房A同時調用機房A和機房B,機房B同時調用機房A和機房B)。我的實現方式是使用代理模式,在代碼中新增一個接口調用的代理層,在代理層中注入機房A和機房B的RPC Consumer配置,根據時間戳的奇偶來判斷調用哪個機房,從而實現跨機房調用。

而裝飾者模式則指在不改變現有對象結構的情況下,動態地給該對象增加一些職責(即增加其額外功能)的模式。在《Head First 設計模式》一書中,更是將裝飾者模式稱爲“給愛用繼承的人一個全新的設計眼界”。例如對於不同緩存模式的實現,可以使用裝飾者模式。

三、裝飾者模式實現Read Through緩存模式

這是我之前做過的一個RPC讀接口性能優化的案例。系統架構比較簡單,就是一個常規的Java Web應用,對外提供RPC服務,底層數據採用的是MySQL,緩存使用的是Redis。

該查詢接口涉及數據庫多張MySQL表和多個外部RPC接口的查詢

在接口性能優化過程中,

對於多張表查詢,檢查慢SQL並進行了索引優化;

對於多外部RPC接口調用改爲並行調用;

後續又使用了Redis緩存進行緩存數據。

當時剛進入職場不久,作爲一個職場新人,年輕氣盛,還想着彰顯一下個人的技術,所以我打算使用裝飾者模式來實現Read Through緩存模式;並且實現了一個當時認爲稍微“展示一點技術”的方案:當緩存未命中時,從數據庫加載數據,返回業務層,將寫入緩存改爲異步寫入

大概代碼如下,大家參考:

public interface CacheInterface {
    String get(String key);
    String set(String key, String value);
}

public class Cache implements CacheInterface {

    private Jedis jedis;

    @Override
    public String get(String key) {
         // run
        System.out.println("Cache get...");
        return jedis.get(key);
    }

    @Override
    public String set(String key, String value) {
        return jedis.set(key, value);
    }
}

public class CacheDecorator implements CacheInterface {

    private Repository myRepositoty = new MyRepository();

    private CacheInterface cache;

    public CacheDecorator(CacheInterface a) {
        this.cache = cache;
    }

    @Override
    public String get(String key) {
        // doSomething
        System.out.println("CacheDecorator do something...");
        String cacheResult = cache.get(key);
        if (!StringUtils.isEmpty(cacheResult)) {
            return cacheResult;
        }
        String result = myRepositoty.get(key);
        CompletableFuture.runAsync(() -> cache.set(key, result));
        return result;
    }

    @Override
    public String set(String key, String value) {
        return cache.set(key, value);
    }
}

public class Test {

    public static void main(String[] args) {
        Cache cache = new Cache();
        CacheDecorator derocator = new CacheDecorator(cache);
        derocator.get("a");
    }

}  

一起學習

歡迎各位在評論區或者私信我一起交流討論,或者加我主頁weixin,備註技術渠道(如博客園),進入技術交流羣,我們一起討論和交流,共同進步!

也歡迎大家關注我的博客園、公衆號(碼上暴富),點贊、留言、轉發。你的支持,是我更文的最大動力!

參考資料

https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章