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

James_Shangguan發表於2024-05-20

《經驗之談:我為什麼選擇了這樣一個激進的快取大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/

相關文章