本地緩存同步的一個簡單方案

現在大部分系統使用的都是分佈式緩存系統Redis。 但在一些場景下,比如緩存單元很大,單元數不多,變化很小,加載時間很長,如算法模型。 這個時候使用本地緩存比Redis的效率要高很多,但是又要保證集羣中各個機器的緩存的一致性,不然就會出現請求耗時不穩定的情況,也有可能出現相同的請求不同服務器返回的結果不一致。
本文介紹了一個簡單的實現集羣中同步各服務器本地緩存的方案。

實現思路:

  • 集羣各個節點通過Redis的pub/sub機制實現簡單的消息隊列,把緩存的變化廣播給集羣中所有節點。
  • 因爲緩存單元的數據本身很大,但是數量並不多,所以只把緩存數據的id保存在Redis的set中。

整個過程分成兩個階段:初始同步與廣播同步

初始同步

程序啓動時,一開始沒有緩存任何模型數據,進入初始同步階段。流程如下:

 

初始同步

監聽緩存變更事件

獲取緩存事件後,並不立即操作,後續再順序處理該事件

下面一些操作都用redis命令演示,實際項目中,使用的是jedis

redis> subscribe channel.model

獲取緩存的數據id

一般從redis讀取緩存的模型id列表

redis> smembers cache.models

緩存所有模型數據

根據上一步讀到的id列表,緩存所有模型數據

一般是從數據庫或分佈式文件系統中加載模型

增量更新

如果到緩存模型數據結束,有監聽到緩存變更事件,則依次響應該事件

完成增量更新後,節點接入下一個階段:廣播同步


廣播同步

集羣中的每個節點都訂閱頻道channel.model, 接收緩存變更的消息(增、刪、改);也在主動變更後,往頻道channel.model發佈消息來廣播給其他節點。消息分爲以下三種類型:

  • 新增緩存
    一般是請求第一次到達,或者是模型生成後,收到HTTP更新消息,就會預加載模型文件。
redis> publish channel.model add:1
  • 更新緩存
redis> publish channel.model update:1
  • 刪除緩存
    不僅僅是用戶邏輯觸發緩存的刪除,更大的可能是因爲緩存策略需要刪除長期不使用的緩存。
    比如我們常用的Gauva Cache。設置如下:
CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterAccess(1, TimeUnit.DAYS)
        .removalListener((RemovalListener<Integer, Model>) notification -> {
            final RemovalCause cause = notification.getCause();
            switch (cause) {
                    //緩存到期
                case EXPIRED:
                    //緩存大小限制
                case SIZE:
                    //緩存被垃圾回收
                case COLLECTED:
                    //如果是緩存到期等原因被刪除,則需要通知分佈式環境下的其他機器也要刪除
                    distCacheManager.removeFromCache(notification.getKey());
                    break;
                    //緩存顯示刪除(這裏沒有調用是避免事件循環)
                case EXPLICIT:
                    //緩存顯示替換(這了沒有調用是避免事件循環)
                case REPLACED:
                    break;
                default:
                    log.error("there should not be [{}]", cause);
            }
redis> publish channel.model delete:1

優缺點

優點:

  • 實現簡單:基於廣泛使用的Redis,沒有引入其他組件,而且實現邏輯也很簡單

缺點:

  • 在一些極端情況下,會出現緩存的更新不及時。比如模型更新後,收到請求的進程本地更新後返回結果,因爲消息是異步的,可能還沒達到Redis時,進程就掛掉了。
  • 當模型更新時,各個進程中緩存的模型在很短的時間內存在不一致的情況。 會影響部分用戶。不過這種情況是完全可以接受的。

注意事項

  • 因爲所有節點都訂閱了同一頻道channel.model,也會接聽到自身廣播的事件,所以節點在響應事件時,可以做冪等處理
  • Java程序使用Jedis實現頻道訂閱,訂閱調用是阻塞的,所以需要使用單獨的線程來執行,不能阻塞主幹流程
  • Jedis頻道訂閱線程可能會與Redis斷開連接,需要捕捉異常,並重新訂閱

參考

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